Skip to content

Commit bd60e6a

Browse files
committed
fix conditional area_description fields
Refactor form to use new base class, and create an "area_description" field for each "area" option. We need each of these fields to be a separate field on the HTML form, otherwise labelling and error links will not work properly.
1 parent a818e95 commit bd60e6a

File tree

10 files changed

+136
-184
lines changed

10 files changed

+136
-184
lines changed

manage_breast_screening/mammograms/forms/symptom_forms.py

Lines changed: 82 additions & 150 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
from dataclasses import dataclass
21
from datetime import date
3-
from typing import Any
42

53
from django.db.models import TextChoices
6-
from django.forms import CheckboxSelectMultiple, Form, ValidationError
4+
from django.forms import CheckboxSelectMultiple
75
from django.forms.widgets import Textarea
86

97
from manage_breast_screening.core.services.auditor import Auditor
@@ -17,6 +15,7 @@
1715
MultipleChoiceField,
1816
RadioSelectWithoutFieldset,
1917
)
18+
from manage_breast_screening.nhsuk_forms.forms import FormWithConditionalFields
2019
from manage_breast_screening.nhsuk_forms.utils import YesNo, yes_no, yes_no_field
2120
from manage_breast_screening.participants.models.symptom import (
2221
NippleChangeChoices,
@@ -109,21 +108,14 @@ def area_description(symptom_name="symptom", hint="For example, the left armpit"
109108
)
110109

111110

112-
class SymptomForm(Form):
111+
class SymptomForm(FormWithConditionalFields):
113112
"""
114113
A base form class for entering symptoms. To be overriden for different symptom types.
115114
"""
116115

117-
@dataclass
118-
class ConditionalRequirement:
119-
conditionally_required_field: str
120-
predicate_field: str
121-
predicate_field_value: Any
122-
123116
def __init__(self, symptom_type, instance=None, **kwargs):
124117
self.instance = instance
125118
self.symptom_type = symptom_type
126-
self.conditional_requirements = []
127119

128120
if instance:
129121
kwargs["initial"] = self.initial_values(instance)
@@ -136,7 +128,7 @@ def initial_values(self, instance):
136128
"""
137129
return {
138130
"area": instance.area,
139-
"area_description": instance.area_description,
131+
f"area_description_{instance.area.lower()}": instance.area_description,
140132
"symptom_sub_type": instance.symptom_sub_type_id,
141133
"symptom_sub_type_details": instance.symptom_sub_type_details,
142134
"when_started": instance.when_started,
@@ -162,8 +154,14 @@ def model_values(self):
162154
area = one
163155
case _:
164156
area = self.cleaned_data["area"]
157+
area_description_field_name = f"area_description_{area.lower()}"
158+
area_description = self.cleaned_data.get(
159+
area_description_field_name, ""
160+
)
161+
162+
area_description_field_name = f"area_description_{area.lower()}"
163+
area_description = self.cleaned_data.get(area_description_field_name, "")
165164

166-
area_description = self.cleaned_data.get("area_description", "")
167165
symptom_sub_type = self.cleaned_data.get("symptom_sub_type")
168166
symptom_sub_type_details = self.cleaned_data.get("symptom_sub_type_details", "")
169167
when_started = self.cleaned_data.get("when_started")
@@ -191,53 +189,6 @@ def model_values(self):
191189
additional_information=additional_information,
192190
)
193191

194-
def set_conditionally_required(
195-
self, conditionally_required_field, predicate_field, predicate_field_value
196-
):
197-
"""
198-
Mark a field as conditionally required if and only if another field (the predicate field)
199-
is set to a specific value.
200-
If the predicate field is set to the predicate value, this field will require a value.
201-
If the predicate field is set to a different value, this field's value will be ignored.
202-
"""
203-
if conditionally_required_field not in self.fields:
204-
raise ValueError(f"{conditionally_required_field} is not a valid field")
205-
if predicate_field not in self.fields:
206-
raise ValueError(f"{predicate_field} is not a valid field")
207-
208-
self.conditional_requirements.append(
209-
self.ConditionalRequirement(
210-
conditionally_required_field=conditionally_required_field,
211-
predicate_field=predicate_field,
212-
predicate_field_value=predicate_field_value,
213-
)
214-
)
215-
216-
self.fields[conditionally_required_field].required = False
217-
218-
def clean(self):
219-
for requirement in self.conditional_requirements:
220-
field = requirement.conditionally_required_field
221-
222-
if (
223-
self.cleaned_data.get(requirement.predicate_field)
224-
== requirement.predicate_field_value
225-
):
226-
cleaned_value = self.cleaned_data.get(field)
227-
if isinstance(cleaned_value, str):
228-
cleaned_value = cleaned_value.strip()
229-
230-
if not cleaned_value:
231-
self.add_error(
232-
field,
233-
ValidationError(
234-
message=self.fields[field].error_messages["required"],
235-
code="required",
236-
),
237-
)
238-
else:
239-
del self.cleaned_data[field]
240-
241192
def update(self, request):
242193
auditor = Auditor.from_request(request)
243194
field_values = self.model_values()
@@ -273,7 +224,9 @@ def create(self, appointment, request):
273224

274225
class LumpForm(SymptomForm):
275226
area = CommonFields.area_radios(symptom_name="lump")
276-
area_description = CommonFields.area_description(symptom_name="lump")
227+
area_description_right_breast = CommonFields.area_description("lump")
228+
area_description_left_breast = CommonFields.area_description("lump")
229+
area_description_other = CommonFields.area_description("lump")
277230
when_started = CommonFields.when_started
278231
specific_date = CommonFields.specific_date
279232
intermittent = CommonFields.intermittent
@@ -286,33 +239,28 @@ class LumpForm(SymptomForm):
286239
def __init__(self, instance=None, **kwargs):
287240
super().__init__(symptom_type=SymptomType.LUMP, instance=instance, **kwargs)
288241

289-
self.set_conditionally_required(
290-
conditionally_required_field="area_description",
291-
predicate_field="area",
292-
predicate_field_value=RightLeftOtherChoices.OTHER,
293-
)
294-
self.set_conditionally_required(
295-
conditionally_required_field="specific_date",
296-
predicate_field="when_started",
297-
predicate_field_value=RelativeDateChoices.SINCE_A_SPECIFIC_DATE,
298-
)
299-
self.set_conditionally_required(
300-
conditionally_required_field="when_resolved",
301-
predicate_field="recently_resolved",
302-
predicate_field_value=True,
303-
)
304-
self.set_conditionally_required(
305-
conditionally_required_field="investigation_details",
306-
predicate_field="investigated",
307-
predicate_field_value=YesNo.YES,
242+
self.given_field("area").require_field_with_prefix("area_description")
243+
244+
self.given_field_value(
245+
"when_started", RelativeDateChoices.SINCE_A_SPECIFIC_DATE
246+
).require_field("specific_date")
247+
248+
self.given_field_value("recently_resolved", True).require_field("when_resolved")
249+
250+
self.given_field_value("investigated", YesNo.YES).require_field(
251+
"investigation_details"
308252
)
309253

310254

311255
class SwellingOrShapeChangeForm(SymptomForm):
312256
area = CommonFields.area_radios(symptom_name="swelling or shape change")
313-
area_description = CommonFields.area_description(
314-
symptom_name="swelling or shape change"
257+
area_description_right_breast = CommonFields.area_description(
258+
"swelling or shape change"
315259
)
260+
area_description_left_breast = CommonFields.area_description(
261+
"swelling or shape change"
262+
)
263+
area_description_other = CommonFields.area_description("swelling or shape change")
316264
when_started = CommonFields.when_started
317265
specific_date = CommonFields.specific_date
318266
intermittent = CommonFields.intermittent
@@ -329,31 +277,24 @@ def __init__(self, instance=None, **kwargs):
329277
**kwargs,
330278
)
331279

332-
self.set_conditionally_required(
333-
conditionally_required_field="area_description",
334-
predicate_field="area",
335-
predicate_field_value=RightLeftOtherChoices.OTHER,
336-
)
337-
self.set_conditionally_required(
338-
conditionally_required_field="specific_date",
339-
predicate_field="when_started",
340-
predicate_field_value=RelativeDateChoices.SINCE_A_SPECIFIC_DATE,
341-
)
342-
self.set_conditionally_required(
343-
conditionally_required_field="when_resolved",
344-
predicate_field="recently_resolved",
345-
predicate_field_value=True,
346-
)
347-
self.set_conditionally_required(
348-
conditionally_required_field="investigation_details",
349-
predicate_field="investigated",
350-
predicate_field_value=YesNo.YES,
280+
self.given_field("area").require_field_with_prefix("area_description")
281+
282+
self.given_field_value(
283+
"when_started", RelativeDateChoices.SINCE_A_SPECIFIC_DATE
284+
).require_field("specific_date")
285+
286+
self.given_field_value("recently_resolved", True).require_field("when_resolved")
287+
288+
self.given_field_value("investigated", YesNo.YES).require_field(
289+
"investigation_details"
351290
)
352291

353292

354293
class SkinChangeForm(SymptomForm):
355294
area = CommonFields.area_radios(symptom_name="skin change")
356-
area_description = CommonFields.area_description(symptom_name="skin change")
295+
area_description_right_breast = CommonFields.area_description("skin change")
296+
area_description_left_breast = CommonFields.area_description("skin change")
297+
area_description_other = CommonFields.area_description("skin change")
357298
symptom_sub_type = ChoiceField(
358299
choices=SkinChangeChoices,
359300
label="How has the skin changed?",
@@ -381,27 +322,22 @@ def __init__(self, instance=None, **kwargs):
381322
**kwargs,
382323
)
383324

384-
self.set_conditionally_required(
385-
conditionally_required_field="area_description",
386-
predicate_field="area",
387-
predicate_field_value=RightLeftOtherChoices.OTHER,
388-
)
389-
self.set_conditionally_required(
390-
conditionally_required_field="symptom_sub_type_details",
391-
predicate_field="symptom_sub_type",
392-
predicate_field_value=SkinChangeChoices.OTHER,
393-
)
394-
self.set_conditionally_required(
395-
conditionally_required_field="specific_date",
396-
predicate_field="when_started",
397-
predicate_field_value=RelativeDateChoices.SINCE_A_SPECIFIC_DATE,
398-
)
399-
self.set_conditionally_required(
400-
conditionally_required_field="investigation_details",
401-
predicate_field="investigated",
402-
predicate_field_value=YesNo.YES,
325+
self.given_field("area").require_field_with_prefix("area_description")
326+
327+
self.given_field_value(
328+
"when_started", RelativeDateChoices.SINCE_A_SPECIFIC_DATE
329+
).require_field("specific_date")
330+
331+
self.given_field_value("recently_resolved", True).require_field("when_resolved")
332+
333+
self.given_field_value("investigated", YesNo.YES).require_field(
334+
"investigation_details"
403335
)
404336

337+
self.given_field_value(
338+
"symptom_sub_type", SkinChangeChoices.OTHER
339+
).require_field("symptom_sub_type_details")
340+
405341

406342
class NippleChangeForm(SymptomForm):
407343
area = MultipleChoiceField(
@@ -438,22 +374,20 @@ def __init__(self, instance=None, **kwargs):
438374
**kwargs,
439375
)
440376

441-
self.set_conditionally_required(
442-
conditionally_required_field="symptom_sub_type_details",
443-
predicate_field="symptom_sub_type",
444-
predicate_field_value=NippleChangeChoices.OTHER,
445-
)
446-
self.set_conditionally_required(
447-
conditionally_required_field="specific_date",
448-
predicate_field="when_started",
449-
predicate_field_value=RelativeDateChoices.SINCE_A_SPECIFIC_DATE,
450-
)
451-
self.set_conditionally_required(
452-
conditionally_required_field="investigation_details",
453-
predicate_field="investigated",
454-
predicate_field_value=YesNo.YES,
377+
self.given_field_value(
378+
"when_started", RelativeDateChoices.SINCE_A_SPECIFIC_DATE
379+
).require_field("specific_date")
380+
381+
self.given_field_value("recently_resolved", True).require_field("when_resolved")
382+
383+
self.given_field_value("investigated", YesNo.YES).require_field(
384+
"investigation_details"
455385
)
456386

387+
self.given_field_value(
388+
"symptom_sub_type", NippleChangeChoices.OTHER
389+
).require_field("symptom_sub_type_details")
390+
457391
def initial_values(self, instance):
458392
return {
459393
"area": self.area_initial(instance.area),
@@ -481,7 +415,9 @@ def area_initial(self, area):
481415

482416
class OtherSymptomForm(SymptomForm):
483417
area = CommonFields.area_radios()
484-
area_description = CommonFields.area_description()
418+
area_description_right_breast = CommonFields.area_description()
419+
area_description_left_breast = CommonFields.area_description()
420+
area_description_other = CommonFields.area_description()
485421
symptom_sub_type_details = CharField(
486422
label="Describe the symptom",
487423
label_classes="nhsuk-label--m",
@@ -504,18 +440,14 @@ def __init__(self, instance=None, **kwargs):
504440
**kwargs,
505441
)
506442

507-
self.set_conditionally_required(
508-
conditionally_required_field="area_description",
509-
predicate_field="area",
510-
predicate_field_value=RightLeftOtherChoices.OTHER,
511-
)
512-
self.set_conditionally_required(
513-
conditionally_required_field="specific_date",
514-
predicate_field="when_started",
515-
predicate_field_value=RelativeDateChoices.SINCE_A_SPECIFIC_DATE,
516-
)
517-
self.set_conditionally_required(
518-
conditionally_required_field="investigation_details",
519-
predicate_field="investigated",
520-
predicate_field_value=YesNo.YES,
443+
self.given_field("area").require_field_with_prefix("area_description")
444+
445+
self.given_field_value(
446+
"when_started", RelativeDateChoices.SINCE_A_SPECIFIC_DATE
447+
).require_field("specific_date")
448+
449+
self.given_field_value("recently_resolved", True).require_field("when_resolved")
450+
451+
self.given_field_value("investigated", YesNo.YES).require_field(
452+
"investigation_details"
521453
)

manage_breast_screening/mammograms/jinja2/mammograms/medical_information/symptoms/nipple_change.jinja renamed to manage_breast_screening/mammograms/jinja2/mammograms/medical_information/symptoms/forms/nipple_change.jinja

File renamed without changes.

manage_breast_screening/mammograms/jinja2/mammograms/medical_information/symptoms/other.jinja renamed to manage_breast_screening/mammograms/jinja2/mammograms/medical_information/symptoms/forms/other.jinja

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@
44

55
{% block form %}
66
{% do form.area.add_divider_after("LEFT_BREAST", "or") %}
7-
{% do form.area.add_conditional_html(
8-
'OTHER',
9-
form.area_description.as_field_group()
10-
) %}
7+
{% do form.area.add_conditional_html('RIGHT_BREAST', form.area_description_right_breast.as_field_group()) %}
8+
{% do form.area.add_conditional_html('LEFT_BREAST', form.area_description_left_breast.as_field_group()) %}
9+
{% do form.area.add_conditional_html('OTHER', form.area_description_other.as_field_group()) %}
1110

1211
{% do form.when_started.add_divider_after("OVER_THREE_YEARS", "or") %}
1312
{% do form.when_started.add_divider_after("SINCE_A_SPECIFIC_DATE", "or") %}

manage_breast_screening/mammograms/jinja2/mammograms/medical_information/symptoms/simple_symptom.jinja renamed to manage_breast_screening/mammograms/jinja2/mammograms/medical_information/symptoms/forms/simple_symptom.jinja

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@
44

55
{% block form %}
66
{% do form.area.add_divider_after("LEFT_BREAST", "or") %}
7-
{% do form.area.add_conditional_html(
8-
'OTHER',
9-
form.area_description.as_field_group()
10-
) %}
7+
{% do form.area.add_conditional_html('RIGHT_BREAST', form.area_description_right_breast.as_field_group()) %}
8+
{% do form.area.add_conditional_html('LEFT_BREAST', form.area_description_left_breast.as_field_group()) %}
9+
{% do form.area.add_conditional_html('OTHER', form.area_description_other.as_field_group()) %}
1110

1211
{% do form.when_started.add_divider_after("OVER_THREE_YEARS", "or") %}
1312
{% do form.when_started.add_divider_after("SINCE_A_SPECIFIC_DATE", "or") %}

manage_breast_screening/mammograms/jinja2/mammograms/medical_information/symptoms/skin_change.jinja renamed to manage_breast_screening/mammograms/jinja2/mammograms/medical_information/symptoms/forms/skin_change.jinja

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@
44

55
{% block form %}
66
{% do form.area.add_divider_after("LEFT", "or") %}
7-
{% do form.area.add_conditional_html(
8-
'OTHER',
9-
form.area_description.as_field_group()
10-
) %}
7+
{% do form.area.add_conditional_html('RIGHT_BREAST', form.area_description_right_breast.as_field_group()) %}
8+
{% do form.area.add_conditional_html('LEFT_BREAST', form.area_description_left_breast.as_field_group()) %}
9+
{% do form.area.add_conditional_html('OTHER', form.area_description_other.as_field_group()) %}
1110

1211
{% do form.symptom_sub_type.add_conditional_html('SKIN_CHANGE_OTHER', form.symptom_sub_type_details.as_field_group()) %}
1312

0 commit comments

Comments
 (0)