from django import forms from django.forms import ( ModelForm, ValidationError, CheckboxInput, TextInput, ) from crispy_forms.helper import FormHelper from crispy_forms.layout import ( Layout, Fieldset, Div, Row, Column, Submit, HTML, ) from research.models import ResearchStudy, ResearchStudyArm, ResearchParticipant class ResearchStudyForm(ModelForm): """Form for creating/editing research studies.""" class Meta: model = ResearchStudy fields = [ "name", "description", "active", "randomisation_mode", "generation_mode", "balance_within_candidate_group", "collect_demographics", "allocation_targets", ] widgets = { "description": forms.Textarea(attrs={"rows": 4}), "allocation_targets": forms.Textarea(attrs={"rows": 4, "placeholder": '{"arm_1": 30, "arm_2": 30}'}), } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.helper = FormHelper() self.helper.form_method = 'post' self.helper.layout = Layout( Fieldset( 'Study Details', 'name', 'description', 'active', ), Fieldset( 'Participant Creation', 'generation_mode', 'collect_demographics', ), Fieldset( 'Randomisation Strategy', 'randomisation_mode', 'balance_within_candidate_group', Row( Column('allocation_targets', css_class='col-md-12'), HTML('For target-based allocation, provide JSON like: {"arm_id": count, ...}'), ), ), Submit('submit', 'Save Study'), ) class ResearchStudyArmForm(ModelForm): """Form for creating/editing study arms (packets).""" class Meta: model = ResearchStudyArm fields = [ "name", "packet", "active", "allocation_weight", "sort_order", "order_sequence", "required_previous_arm", ] def __init__(self, *args, study=None, **kwargs): super().__init__(*args, **kwargs) self.helper = FormHelper() self.helper.form_method = 'post' # Filter previous_arm choices to arms in the same study if study: self.fields['required_previous_arm'].queryset = ResearchStudyArm.objects.filter( study=study ).exclude(pk=self.instance.pk if self.instance.pk else None) else: self.fields['required_previous_arm'].queryset = ResearchStudyArm.objects.none() self.helper.layout = Layout( Fieldset( 'Arm Configuration', 'name', 'packet', 'active', 'sort_order', ), Fieldset( 'Randomisation', 'allocation_weight', ), Fieldset( 'Ordering & Prerequisites', 'order_sequence', 'required_previous_arm', HTML('Set order_sequence to enforce ordering. Set required_previous_arm to enforce prerequisites.'), ), Submit('submit', 'Save Arm'), ) class ResearchParticipantIntakeForm(ModelForm): """Form for participant intake (demographics and consent).""" class Meta: model = ResearchParticipant fields = [ "candidate_group", "age_band", "sex", "training_grade", "years_experience", "email", "consented", ] widgets = { "candidate_group": TextInput(attrs={"placeholder": "e.g. Group A"}), "age_band": TextInput(attrs={"placeholder": "e.g. 30-39"}), "sex": TextInput(attrs={"placeholder": "Optional"}), "training_grade": TextInput(attrs={"placeholder": "e.g. ST4"}), "years_experience": TextInput(attrs={"placeholder": "e.g. 5"}), "email": TextInput(attrs={"placeholder": "optional@example.com", "type": "email"}), "consented": CheckboxInput(), } def __init__(self, *args, collect_demographics=True, **kwargs): super().__init__(*args, **kwargs) self.collect_demographics = collect_demographics if not collect_demographics: # Make demographic fields optional if not collecting for f in [ "candidate_group", "age_band", "sex", "training_grade", "years_experience", ]: self.fields[f].required = False else: # Make candidate_group required if collecting (useful for balancing) self.fields["candidate_group"].required = True self.helper = FormHelper() self.helper.form_method = 'post' self.helper.layout = Layout( Fieldset( 'Demographics', 'candidate_group', 'age_band', 'sex', 'training_grade', 'years_experience', ), Fieldset( 'Contact & Consent', 'email', HTML('