Repository: cisagov/manage.get.gov

Issue:
Bug: Applicant cannot advance past the other employees from your org question under certain circumstances
On the "other employees" page of the domain application once a user enters data into the fields for contact and hits save. They cannot then erase all the data from the fields and advance. **Since this question is optional**, a user should be able to erase the data from the contact fields and save, effectively "removing" that contact. 

### Steps to reproduce
1. start a new domain request 
2. on the other employees from your org page, fill in all details for other contact 1. 
3. Hit save and continue
4. return to the other employees from your org page and erase all the data
5. Hit save and continue (this is where errors get thrown).

### Acceptance Criteria

- [ ] Once the data is erased, the user should be able to hit save and continue and then see the No other employees from your org page so they can explain why they are not providing a contact.
- [ ] No errors should show if they erase all the data and hit save and continue.


### Issue Links
🔗  to: #903 (this is a separate ticket for future work where a remove/delete contact button will be designed and implemented)

### Implementation note
On brief inspection, it looks like other forms use a "clean" function which will be triggered before is_valid. The OtherContactsForm may just need this clean function to set the values to be null as opposed to empty strings. There also should be a unit test to make sure if a user manually removes everything (effectively deleting the value) that the contact associated gets removed from the domain as well. 


Discussion:
I will make a new ticket for designing a button to remove extra contacts

Resolving patch:
diff --git a/src/registrar/forms/application_wizard.py b/src/registrar/forms/application_wizard.py
--- a/src/registrar/forms/application_wizard.py
+++ b/src/registrar/forms/application_wizard.py
@@ -585,12 +585,44 @@ class OtherContactsForm(RegistrarForm):
         error_messages={"required": "Enter a phone number for this contact."},
     )
 
+    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, allow for a form which is empty to be considered
+        valid even though certain required fields have not passed field
+        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 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
+
 
 class BaseOtherContactsFormSet(RegistrarFormSet):
     JOIN = "other_contacts"
 
     def should_delete(self, cleaned):
-        empty = (isinstance(v, str) and not v.strip() for v in cleaned.values())
+        empty = (isinstance(v, str) and (v.strip() == "" or v is None) for v in cleaned.values())
         return all(empty)
 
     def to_database(self, obj: DomainApplication):


Test patch:
diff --git a/src/registrar/tests/test_forms.py b/src/registrar/tests/test_forms.py
--- a/src/registrar/tests/test_forms.py
+++ b/src/registrar/tests/test_forms.py
@@ -216,7 +216,7 @@ def test_your_contact_phone_invalid(self):
 
     def test_other_contact_email_invalid(self):
         """must be a valid email address."""
-        form = OtherContactsForm(data={"email": "boss@boss"})
+        form = OtherContactsForm(data={"email": "splendid@boss"})
         self.assertEqual(
             form.errors["email"],
             ["Enter an email address in the required format, like name@example.com."],
@@ -224,7 +224,7 @@ def test_other_contact_email_invalid(self):
 
     def test_other_contact_phone_invalid(self):
         """Must be a valid phone number."""
-        form = OtherContactsForm(data={"phone": "boss@boss"})
+        form = OtherContactsForm(data={"phone": "super@boss"})
         self.assertTrue(form.errors["phone"][0].startswith("Enter a valid phone number "))
 
     def test_requirements_form_blank(self):
diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py
--- a/src/registrar/tests/test_views.py
+++ b/src/registrar/tests/test_views.py
@@ -729,6 +729,92 @@ def test_application_no_other_contacts(self):
         actual_url_slug = no_contacts_page.request.path.split("/")[-2]
         self.assertEqual(expected_url_slug, actual_url_slug)
 
+    def test_application_delete_other_contact(self):
+        """Other contacts can be deleted after being saved to database."""
+        # Populate the databse with a domain application that
+        # has 1 "other contact" assigned to it
+        ao, _ = Contact.objects.get_or_create(
+            first_name="Testy",
+            last_name="Tester",
+            title="Chief Tester",
+            email="testy@town.com",
+            phone="(555) 555 5555",
+        )
+        you, _ = Contact.objects.get_or_create(
+            first_name="Testy you",
+            last_name="Tester you",
+            title="Admin Tester",
+            email="testy-admin@town.com",
+            phone="(555) 555 5556",
+        )
+        other, _ = Contact.objects.get_or_create(
+            first_name="Testy2",
+            last_name="Tester2",
+            title="Another Tester",
+            email="testy2@town.com",
+            phone="(555) 555 5557",
+        )
+        application, _ = DomainApplication.objects.get_or_create(
+            organization_type="federal",
+            federal_type="executive",
+            purpose="Purpose of the site",
+            anything_else="No",
+            is_policy_acknowledged=True,
+            organization_name="Testorg",
+            address_line1="address 1",
+            state_territory="NY",
+            zipcode="10002",
+            authorizing_official=ao,
+            submitter=you,
+            creator=self.user,
+            status="started",
+        )
+        application.other_contacts.add(other)
+
+        # prime the form by visiting /edit
+        self.app.get(reverse("edit-application", kwargs={"id": application.pk}))
+        # django-webtest does not handle cookie-based sessions well because it keeps
+        # resetting the session key on each new request, thus destroying the concept
+        # of a "session". We are going to do it manually, saving the session ID here
+        # and then setting the cookie on each request.
+        session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
+        self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
+
+        other_contacts_page = self.app.get(reverse("application:other_contacts"))
+        self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
+
+        other_contacts_form = other_contacts_page.forms[0]
+
+        # Minimal check to ensure the form is loaded with data (if this part of
+        # the application doesn't work, we should be equipped with other unit
+        # tests to flag it)
+        self.assertEqual(other_contacts_form["other_contacts-0-first_name"].value, "Testy2")
+
+        # clear the form
+        other_contacts_form["other_contacts-0-first_name"] = ""
+        other_contacts_form["other_contacts-0-middle_name"] = ""
+        other_contacts_form["other_contacts-0-last_name"] = ""
+        other_contacts_form["other_contacts-0-title"] = ""
+        other_contacts_form["other_contacts-0-email"] = ""
+        other_contacts_form["other_contacts-0-phone"] = ""
+
+        # Submit the now empty form
+        result = other_contacts_form.submit()
+        self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
+
+        # Verify that the contact we saved earlier has been removed from the database
+        application = DomainApplication.objects.get()  # There are no contacts anymore
+        self.assertEqual(
+            application.other_contacts.count(),
+            0,
+        )
+
+        # Verify that on submit, user is advanced to "no contacts" page
+        no_contacts_page = result.follow()
+        expected_url_slug = str(Step.NO_OTHER_CONTACTS)
+        actual_url_slug = no_contacts_page.request.path.split("/")[-2]
+        self.assertEqual(expected_url_slug, actual_url_slug)
+
     def test_application_about_your_organiztion_interstate(self):
         """Special districts have to answer an additional question."""
         type_page = self.app.get(reverse("application:")).follow()