Merge pull request #1613 from cisagov/dk/903-delete-other-contacts

Issue 903: Delete other contacts (DK sandbox)
This commit is contained in:
rachidatecs 2024-01-16 18:10:25 -05:00 committed by GitHub
commit 66cf92e0dd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 503 additions and 151 deletions

View file

@ -177,7 +177,7 @@ class RegistrarFormSet(forms.BaseFormSet):
# no matching database object, create it
# make sure not to create a database object if cleaned has 'delete' attribute
elif db_obj is None and cleaned and not cleaned.get("delete", False):
elif db_obj is None and cleaned and not cleaned.get("DELETE", False):
kwargs = pre_create(db_obj, cleaned)
getattr(obj, join).create(**kwargs)
@ -609,9 +609,12 @@ class OtherContactsYesNoForm(RegistrarForm):
self.fields["has_other_contacts"] = forms.TypedChoiceField(
coerce=lambda x: x.lower() == "true" if x is not None else None, # coerce strings to bool, excepting None
choices=((True, "Yes, I can name other employees."), (False, "No (Well ask you to explain why).")),
choices=((True, "Yes, I can name other employees."), (False, "No. (Well ask you to explain why.)")),
initial=initial_value,
widget=forms.RadioSelect,
error_messages={
"required": "This question is required.",
},
)
@ -638,7 +641,10 @@ class OtherContactsForm(RegistrarForm):
)
email = forms.EmailField(
label="Email",
error_messages={"invalid": ("Enter an email address in the required format, like name@example.com.")},
error_messages={
"required": ("Enter an email address in the required format, like name@example.com."),
"invalid": ("Enter an email address in the required format, like name@example.com."),
},
)
phone = PhoneNumberField(
label="Phone",
@ -649,8 +655,17 @@ class OtherContactsForm(RegistrarForm):
)
def __init__(self, *args, **kwargs):
"""
Override the __init__ method for RegistrarForm.
Set form_data_marked_for_deletion to false.
Empty_permitted set to False, as this is overridden in certain circumstances by
Django's BaseFormSet, and results in empty forms being allowed and field level
errors not appropriately raised. This works with code in the view which appropriately
displays required attributes on fields.
"""
self.form_data_marked_for_deletion = False
super().__init__(*args, **kwargs)
self.empty_permitted = False
def mark_form_for_deletion(self):
self.form_data_marked_for_deletion = True
@ -659,12 +674,11 @@ class OtherContactsForm(RegistrarForm):
"""
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
In this override, allow for a form which is deleted by user or marked for
deletion by formset to be considered valid even though certain required fields have
not passed field validation
"""
if self.form_data_marked_for_deletion:
if self.form_data_marked_for_deletion or self.cleaned_data.get("DELETE"):
# clear any errors raised by the form fields
# (before this clean() method is run, each field
# performs its own clean, which could result in
@ -678,12 +692,26 @@ class OtherContactsForm(RegistrarForm):
# return empty object with only 'delete' attribute defined.
# this will prevent _to_database from creating an empty
# database object
return {"delete": True}
return {"DELETE": True}
return self.cleaned_data
class BaseOtherContactsFormSet(RegistrarFormSet):
"""
FormSet for Other Contacts
There are two conditions by which a form in the formset can be marked for deletion.
One is if the user clicks 'DELETE' button, and this is submitted in the form. The
other is if the YesNo form, which is submitted with this formset, is set to No; in
this case, all forms in formset are marked for deletion. Both of these conditions
must co-exist.
Also, other_contacts have db relationships to multiple db objects. When attempting
to delete an other_contact from an application, those db relationships must be
tested and handled; this is configured with REVERSE_JOINS, which is an array of
strings representing the relationships between contact model and other models.
"""
JOIN = "other_contacts"
REVERSE_JOINS = [
"user",
@ -695,7 +723,13 @@ class BaseOtherContactsFormSet(RegistrarFormSet):
"contact_applications_information",
]
def get_deletion_widget(self):
return forms.HiddenInput(attrs={"class": "deletion"})
def __init__(self, *args, **kwargs):
"""
Override __init__ for RegistrarFormSet.
"""
self.formset_data_marked_for_deletion = False
self.application = kwargs.pop("application", None)
super(RegistrarFormSet, self).__init__(*args, **kwargs)
@ -706,8 +740,17 @@ class BaseOtherContactsFormSet(RegistrarFormSet):
self.forms[index].use_required_attribute = True
def should_delete(self, cleaned):
empty = (isinstance(v, str) and (v.strip() == "" or v is None) for v in cleaned.values())
return all(empty) or self.formset_data_marked_for_deletion
"""
Implements should_delete method from BaseFormSet.
"""
return self.formset_data_marked_for_deletion or cleaned.get("DELETE", False)
def pre_create(self, db_obj, cleaned):
"""Code to run before an item in the formset is created in the database."""
# remove DELETE from cleaned
if "DELETE" in cleaned:
cleaned.pop("DELETE")
return cleaned
def to_database(self, obj: DomainApplication):
self._to_database(obj, self.JOIN, self.REVERSE_JOINS, self.should_delete, self.pre_update, self.pre_create)
@ -736,9 +779,10 @@ class BaseOtherContactsFormSet(RegistrarFormSet):
OtherContactsFormSet = forms.formset_factory(
OtherContactsForm,
extra=1,
extra=0,
absolute_max=1500, # django default; use `max_num` to limit entries
min_num=1,
can_delete=True,
validate_min=True,
formset=BaseOtherContactsFormSet,
)
@ -748,11 +792,7 @@ class NoOtherContactsForm(RegistrarForm):
no_other_contacts_rationale = forms.CharField(
required=True,
# label has to end in a space to get the label_suffix to show
label=(
"You dont need to provide names of other employees now, but it may "
"slow down our assessment of your eligibility. Describe why there are "
"no other employees who can help verify your request."
),
label=("No other employees rationale"),
widget=forms.Textarea(),
validators=[
MaxLengthValidator(