diff --git a/src/registrar/forms/application_wizard.py b/src/registrar/forms/application_wizard.py index 1e2790214..eb4a6c173 100644 --- a/src/registrar/forms/application_wizard.py +++ b/src/registrar/forms/application_wizard.py @@ -551,6 +551,7 @@ class YourContactForm(RegistrarForm): class OtherContactsYesNoForm(RegistrarForm): has_other_contacts = forms.TypedChoiceField( + coerce=lambda x: x.lower() == 'true', choices=( (True, "Yes, I can name other employees."), (False, "No (We'll ask you to explain why).") @@ -594,6 +595,15 @@ class OtherContactsForm(RegistrarForm): error_messages={"required": "Enter a phone number for this contact."}, ) + def __init__(self, *args, **kwargs): + self.form_data_deleted = False + super().__init__(*args, **kwargs) + + def remove_form_data(self): + logger.info("removing form data from other contact") + self.data = {} + self.form_data_deleted = True + def clean(self): """ This method overrides the default behavior for forms. @@ -603,29 +613,35 @@ class OtherContactsForm(RegistrarForm): validation """ - # Set form_is_empty to True initially - form_is_empty = True - for name, field in self.fields.items(): - # get the value of the field from the widget - value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name)) - # if any field in the submitted form is not empty, set form_is_empty to False - if value is not None and value != "": - form_is_empty = False + if self.form_data_deleted: + # Set form_is_empty to True initially + form_is_empty = True + for name, field in self.fields.items(): + # get the value of the field from the widget + value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name)) + # if any field in the submitted form is not empty, set form_is_empty to False + if value is not None and value != "": + form_is_empty = False - if form_is_empty: - # clear any errors raised by the form fields - # (before this clean() method is run, each field - # performs its own clean, which could result in - # errors that we wish to ignore at this point) - # - # NOTE: we cannot just clear() the errors list. - # That causes problems. - for field in self.fields: - if field in self.errors: - del self.errors[field] + if form_is_empty: + # clear any errors raised by the form fields + # (before this clean() method is run, each field + # performs its own clean, which could result in + # errors that we wish to ignore at this point) + # + # NOTE: we cannot just clear() the errors list. + # That causes problems. + for field in self.fields: + if field in self.errors: + del self.errors[field] return self.cleaned_data + def is_valid(self): + val = super().is_valid() + logger.info(f"other contacts form is valid = {val}") + return val + class BaseOtherContactsFormSet(RegistrarFormSet): JOIN = "other_contacts" @@ -635,12 +651,18 @@ class BaseOtherContactsFormSet(RegistrarFormSet): return all(empty) def to_database(self, obj: DomainApplication): + logger.info("to_database called on BaseOtherContactsFormSet") self._to_database(obj, self.JOIN, self.should_delete, self.pre_update, self.pre_create) @classmethod def from_database(cls, obj): return super().from_database(obj, cls.JOIN, cls.on_fetch) + def remove_form_data(self): + logger.info("removing form data from other contact set") + for form in self.forms: + form.remove_form_data() + OtherContactsFormSet = forms.formset_factory( OtherContactsForm, @@ -667,6 +689,63 @@ class NoOtherContactsForm(RegistrarForm): ], ) + def __init__(self, *args, **kwargs): + self.form_data_marked_for_deletion = False + super().__init__(*args, **kwargs) + + def remove_form_data(self): + logger.info("removing form data from no other contacts") + # self.data = {"no_other_contacts_rationale": ""} + self.form_data_marked_for_deletion = True + + def clean(self): + """ + This method overrides the default behavior for forms. + This cleans the form after field validation has already taken place. + In this override, remove errors associated with the form if form data + is marked for deletion. + """ + + if self.form_data_marked_for_deletion: + # clear any errors raised by the form fields + # (before this clean() method is run, each field + # performs its own clean, which could result in + # errors that we wish to ignore at this point) + # + # NOTE: we cannot just clear() the errors list. + # That causes problems. + for field in self.fields: + if field in self.errors: + del self.errors[field] + + return self.cleaned_data + + def to_database(self, obj): + """ + This method overrides the behavior of RegistrarForm. + If form data is marked for deletion, set relevant fields + to None before saving. + Do nothing if form is not valid. + """ + logger.info(f"to_database called on {self.__class__.__name__}") + if not self.is_valid(): + return + if self.form_data_marked_for_deletion: + for field_name, _ in self.fields.items(): + logger.info(f"{field_name}: None") + setattr(obj, field_name, None) + else: + for name, value in self.cleaned_data.items(): + logger.info(f"{name}: {value}") + setattr(obj, name, value) + obj.save() + + def is_valid(self): + """This is for debugging only and can be deleted""" + val = super().is_valid() + logger.info(f"no other contacts form is valid = {val}") + return val + class AnythingElseForm(RegistrarForm): anything_else = forms.CharField( diff --git a/src/registrar/views/application.py b/src/registrar/views/application.py index 5aba9cf23..31948e034 100644 --- a/src/registrar/views/application.py +++ b/src/registrar/views/application.py @@ -487,21 +487,124 @@ class OtherContacts(ApplicationWizard): forms = [forms.OtherContactsYesNoForm, forms.OtherContactsFormSet, forms.NoOtherContactsForm] def post(self, request, *args, **kwargs) -> HttpResponse: - parent_form = forms.OtherContactsYesNoForm(request.POST) - other_contacts_formset = forms.OtherContactsFormSet(request.POST, request.FILES) - no_other_contacts_form = forms.NoOtherContactsForm(request.POST) + """This method handles POST requests.""" + # Log the keys and values of request.POST + for key, value in request.POST.items(): + logger.info("Key: %s, Value: %s", key, value) + # if accessing this class directly, redirect to the first step + if self.__class__ == ApplicationWizard: + return self.goto(self.steps.first) - logger.info("in post") - has_other_contacts_selected = parent_form.data.get('other_contacts-has_other_contacts') - logger.info(f"has other contacts = {has_other_contacts_selected}") - if parent_form.is_valid(): - if has_other_contacts_selected: + # which button did the user press? + button: str = request.POST.get("submit_button", "") + + forms = self.get_forms(use_post=True) + # forms is now set as follows: + # forms.0 is yes no form + # forms.1 - forms.length-1 are other contacts forms + # forms.length is no other contacts form + yes_no_form = forms[0] + other_contacts_forms = forms[1] + no_other_contacts_form = forms[2] + + all_forms_valid = True + # test first for yes_no_form validity + if yes_no_form.is_valid(): + logger.info("yes no form is valid") + # test for has_contacts + if yes_no_form.cleaned_data.get('has_other_contacts'): logger.info("has other contacts") - other_contacts_formset.data = {} + # remove data from no_other_contacts_form and set + # form to always_valid + no_other_contacts_form.remove_form_data() + # test that the other_contacts_forms and no_other_contacts_forms are valid + if not self.is_valid(forms[1:]): + all_forms_valid = False else: - logger.info("doesn't have other contacts") - no_other_contacts_form.data = {} - super().post(request, *args, **kwargs) + logger.info("has no other contacts") + # remove data from each other_contacts_form + other_contacts_forms.remove_form_data() + # test that the other_contacts_forms and no_other_contacts_forms are valid + if not self.is_valid(forms[1:]): + all_forms_valid = False + else: + all_forms_valid = False + + if all_forms_valid: + logger.info("all forms are valid") + # always save progress + self.save(forms) + else: + context = self.get_context_data() + context["forms"] = forms + return render(request, self.template_name, context) + + # if user opted to save their progress, + # return them to the page they were already on + if button == "save": + messages.success(request, "Your progress has been saved!") + return self.goto(self.steps.current) + # if user opted to save progress and return, + # return them to the home page + if button == "save_and_return": + return HttpResponseRedirect(reverse("home")) + # otherwise, proceed as normal + return self.goto_next_step() + + # def post(self, request, *args, **kwargs) -> HttpResponse: + # parent_form = forms.OtherContactsYesNoForm(request.POST, **kwargs) + # other_contacts_formset = forms.OtherContactsFormSet(request.POST, **kwargs) + # no_other_contacts_form = forms.NoOtherContactsForm(request.POST, **kwargs) + + # logger.info("in post") + # has_other_contacts_selected = parent_form.data.get('other_contacts-has_other_contacts') + # logger.info(f"has other contacts = {has_other_contacts_selected}") + # if has_other_contacts_selected: + # logger.info("has other contacts") + # other_contacts_formset.data = {} + # else: + # logger.info("doesn't have other contacts") + # no_other_contacts_form.data = {} + # return super().post(request, *args, **kwargs) + + # def is_valid(self, forms: list) -> bool: + # """Returns True if all forms in the wizard are valid.""" + # if forms[0].is_valid(): + # # test for has_contacts + # if forms[0].cleaned_data.get('has_other_contacts'): + # logger.info("testing validity on other contacts") + # validity_list = [] + + # # Iterate over the sublist of forms from index 2 to the second-to-last index + # for form in forms[1:-1]: + # # Check if the form is valid and append the result to the validity_list + # logger.info(f"testing validity of form of type {form.__class__.__name__}") + # validity_list.append(form.is_valid()) + + # # Check if all elements in validity_list are True + # return all(validity_list) + # # return all(form.is_valid() for form in forms[2:-2]) + # else: + # logger.info("testing validity on no other contacts") + # return forms[-1].is_valid() + # # if has contacts , return if next length-2 are valid + # # else return last form in list is valid + # else: + # return False + # # are_valid = (form.is_valid() for form in forms) + # # return all(are_valid) + + + # def save(self, forms: list): + # """ + # Unpack the form responses onto the model object properties. + + # Saves the application to the database. + # """ + # logger.info("in save") + # for form in forms: + # if form is not None and hasattr(form, "to_database"): + # form.to_database(self.application) class NoOtherContacts(ApplicationWizard):