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 ,
@@ -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
274225class 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
311255class 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
354293class 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
406342class 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
482416class 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 )
0 commit comments