1- from dataclasses import dataclass
21from datetime import date
3- from typing import Any
42
53from django .db .models import TextChoices
6- from django .forms import CheckboxSelectMultiple , Form , ValidationError
4+ from django .forms import CheckboxSelectMultiple
75from django .forms .widgets import Textarea
86
97from manage_breast_screening .core .services .auditor import Auditor
1715 MultipleChoiceField ,
1816 RadioSelectWithoutFieldset ,
1917)
18+ from manage_breast_screening .nhsuk_forms .forms import FormWithConditionalFields
2019from manage_breast_screening .nhsuk_forms .utils import YesNo , yes_no , yes_no_field
2120from manage_breast_screening .participants .models .symptom import (
2221 NippleChangeChoices ,
@@ -97,7 +96,11 @@ def area_radios(symptom_name="symptom"):
9796 )
9897
9998 @staticmethod
100- def area_description (symptom_name = "symptom" , hint = "For example, the left armpit" ):
99+ def area_description (
100+ symptom_name = "symptom" ,
101+ hint = "For example, the left armpit" ,
102+ visually_hidden_label_suffix = None ,
103+ ):
101104 return CharField (
102105 required = False ,
103106 label = "Describe the specific area" ,
@@ -106,24 +109,18 @@ def area_description(symptom_name="symptom", hint="For example, the left armpit"
106109 "required" : f"Describe the specific area where the { symptom_name } is located"
107110 },
108111 classes = "nhsuk-u-width-two-thirds" ,
112+ visually_hidden_label_suffix = visually_hidden_label_suffix ,
109113 )
110114
111115
112- class SymptomForm (Form ):
116+ class SymptomForm (FormWithConditionalFields ):
113117 """
114118 A base form class for entering symptoms. To be overriden for different symptom types.
115119 """
116120
117- @dataclass
118- class ConditionalRequirement :
119- conditionally_required_field : str
120- predicate_field : str
121- predicate_field_value : Any
122-
123121 def __init__ (self , symptom_type , instance = None , ** kwargs ):
124122 self .instance = instance
125123 self .symptom_type = symptom_type
126- self .conditional_requirements = []
127124
128125 if instance :
129126 kwargs ["initial" ] = self .initial_values (instance )
@@ -136,7 +133,7 @@ def initial_values(self, instance):
136133 """
137134 return {
138135 "area" : instance .area ,
139- "area_description " : instance .area_description ,
136+ f"area_description_ { instance . area . lower () } " : instance .area_description ,
140137 "symptom_sub_type" : instance .symptom_sub_type_id ,
141138 "symptom_sub_type_details" : instance .symptom_sub_type_details ,
142139 "when_started" : instance .when_started ,
@@ -162,8 +159,14 @@ def model_values(self):
162159 area = one
163160 case _:
164161 area = self .cleaned_data ["area" ]
162+ area_description_field_name = f"area_description_{ area .lower ()} "
163+ area_description = self .cleaned_data .get (
164+ area_description_field_name , ""
165+ )
166+
167+ area_description_field_name = f"area_description_{ area .lower ()} "
168+ area_description = self .cleaned_data .get (area_description_field_name , "" )
165169
166- area_description = self .cleaned_data .get ("area_description" , "" )
167170 symptom_sub_type = self .cleaned_data .get ("symptom_sub_type" )
168171 symptom_sub_type_details = self .cleaned_data .get ("symptom_sub_type_details" , "" )
169172 when_started = self .cleaned_data .get ("when_started" )
@@ -191,53 +194,6 @@ def model_values(self):
191194 additional_information = additional_information ,
192195 )
193196
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-
241197 def update (self , request ):
242198 auditor = Auditor .from_request (request )
243199 field_values = self .model_values ()
@@ -273,7 +229,15 @@ def create(self, appointment, request):
273229
274230class LumpForm (SymptomForm ):
275231 area = CommonFields .area_radios (symptom_name = "lump" )
276- area_description = CommonFields .area_description (symptom_name = "lump" )
232+ area_description_right_breast = CommonFields .area_description (
233+ "lump" , visually_hidden_label_suffix = "right breast"
234+ )
235+ area_description_left_breast = CommonFields .area_description (
236+ "lump" , visually_hidden_label_suffix = "left breast"
237+ )
238+ area_description_other = CommonFields .area_description (
239+ "lump" , visually_hidden_label_suffix = "other"
240+ )
277241 when_started = CommonFields .when_started
278242 specific_date = CommonFields .specific_date
279243 intermittent = CommonFields .intermittent
@@ -286,32 +250,29 @@ class LumpForm(SymptomForm):
286250 def __init__ (self , instance = None , ** kwargs ):
287251 super ().__init__ (symptom_type = SymptomType .LUMP , instance = instance , ** kwargs )
288252
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 ,
253+ self .given_field ("area" ).require_field_with_prefix ("area_description" )
254+
255+ self .given_field_value (
256+ "when_started" , RelativeDateChoices .SINCE_A_SPECIFIC_DATE
257+ ).require_field ("specific_date" )
258+
259+ self .given_field_value ("recently_resolved" , True ).require_field ("when_resolved" )
260+
261+ self .given_field_value ("investigated" , YesNo .YES ).require_field (
262+ "investigation_details"
308263 )
309264
310265
311266class SwellingOrShapeChangeForm (SymptomForm ):
312267 area = CommonFields .area_radios (symptom_name = "swelling or shape change" )
313- area_description = CommonFields .area_description (
314- symptom_name = "swelling or shape change"
268+ area_description_right_breast = CommonFields .area_description (
269+ "swelling or shape change" , visually_hidden_label_suffix = "right breast"
270+ )
271+ area_description_left_breast = CommonFields .area_description (
272+ "swelling or shape change" , visually_hidden_label_suffix = "left breast"
273+ )
274+ area_description_other = CommonFields .area_description (
275+ "swelling or shape change" , visually_hidden_label_suffix = "other"
315276 )
316277 when_started = CommonFields .when_started
317278 specific_date = CommonFields .specific_date
@@ -329,31 +290,30 @@ def __init__(self, instance=None, **kwargs):
329290 ** kwargs ,
330291 )
331292
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 ,
293+ self .given_field ("area" ).require_field_with_prefix ("area_description" )
294+
295+ self .given_field_value (
296+ "when_started" , RelativeDateChoices .SINCE_A_SPECIFIC_DATE
297+ ).require_field ("specific_date" )
298+
299+ self .given_field_value ("recently_resolved" , True ).require_field ("when_resolved" )
300+
301+ self .given_field_value ("investigated" , YesNo .YES ).require_field (
302+ "investigation_details"
351303 )
352304
353305
354306class SkinChangeForm (SymptomForm ):
355307 area = CommonFields .area_radios (symptom_name = "skin change" )
356- area_description = CommonFields .area_description (symptom_name = "skin change" )
308+ area_description_right_breast = CommonFields .area_description (
309+ "skin change" , visually_hidden_label_suffix = "right breast"
310+ )
311+ area_description_left_breast = CommonFields .area_description (
312+ "skin change" , visually_hidden_label_suffix = "left breast"
313+ )
314+ area_description_other = CommonFields .area_description (
315+ "skin change" , visually_hidden_label_suffix = "other"
316+ )
357317 symptom_sub_type = ChoiceField (
358318 choices = SkinChangeChoices ,
359319 label = "How has the skin changed?" ,
@@ -381,27 +341,22 @@ def __init__(self, instance=None, **kwargs):
381341 ** kwargs ,
382342 )
383343
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 ,
344+ self .given_field ("area" ).require_field_with_prefix ("area_description" )
345+
346+ self .given_field_value (
347+ "when_started" , RelativeDateChoices .SINCE_A_SPECIFIC_DATE
348+ ).require_field ("specific_date" )
349+
350+ self .given_field_value ("recently_resolved" , True ).require_field ("when_resolved" )
351+
352+ self .given_field_value ("investigated" , YesNo .YES ).require_field (
353+ "investigation_details"
403354 )
404355
356+ self .given_field_value (
357+ "symptom_sub_type" , SkinChangeChoices .OTHER
358+ ).require_field ("symptom_sub_type_details" )
359+
405360
406361class NippleChangeForm (SymptomForm ):
407362 area = MultipleChoiceField (
@@ -438,22 +393,20 @@ def __init__(self, instance=None, **kwargs):
438393 ** kwargs ,
439394 )
440395
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 ,
396+ self .given_field_value (
397+ "when_started" , RelativeDateChoices .SINCE_A_SPECIFIC_DATE
398+ ).require_field ("specific_date" )
399+
400+ self .given_field_value ("recently_resolved" , True ).require_field ("when_resolved" )
401+
402+ self .given_field_value ("investigated" , YesNo .YES ).require_field (
403+ "investigation_details"
455404 )
456405
406+ self .given_field_value (
407+ "symptom_sub_type" , NippleChangeChoices .OTHER
408+ ).require_field ("symptom_sub_type_details" )
409+
457410 def initial_values (self , instance ):
458411 return {
459412 "area" : self .area_initial (instance .area ),
@@ -481,7 +434,15 @@ def area_initial(self, area):
481434
482435class OtherSymptomForm (SymptomForm ):
483436 area = CommonFields .area_radios ()
484- area_description = CommonFields .area_description ()
437+ area_description_right_breast = CommonFields .area_description (
438+ visually_hidden_label_suffix = "right breast"
439+ )
440+ area_description_left_breast = CommonFields .area_description (
441+ visually_hidden_label_suffix = "left breast"
442+ )
443+ area_description_other = CommonFields .area_description (
444+ visually_hidden_label_suffix = "other"
445+ )
485446 symptom_sub_type_details = CharField (
486447 label = "Describe the symptom" ,
487448 label_classes = "nhsuk-label--m" ,
@@ -504,18 +465,14 @@ def __init__(self, instance=None, **kwargs):
504465 ** kwargs ,
505466 )
506467
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 ,
468+ self .given_field ("area" ).require_field_with_prefix ("area_description" )
469+
470+ self .given_field_value (
471+ "when_started" , RelativeDateChoices .SINCE_A_SPECIFIC_DATE
472+ ).require_field ("specific_date" )
473+
474+ self .given_field_value ("recently_resolved" , True ).require_field ("when_resolved" )
475+
476+ self .given_field_value ("investigated" , YesNo .YES ).require_field (
477+ "investigation_details"
521478 )
0 commit comments