From 196ab6323b7c8a3d63821b35a971b567c18cde43 Mon Sep 17 00:00:00 2001
From: zandercymatics <141044360+zandercymatics@users.noreply.github.com>
Date: Thu, 14 Mar 2024 15:06:15 -0600
Subject: [PATCH 01/96] Add copy button
---
src/registrar/admin.py | 19 ++++++-
src/registrar/assets/sass/_theme/_admin.scss | 19 +++++++
src/registrar/templates/admin/fieldset.html | 49 +++++++++++++++++++
.../admin/includes/contact_fieldset.html | 48 ++++++++++++++++++
.../admin/public_contact_change_form.html | 8 +++
5 files changed, 142 insertions(+), 1 deletion(-)
create mode 100644 src/registrar/templates/admin/fieldset.html
create mode 100644 src/registrar/templates/django/admin/includes/contact_fieldset.html
create mode 100644 src/registrar/templates/django/admin/public_contact_change_form.html
diff --git a/src/registrar/admin.py b/src/registrar/admin.py
index ff8046da9..01ad5e960 100644
--- a/src/registrar/admin.py
+++ b/src/registrar/admin.py
@@ -640,6 +640,17 @@ class ContactAdmin(ListHeaderAdmin):
# in autocomplete_fields for user
ordering = ["first_name", "last_name", "email"]
+ fieldsets = [
+ (
+ None,
+ {
+ "fields": ["user", "first_name", "middle_name", "last_name", "title", "email", "phone"]
+ },
+ )
+ ]
+
+ change_form_template = "django/admin/public_contact_change_form.html"
+
# We name the custom prop 'contact' because linter
# is not allowing a short_description attr on it
# This gets around the linter limitation, for now.
@@ -1805,6 +1816,12 @@ class DraftDomainAdmin(ListHeaderAdmin):
ordering = ["name"]
+class PublicContactAdmin(ListHeaderAdmin):
+ """Custom PublicContact admin class."""
+
+ change_form_template = "django/admin/public_contact_change_form.html"
+
+
class VerifiedByStaffAdmin(ListHeaderAdmin):
list_display = ("email", "requestor", "truncated_notes", "created_at")
search_fields = ["email"]
@@ -1845,7 +1862,7 @@ admin.site.register(models.DraftDomain, DraftDomainAdmin)
# do not propagate to registry and logic not applied
admin.site.register(models.Host, MyHostAdmin)
admin.site.register(models.Website, WebsiteAdmin)
-admin.site.register(models.PublicContact, AuditedAdmin)
+admin.site.register(models.PublicContact, PublicContactAdmin)
admin.site.register(models.DomainRequest, DomainRequestAdmin)
admin.site.register(models.TransitionDomain, TransitionDomainAdmin)
admin.site.register(models.VerifiedByStaff, VerifiedByStaffAdmin)
diff --git a/src/registrar/assets/sass/_theme/_admin.scss b/src/registrar/assets/sass/_theme/_admin.scss
index dc67bc8b6..6909f77c2 100644
--- a/src/registrar/assets/sass/_theme/_admin.scss
+++ b/src/registrar/assets/sass/_theme/_admin.scss
@@ -299,3 +299,22 @@ input.admin-confirm-button {
display: contents !important;
}
}
+
+.admin-icon-group {
+ position: relative;
+ display: flex;
+ align-items: center;
+
+ .usa-button__icon {
+ position: absolute;
+ right: 0;
+ top: 0;
+ height: 100%;
+ border: none;
+ background: transparent;
+ padding: 0 1rem;
+ margin: 0;
+ }
+}
+
+
diff --git a/src/registrar/templates/admin/fieldset.html b/src/registrar/templates/admin/fieldset.html
new file mode 100644
index 000000000..dadae5811
--- /dev/null
+++ b/src/registrar/templates/admin/fieldset.html
@@ -0,0 +1,49 @@
+{% load i18n static %}
+
+{% comment %}
+This is copied from Djangos implementation of this template, with added "blocks"
+It is not inherently customizable on its own, so we can modify this instead.
+https://github.com/django/django/blob/main/django/contrib/admin/templates/admin/includes/fieldset.html
+{% endcomment %}
+
\ No newline at end of file
diff --git a/src/registrar/templates/django/admin/includes/contact_fieldset.html b/src/registrar/templates/django/admin/includes/contact_fieldset.html
new file mode 100644
index 000000000..c2a10f4fd
--- /dev/null
+++ b/src/registrar/templates/django/admin/includes/contact_fieldset.html
@@ -0,0 +1,48 @@
+{% extends "admin/fieldset.html" %}
+{% load i18n static %}
+
+{% comment %}
+This is using a custom implementation fieldset.html (see admin/fieldset.html)
+{% endcomment %}
+{% block fieldset_lines %}
+{% for line in fieldset %}
+
+ {% endfor %}
+ {% if not line.fields|length == 1 %}
{% endif %}
+
+{% endfor %}
+
+{% endblock fieldset_lines %}
diff --git a/src/registrar/templates/django/admin/public_contact_change_form.html b/src/registrar/templates/django/admin/public_contact_change_form.html
new file mode 100644
index 000000000..181201d0b
--- /dev/null
+++ b/src/registrar/templates/django/admin/public_contact_change_form.html
@@ -0,0 +1,8 @@
+{% extends 'admin/change_form.html' %}
+{% load i18n static %}
+
+{% block field_sets %}
+ {% for fieldset in adminform %}
+ {% include "django/admin/includes/contact_fieldset.html" %}
+ {% endfor %}
+{% endblock %}
From 44c4e2ef26ca07f8e576ab6bca7ff5783e8431cb Mon Sep 17 00:00:00 2001
From: zandercymatics <141044360+zandercymatics@users.noreply.github.com>
Date: Thu, 14 Mar 2024 15:46:29 -0600
Subject: [PATCH 02/96] Add checkbox
---
src/registrar/assets/js/get-gov-admin.js | 42 +++++++++++++++++++
.../admin/includes/contact_fieldset.html | 2 +-
2 files changed, 43 insertions(+), 1 deletion(-)
diff --git a/src/registrar/assets/js/get-gov-admin.js b/src/registrar/assets/js/get-gov-admin.js
index ff73acb65..df748c840 100644
--- a/src/registrar/assets/js/get-gov-admin.js
+++ b/src/registrar/assets/js/get-gov-admin.js
@@ -76,6 +76,48 @@ function openInNewTab(el, removeAttribute = false){
prepareDjangoAdmin();
})();
+/** An IIFE for pages in DjangoAdmin that use a clipboard button
+*/
+(function (){
+ function copyToClipboardAndChangeIcon(button) {
+ // Assuming the input is the previous sibling of the button
+ let input = button.previousElementSibling;
+
+ // Copy input value to clipboard
+ if (input) {
+ navigator.clipboard.writeText(input.value).then(function() {
+ // Change the icon to a checkmark on successful copy
+ let buttonIcon = button .querySelector('.usa-button__clipboard use');
+ if (buttonIcon) {
+ let currentHref = buttonIcon.getAttribute('xlink:href');
+ let baseHref = currentHref.split('#')[0];
+
+ // Append the new icon reference
+ buttonIcon.setAttribute('xlink:href', baseHref + '#check');
+ setTimeout(function() {
+ // Change back to the copy icon
+ buttonIcon.setAttribute('xlink:href', currentHref);
+ }, 2000);
+ }
+
+ }).catch(function(error) {
+ console.error('Clipboard copy failed', error);
+ });
+ }
+ }
+
+ function handleClipboardButtons() {
+ clipboardButtons = document.querySelectorAll(".usa-button__clipboard")
+ clipboardButtons.forEach((button) => {
+ button.addEventListener("click", ()=>{copyToClipboardAndChangeIcon(button)});
+ });
+ }
+
+ handleClipboardButtons();
+
+})();
+
+
/**
* An IIFE to listen to changes on filter_horizontal and enable or disable the change/delete/view buttons as applicable
*
diff --git a/src/registrar/templates/django/admin/includes/contact_fieldset.html b/src/registrar/templates/django/admin/includes/contact_fieldset.html
index c2a10f4fd..90e8f911f 100644
--- a/src/registrar/templates/django/admin/includes/contact_fieldset.html
+++ b/src/registrar/templates/django/admin/includes/contact_fieldset.html
@@ -21,7 +21,7 @@ This is using a custom implementation fieldset.html (see admin/fieldset.html)
{% elif field.field.name == "email" %}
{{ field.field }}
-
{% endif %}
-
-{% if widget.attrs.maxlength and show_max_length %}
-
- You can enter up to {{ widget.attrs.maxlength }} characters
-
-
-
-{% endif %}
From d8777c5dae698d808acab0ca7596f4dd70933020 Mon Sep 17 00:00:00 2001
From: zandercymatics <141044360+zandercymatics@users.noreply.github.com>
Date: Wed, 3 Apr 2024 15:44:57 -0600
Subject: [PATCH 51/96] Fix javascript errors
---
.../templates/includes/input_with_errors.html | 22 +++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/src/registrar/templates/includes/input_with_errors.html b/src/registrar/templates/includes/input_with_errors.html
index b6c24ba3e..e189f8d85 100644
--- a/src/registrar/templates/includes/input_with_errors.html
+++ b/src/registrar/templates/includes/input_with_errors.html
@@ -75,3 +75,25 @@ error messages, if necessary.
{% else %}
{% endif %}
+
+{% if widget.attrs.maxlength %}
+
+ You can enter up to {{ widget.attrs.maxlength }} characters
+
+
+
+{% endif %}
From d0d312598da04b347ef45af6d240b7d304078be6 Mon Sep 17 00:00:00 2001
From: Erin <121973038+erinysong@users.noreply.github.com>
Date: Wed, 3 Apr 2024 14:49:38 -0700
Subject: [PATCH 52/96] Edit typo on create_groups migration
---
src/registrar/migrations/0037_create_groups_v01.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/registrar/migrations/0037_create_groups_v01.py b/src/registrar/migrations/0037_create_groups_v01.py
index 3540ea2f3..0c04a8b61 100644
--- a/src/registrar/migrations/0037_create_groups_v01.py
+++ b/src/registrar/migrations/0037_create_groups_v01.py
@@ -1,5 +1,5 @@
# This migration creates the create_full_access_group and create_cisa_analyst_group groups
-# It is dependent on 0035 (which populates ContentType and Permissions)
+# It is dependent on 0036 (which populates ContentType and Permissions)
# If permissions on the groups need changing, edit CISA_ANALYST_GROUP_PERMISSIONS
# in the user_group model then:
# [NOT RECOMMENDED]
From c25a082aa091caa325cbec2ba326daaa8e59ca77 Mon Sep 17 00:00:00 2001
From: Erin <121973038+erinysong@users.noreply.github.com>
Date: Wed, 3 Apr 2024 15:18:50 -0700
Subject: [PATCH 53/96] Add instructions for user group migrations
---
src/registrar/models/user_group.py | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/src/registrar/models/user_group.py b/src/registrar/models/user_group.py
index 2aa2f642e..6211094ec 100644
--- a/src/registrar/models/user_group.py
+++ b/src/registrar/models/user_group.py
@@ -5,6 +5,16 @@ logger = logging.getLogger(__name__)
class UserGroup(Group):
+ """
+ UserGroup sets read and write permissions for superusers (who have full access)
+ and analysts. To update analyst permissions do the following:
+ 1. Make desired changes to analyst group permissions in user_group.py.
+ 2. Follow the steps in 0037_create_groups_v01.py to create a duplicate
+ migration for the updated user group permissions.
+ 3. To migrate locally, run docker-compose up. To migrate on a sandbox,
+ push the new migration onto your sandbox before migrating.
+ """
+
class Meta:
verbose_name = "User group"
verbose_name_plural = "User groups"
@@ -49,7 +59,7 @@ class UserGroup(Group):
{
"app_label": "registrar",
"model": "user",
- "permissions": ["analyst_access_permission", "change_user"],
+ "permissions": ["analyst_access_permission", "change_user", "delete_user"],
},
{
"app_label": "registrar",
From c4cf7d5669e156fb755d2034c8e42c3af49f727b Mon Sep 17 00:00:00 2001
From: Erin <121973038+erinysong@users.noreply.github.com>
Date: Wed, 3 Apr 2024 15:27:49 -0700
Subject: [PATCH 54/96] Update dependency typos in user group migrations
---
src/registrar/migrations/0038_create_groups_v02.py | 2 +-
src/registrar/migrations/0042_create_groups_v03.py | 2 +-
src/registrar/migrations/0044_create_groups_v04.py | 2 +-
src/registrar/migrations/0053_create_groups_v05.py | 2 +-
src/registrar/migrations/0065_create_groups_v06.py | 2 +-
src/registrar/migrations/0067_create_groups_v07.py | 2 +-
src/registrar/migrations/0075_create_groups_v08.py | 2 +-
7 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/src/registrar/migrations/0038_create_groups_v02.py b/src/registrar/migrations/0038_create_groups_v02.py
index fc61db3c0..70d13b61a 100644
--- a/src/registrar/migrations/0038_create_groups_v02.py
+++ b/src/registrar/migrations/0038_create_groups_v02.py
@@ -1,5 +1,5 @@
# This migration creates the create_full_access_group and create_cisa_analyst_group groups
-# It is dependent on 0035 (which populates ContentType and Permissions)
+# It is dependent on 0037 (which also updates user role permissions)
# If permissions on the groups need changing, edit CISA_ANALYST_GROUP_PERMISSIONS
# in the user_group model then:
# [NOT RECOMMENDED]
diff --git a/src/registrar/migrations/0042_create_groups_v03.py b/src/registrar/migrations/0042_create_groups_v03.py
index 01b7985bf..e30841599 100644
--- a/src/registrar/migrations/0042_create_groups_v03.py
+++ b/src/registrar/migrations/0042_create_groups_v03.py
@@ -1,5 +1,5 @@
# This migration creates the create_full_access_group and create_cisa_analyst_group groups
-# It is dependent on 0035 (which populates ContentType and Permissions)
+# It is dependent on 0041 (which changes fields in domain request and domain information)
# If permissions on the groups need changing, edit CISA_ANALYST_GROUP_PERMISSIONS
# in the user_group model then:
# [NOT RECOMMENDED]
diff --git a/src/registrar/migrations/0044_create_groups_v04.py b/src/registrar/migrations/0044_create_groups_v04.py
index ecb48e335..63cad49bb 100644
--- a/src/registrar/migrations/0044_create_groups_v04.py
+++ b/src/registrar/migrations/0044_create_groups_v04.py
@@ -1,5 +1,5 @@
# This migration creates the create_full_access_group and create_cisa_analyst_group groups
-# It is dependent on 0035 (which populates ContentType and Permissions)
+# It is dependent on 0043 (which adds an expiry date field to a domain.)
# If permissions on the groups need changing, edit CISA_ANALYST_GROUP_PERMISSIONS
# in the user_group model then:
# [NOT RECOMMENDED]
diff --git a/src/registrar/migrations/0053_create_groups_v05.py b/src/registrar/migrations/0053_create_groups_v05.py
index aaf74a9db..91e8389df 100644
--- a/src/registrar/migrations/0053_create_groups_v05.py
+++ b/src/registrar/migrations/0053_create_groups_v05.py
@@ -1,5 +1,5 @@
# This migration creates the create_full_access_group and create_cisa_analyst_group groups
-# It is dependent on 0035 (which populates ContentType and Permissions)
+# It is dependent on 0052 (which alters fields in a domain request)
# If permissions on the groups need changing, edit CISA_ANALYST_GROUP_PERMISSIONS
# in the user_group model then:
# [NOT RECOMMENDED]
diff --git a/src/registrar/migrations/0065_create_groups_v06.py b/src/registrar/migrations/0065_create_groups_v06.py
index d2cb32cee..965dc06a8 100644
--- a/src/registrar/migrations/0065_create_groups_v06.py
+++ b/src/registrar/migrations/0065_create_groups_v06.py
@@ -1,5 +1,5 @@
# This migration creates the create_full_access_group and create_cisa_analyst_group groups
-# It is dependent on 0035 (which populates ContentType and Permissions)
+# It is dependent on 0065 (which renames fields in domain application)
# If permissions on the groups need changing, edit CISA_ANALYST_GROUP_PERMISSIONS
# in the user_group model then:
# [NOT RECOMMENDED]
diff --git a/src/registrar/migrations/0067_create_groups_v07.py b/src/registrar/migrations/0067_create_groups_v07.py
index 85138d4af..809738ba3 100644
--- a/src/registrar/migrations/0067_create_groups_v07.py
+++ b/src/registrar/migrations/0067_create_groups_v07.py
@@ -1,5 +1,5 @@
# This migration creates the create_full_access_group and create_cisa_analyst_group groups
-# It is dependent on 0035 (which populates ContentType and Permissions)
+# It is dependent on 0066 (which updates users with permission as Verified by Staff)
# If permissions on the groups need changing, edit CISA_ANALYST_GROUP_PERMISSIONS
# in the user_group model then:
# [NOT RECOMMENDED]
diff --git a/src/registrar/migrations/0075_create_groups_v08.py b/src/registrar/migrations/0075_create_groups_v08.py
index b0b2ed740..a4df52d21 100644
--- a/src/registrar/migrations/0075_create_groups_v08.py
+++ b/src/registrar/migrations/0075_create_groups_v08.py
@@ -1,5 +1,5 @@
# This migration creates the create_full_access_group and create_cisa_analyst_group groups
-# It is dependent on 0035 (which populates ContentType and Permissions)
+# It is dependent on 0074 (which renames Domain Application and its fields)
# If permissions on the groups need changing, edit CISA_ANALYST_GROUP_PERMISSIONS
# in the user_group model then:
# [NOT RECOMMENDED]
From 8e5c1aadbf841559ea6ae17f11399139a07fea72 Mon Sep 17 00:00:00 2001
From: Erin <121973038+erinysong@users.noreply.github.com>
Date: Wed, 3 Apr 2024 15:29:16 -0700
Subject: [PATCH 55/96] Revert user group permission changes from testing
---
src/registrar/models/user_group.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/registrar/models/user_group.py b/src/registrar/models/user_group.py
index 6211094ec..3071fba11 100644
--- a/src/registrar/models/user_group.py
+++ b/src/registrar/models/user_group.py
@@ -59,7 +59,7 @@ class UserGroup(Group):
{
"app_label": "registrar",
"model": "user",
- "permissions": ["analyst_access_permission", "change_user", "delete_user"],
+ "permissions": ["analyst_access_permission", "change_user"],
},
{
"app_label": "registrar",
From df3e628e613ed885fb382c0bac176b1c646b2d62 Mon Sep 17 00:00:00 2001
From: zandercymatics <141044360+zandercymatics@users.noreply.github.com>
Date: Thu, 4 Apr 2024 08:33:18 -0600
Subject: [PATCH 56/96] Remove max length
---
src/registrar/forms/domain.py | 4 ----
src/registrar/forms/domain_request_wizard.py | 6 ------
2 files changed, 10 deletions(-)
diff --git a/src/registrar/forms/domain.py b/src/registrar/forms/domain.py
index 1a6a8778e..0d135c58b 100644
--- a/src/registrar/forms/domain.py
+++ b/src/registrar/forms/domain.py
@@ -33,9 +33,7 @@ class DomainAddUserForm(forms.Form):
email = forms.EmailField(
label="Email",
- max_length=254,
error_messages={"invalid": ("Enter your email address in the required format, like name@example.com.")},
- # This validator should exist in the event that a preexisting field is of invalid length
validators=[
MaxLengthValidator(
254,
@@ -302,12 +300,10 @@ class DomainSecurityEmailForm(forms.Form):
security_email = forms.EmailField(
label="Security email (optional)",
- max_length=254,
required=False,
error_messages={
"invalid": str(SecurityEmailError(code=SecurityEmailErrorCodes.BAD_DATA)),
},
- # This validator should exist in the event that a preexisting field is of invalid length
validators=[
MaxLengthValidator(
254,
diff --git a/src/registrar/forms/domain_request_wizard.py b/src/registrar/forms/domain_request_wizard.py
index 8ef0f6e33..9ac17b145 100644
--- a/src/registrar/forms/domain_request_wizard.py
+++ b/src/registrar/forms/domain_request_wizard.py
@@ -369,9 +369,7 @@ class AuthorizingOfficialForm(RegistrarForm):
)
email = forms.EmailField(
label="Email",
- max_length=254,
error_messages={"invalid": ("Enter an email address in the required format, like name@example.com.")},
- # This validator should exist in the event that a preexisting field is of invalid length
validators=[
MaxLengthValidator(
254,
@@ -574,9 +572,7 @@ class YourContactForm(RegistrarForm):
)
email = forms.EmailField(
label="Email",
- max_length=254,
error_messages={"invalid": ("Enter your email address in the required format, like name@example.com.")},
- # This validator should exist in the event that a preexisting field is of invalid length
validators=[
MaxLengthValidator(
254,
@@ -637,12 +633,10 @@ class OtherContactsForm(RegistrarForm):
)
email = forms.EmailField(
label="Email",
- max_length=254,
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."),
},
- # This validator should exist in the event that a preexisting field is of invalid length
validators=[
MaxLengthValidator(
254,
From 1e4be56e8ebb62d6bb1fa5b363b11ef28577de5e Mon Sep 17 00:00:00 2001
From: zandercymatics <141044360+zandercymatics@users.noreply.github.com>
Date: Thu, 4 Apr 2024 08:40:40 -0600
Subject: [PATCH 57/96] 254 -> 320 characters
---
src/registrar/forms/domain.py | 8 ++++----
src/registrar/forms/domain_request_wizard.py | 12 ++++++------
2 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/src/registrar/forms/domain.py b/src/registrar/forms/domain.py
index 0d135c58b..12417c0d2 100644
--- a/src/registrar/forms/domain.py
+++ b/src/registrar/forms/domain.py
@@ -36,8 +36,8 @@ class DomainAddUserForm(forms.Form):
error_messages={"invalid": ("Enter your email address in the required format, like name@example.com.")},
validators=[
MaxLengthValidator(
- 254,
- message="Response must be less than 254 characters.",
+ 320,
+ message="Response must be less than 320 characters.",
)
],
)
@@ -306,8 +306,8 @@ class DomainSecurityEmailForm(forms.Form):
},
validators=[
MaxLengthValidator(
- 254,
- message="Response must be less than 254 characters.",
+ 320,
+ message="Response must be less than 320 characters.",
)
],
)
diff --git a/src/registrar/forms/domain_request_wizard.py b/src/registrar/forms/domain_request_wizard.py
index 9ac17b145..1e8034fe2 100644
--- a/src/registrar/forms/domain_request_wizard.py
+++ b/src/registrar/forms/domain_request_wizard.py
@@ -372,8 +372,8 @@ class AuthorizingOfficialForm(RegistrarForm):
error_messages={"invalid": ("Enter an email address in the required format, like name@example.com.")},
validators=[
MaxLengthValidator(
- 254,
- message="Response must be less than 254 characters.",
+ 320,
+ message="Response must be less than 320 characters.",
)
],
)
@@ -575,8 +575,8 @@ class YourContactForm(RegistrarForm):
error_messages={"invalid": ("Enter your email address in the required format, like name@example.com.")},
validators=[
MaxLengthValidator(
- 254,
- message="Response must be less than 254 characters.",
+ 320,
+ message="Response must be less than 320 characters.",
)
],
)
@@ -639,8 +639,8 @@ class OtherContactsForm(RegistrarForm):
},
validators=[
MaxLengthValidator(
- 254,
- message="Response must be less than 254 characters.",
+ 320,
+ message="Response must be less than 320 characters.",
)
],
)
From 34232f9b6c8199e5d5136a19972ac1794ddad988 Mon Sep 17 00:00:00 2001
From: zandercymatics <141044360+zandercymatics@users.noreply.github.com>
Date: Thu, 4 Apr 2024 10:48:33 -0600
Subject: [PATCH 58/96] Change stlying
---
src/registrar/assets/js/get-gov-admin.js | 1 +
src/registrar/assets/sass/_theme/_admin.scss | 31 ++++++++++++++
.../assets/sass/_theme/_tooltips.scss | 6 +--
.../templates/admin/input_with_clipboard.html | 6 ++-
.../admin/includes/contact_detail_list.html | 41 ++++++++++++++-----
.../admin/includes/detail_table_fieldset.html | 27 +++++++-----
src/registrar/templates/domain_users.html | 2 +-
src/registrar/templates/home.html | 2 +-
8 files changed, 89 insertions(+), 27 deletions(-)
diff --git a/src/registrar/assets/js/get-gov-admin.js b/src/registrar/assets/js/get-gov-admin.js
index 4ae0af4a5..581c2b899 100644
--- a/src/registrar/assets/js/get-gov-admin.js
+++ b/src/registrar/assets/js/get-gov-admin.js
@@ -155,6 +155,7 @@ function openInNewTab(el, removeAttribute = false){
navigator.clipboard.writeText(input.value).then(function() {
// Change the icon to a checkmark on successful copy
let buttonIcon = button .querySelector('.usa-button__clipboard use');
+ console.log(`what is the button icon ${buttonIcon}`)
if (buttonIcon) {
let currentHref = buttonIcon.getAttribute('xlink:href');
let baseHref = currentHref.split('#')[0];
diff --git a/src/registrar/assets/sass/_theme/_admin.scss b/src/registrar/assets/sass/_theme/_admin.scss
index c636aab5c..8b5870b86 100644
--- a/src/registrar/assets/sass/_theme/_admin.scss
+++ b/src/registrar/assets/sass/_theme/_admin.scss
@@ -556,8 +556,39 @@ address.dja-address-contact-list {
min-height: 2.25rem !important;
}
+ button {
+ line-height: 15px;
+ }
+
+}
+
+.admin-icon-group.admin-icon-group__clipboard-link {
+ position: relative;
+ display: inline;
+ align-items: center;
+
+ .usa-button__icon {
+ position: absolute;
+ right: auto;
+ left: 4px;
+ height: 100%;
+ }
+ button {
+ font-size: unset !important;
+ display: inline-flex;
+ padding-top: 4px;
+ line-height: 14px;
+ }
}
.no-outline-on-click:focus {
outline: none !important;
+}
+
+svg.no-pointer-events {
+ use {
+ // USWDS has weird interactions with SVGs regarding tooltips,
+ // and other components. In this event, we need to disable pointer interactions.
+ pointer-events: none;
+ }
}
\ No newline at end of file
diff --git a/src/registrar/assets/sass/_theme/_tooltips.scss b/src/registrar/assets/sass/_theme/_tooltips.scss
index 01348e1b1..04c6f3cda 100644
--- a/src/registrar/assets/sass/_theme/_tooltips.scss
+++ b/src/registrar/assets/sass/_theme/_tooltips.scss
@@ -2,7 +2,7 @@
// Only apply this custom wrapping to desktop
@include at-media(desktop) {
- .usa-tooltip__body {
+ .usa-tooltip--registrar .usa-tooltip__body {
width: 350px;
white-space: normal;
text-align: center;
@@ -10,7 +10,7 @@
}
@include at-media(tablet) {
- .usa-tooltip__body {
+ .usa-tooltip--registrar .usa-tooltip__body {
width: 250px !important;
white-space: normal !important;
text-align: center !important;
@@ -18,7 +18,7 @@
}
@include at-media(mobile) {
- .usa-tooltip__body {
+ .usa-tooltip--registrar .usa-tooltip__body {
width: 250px !important;
white-space: normal !important;
text-align: center !important;
diff --git a/src/registrar/templates/admin/input_with_clipboard.html b/src/registrar/templates/admin/input_with_clipboard.html
index c2de55bf1..76f76b63f 100644
--- a/src/registrar/templates/admin/input_with_clipboard.html
+++ b/src/registrar/templates/admin/input_with_clipboard.html
@@ -10,10 +10,14 @@ Template for an input field with a clipboard
class="usa-button usa-button--unstyled padding-right-1 usa-button__icon usa-button__clipboard"
type="button"
>
+
+
\ No newline at end of file
diff --git a/src/registrar/templates/django/admin/includes/contact_detail_list.html b/src/registrar/templates/django/admin/includes/contact_detail_list.html
index cded7526b..48a980fbc 100644
--- a/src/registrar/templates/django/admin/includes/contact_detail_list.html
+++ b/src/registrar/templates/django/admin/includes/contact_detail_list.html
@@ -25,18 +25,37 @@
{# Email #}
{% if user.email or user.contact.email %}
{% if user.contact.email %}
-
- {{ user.contact.email }}
-
+ {{ user.contact.email }} |
+
{% endif %}
{% else %}
diff --git a/src/registrar/templates/django/admin/includes/detail_table_fieldset.html b/src/registrar/templates/django/admin/includes/detail_table_fieldset.html
index 53bdbe821..459ffe438 100644
--- a/src/registrar/templates/django/admin/includes/detail_table_fieldset.html
+++ b/src/registrar/templates/django/admin/includes/detail_table_fieldset.html
@@ -84,7 +84,7 @@ This is using a custom implementation fieldset.html (see admin/fieldset.html)
-
Other contact information
+
Other contact information
@@ -93,17 +93,24 @@ This is using a custom implementation fieldset.html (see admin/fieldset.html)
From e9332edc8b4a24ebdc7334465fcde84da872f39d Mon Sep 17 00:00:00 2001
From: zandercymatics <141044360+zandercymatics@users.noreply.github.com>
Date: Fri, 5 Apr 2024 15:21:45 -0600
Subject: [PATCH 67/96] Fix button styling
---
src/registrar/assets/js/get-gov-admin.js | 7 +++++++
src/registrar/assets/sass/_theme/_admin.scss | 4 ++++
.../django/admin/includes/detail_table_fieldset.html | 8 ++++----
3 files changed, 15 insertions(+), 4 deletions(-)
diff --git a/src/registrar/assets/js/get-gov-admin.js b/src/registrar/assets/js/get-gov-admin.js
index 844872144..2439d1346 100644
--- a/src/registrar/assets/js/get-gov-admin.js
+++ b/src/registrar/assets/js/get-gov-admin.js
@@ -164,11 +164,18 @@ function openInNewTab(el, removeAttribute = false){
// Change the button text
nearestSpan = button.querySelector("span")
+ if (button.classList.contains('usa-button')) {
+ nearestSpan.innerText = "Copy email";
+ } else {
+ nearestSpan.innerText = "Copy";
+ }
nearestSpan.innerText = "Copied to clipboard"
setTimeout(function() {
// Change back to the copy icon
buttonIcon.setAttribute('xlink:href', currentHref);
+ usa-button__small
+ if (button.classList.)
nearestSpan.innerText = "Copy"
}, 2000);
diff --git a/src/registrar/assets/sass/_theme/_admin.scss b/src/registrar/assets/sass/_theme/_admin.scss
index 54160b4ff..fd66754bc 100644
--- a/src/registrar/assets/sass/_theme/_admin.scss
+++ b/src/registrar/assets/sass/_theme/_admin.scss
@@ -597,3 +597,7 @@ address.dja-address-contact-list {
.no-outline-on-click:focus {
outline: none !important;
}
+
+.usa-button__small-text {
+ font-size: small;
+}
diff --git a/src/registrar/templates/django/admin/includes/detail_table_fieldset.html b/src/registrar/templates/django/admin/includes/detail_table_fieldset.html
index 3ed8c4333..a0a679290 100644
--- a/src/registrar/templates/django/admin/includes/detail_table_fieldset.html
+++ b/src/registrar/templates/django/admin/includes/detail_table_fieldset.html
@@ -84,7 +84,7 @@ This is using a custom implementation fieldset.html (see admin/fieldset.html)
-
Other contact information
+
Other contact information
@@ -97,10 +97,10 @@ This is using a custom implementation fieldset.html (see admin/fieldset.html)
{{ contact.phone }}
-
+
- Copy
+ Copy email
From 113b9b62bd2af783bf514c42823a2d75ad77df66 Mon Sep 17 00:00:00 2001
From: zandercymatics <141044360+zandercymatics@users.noreply.github.com>
Date: Fri, 5 Apr 2024 15:25:09 -0600
Subject: [PATCH 68/96] Update get-gov-admin.js
---
src/registrar/assets/js/get-gov-admin.js | 13 +++++--------
1 file changed, 5 insertions(+), 8 deletions(-)
diff --git a/src/registrar/assets/js/get-gov-admin.js b/src/registrar/assets/js/get-gov-admin.js
index 2439d1346..954035f95 100644
--- a/src/registrar/assets/js/get-gov-admin.js
+++ b/src/registrar/assets/js/get-gov-admin.js
@@ -164,19 +164,16 @@ function openInNewTab(el, removeAttribute = false){
// Change the button text
nearestSpan = button.querySelector("span")
- if (button.classList.contains('usa-button')) {
- nearestSpan.innerText = "Copy email";
- } else {
- nearestSpan.innerText = "Copy";
- }
nearestSpan.innerText = "Copied to clipboard"
setTimeout(function() {
// Change back to the copy icon
buttonIcon.setAttribute('xlink:href', currentHref);
- usa-button__small
- if (button.classList.)
- nearestSpan.innerText = "Copy"
+ if (button.classList.contains('usa-button')) {
+ nearestSpan.innerText = "Copy email";
+ } else {
+ nearestSpan.innerText = "Copy";
+ }
}, 2000);
}
From e4976e9da5a5d1dbad3693578f42aaaf111b6b5b Mon Sep 17 00:00:00 2001
From: zandercymatics <141044360+zandercymatics@users.noreply.github.com>
Date: Fri, 5 Apr 2024 15:37:57 -0600
Subject: [PATCH 69/96] Fix err
---
src/registrar/assets/js/get-gov-admin.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/registrar/assets/js/get-gov-admin.js b/src/registrar/assets/js/get-gov-admin.js
index 954035f95..2909a48be 100644
--- a/src/registrar/assets/js/get-gov-admin.js
+++ b/src/registrar/assets/js/get-gov-admin.js
@@ -169,7 +169,7 @@ function openInNewTab(el, removeAttribute = false){
setTimeout(function() {
// Change back to the copy icon
buttonIcon.setAttribute('xlink:href', currentHref);
- if (button.classList.contains('usa-button')) {
+ if (button.classList.contains('usa-button__small-text')) {
nearestSpan.innerText = "Copy email";
} else {
nearestSpan.innerText = "Copy";
From 3837c3ca21b91e1aba0d2680a86c1f510956a498 Mon Sep 17 00:00:00 2001
From: Alysia Broddrick
Date: Fri, 5 Apr 2024 14:50:33 -0700
Subject: [PATCH 70/96] updated files
---
ops/manifests/manifest-ab.yaml | 2 +-
ops/manifests/manifest-stable.yaml | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/ops/manifests/manifest-ab.yaml b/ops/manifests/manifest-ab.yaml
index 3ca800392..c7228a7c4 100644
--- a/ops/manifests/manifest-ab.yaml
+++ b/ops/manifests/manifest-ab.yaml
@@ -5,7 +5,7 @@ applications:
- python_buildpack
path: ../../src
instances: 1
- memory: 512M
+ memory: 1G
stack: cflinuxfs4
timeout: 180
command: ./run.sh
diff --git a/ops/manifests/manifest-stable.yaml b/ops/manifests/manifest-stable.yaml
index a70035445..80c97339f 100644
--- a/ops/manifests/manifest-stable.yaml
+++ b/ops/manifests/manifest-stable.yaml
@@ -5,7 +5,7 @@ applications:
- python_buildpack
path: ../../src
instances: 2
- memory: 512M
+ memory: 1G
stack: cflinuxfs4
timeout: 180
command: ./run.sh
From 839b325e66b38d5281e92d941d0300d64fdd1373 Mon Sep 17 00:00:00 2001
From: Alysia Broddrick
Date: Fri, 5 Apr 2024 15:59:25 -0700
Subject: [PATCH 71/96] undo my manifest change
---
ops/manifests/manifest-ab.yaml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/ops/manifests/manifest-ab.yaml b/ops/manifests/manifest-ab.yaml
index c7228a7c4..3ca800392 100644
--- a/ops/manifests/manifest-ab.yaml
+++ b/ops/manifests/manifest-ab.yaml
@@ -5,7 +5,7 @@ applications:
- python_buildpack
path: ../../src
instances: 1
- memory: 1G
+ memory: 512M
stack: cflinuxfs4
timeout: 180
command: ./run.sh
From 9a2e827634d63fbe702c2beb3a06abaac92bff0a Mon Sep 17 00:00:00 2001
From: zandercymatics <141044360+zandercymatics@users.noreply.github.com>
Date: Mon, 8 Apr 2024 08:34:39 -0600
Subject: [PATCH 72/96] Fix unit tests
---
src/registrar/tests/test_admin.py | 28 ++++++++++------------------
1 file changed, 10 insertions(+), 18 deletions(-)
diff --git a/src/registrar/tests/test_admin.py b/src/registrar/tests/test_admin.py
index af10dd2eb..bf92b3178 100644
--- a/src/registrar/tests/test_admin.py
+++ b/src/registrar/tests/test_admin.py
@@ -1455,41 +1455,37 @@ class TestDomainRequestAdmin(MockEppLib):
self.assertContains(response, "Meoward Jones")
# == Check for the submitter == #
+ self.assertContains(response, "mayor@igorville.gov", count=2)
expected_submitter_fields = [
# Field, expected value
("title", "Admin Tester"),
- ("email", "mayor@igorville.gov"),
("phone", "(555) 555 5556"),
]
self.test_helper.assert_response_contains_distinct_values(response, expected_submitter_fields)
self.assertContains(response, "Testy2 Tester2")
# == Check for the authorizing_official == #
-
+ self.assertContains(response, "testy@town.com", count=2)
expected_ao_fields = [
# Field, expected value
("title", "Chief Tester"),
- ("email", "testy@town.com"),
("phone", "(555) 555 5555"),
]
self.test_helper.assert_response_contains_distinct_values(response, expected_ao_fields)
- # count=5 because the underlying domain has two users with this name.
- # The dropdown has 3 of these.
- self.assertContains(response, "Testy Tester", count=5)
+ self.assertContains(response, "Testy Tester", count=10)
# == Test the other_employees field == #
-
+ self.assertContains(response, "testy2@town.com", count=2)
expected_other_employees_fields = [
# Field, expected value
("title", "Another Tester"),
- ("email", "testy2@town.com"),
("phone", "(555) 555 5557"),
]
self.test_helper.assert_response_contains_distinct_values(response, expected_other_employees_fields)
# Test for the copy link
- self.assertContains(response, "usa-button__clipboard-link", count=4)
+ self.assertContains(response, "usa-button__clipboard", count=4)
def test_save_model_sets_restricted_status_on_user(self):
with less_console_noise():
@@ -2219,41 +2215,37 @@ class TestDomainInformationAdmin(TestCase):
self.assertContains(response, "Meoward Jones")
# == Check for the submitter == #
+ self.assertContains(response, "mayor@igorville.gov", count=2)
expected_submitter_fields = [
# Field, expected value
("title", "Admin Tester"),
- ("email", "mayor@igorville.gov"),
("phone", "(555) 555 5556"),
]
self.test_helper.assert_response_contains_distinct_values(response, expected_submitter_fields)
self.assertContains(response, "Testy2 Tester2")
# == Check for the authorizing_official == #
-
+ self.assertContains(response, "testy@town.com", count=2)
expected_ao_fields = [
# Field, expected value
("title", "Chief Tester"),
- ("email", "testy@town.com"),
("phone", "(555) 555 5555"),
]
self.test_helper.assert_response_contains_distinct_values(response, expected_ao_fields)
- # count=5 because the underlying domain has two users with this name.
- # The dropdown has 3 of these.
- self.assertContains(response, "Testy Tester", count=5)
+ self.assertContains(response, "Testy Tester", count=10)
# == Test the other_employees field == #
-
+ self.assertContains(response, "testy2@town.com", count=2)
expected_other_employees_fields = [
# Field, expected value
("title", "Another Tester"),
- ("email", "testy2@town.com"),
("phone", "(555) 555 5557"),
]
self.test_helper.assert_response_contains_distinct_values(response, expected_other_employees_fields)
# Test for the copy link
- self.assertContains(response, "usa-button__clipboard-link", count=4)
+ self.assertContains(response, "usa-button__clipboard", count=4)
def test_readonly_fields_for_analyst(self):
"""Ensures that analysts have their permissions setup correctly"""
From 1df928dad68b789b3a62f631d635a2910674219e Mon Sep 17 00:00:00 2001
From: zandercymatics <141044360+zandercymatics@users.noreply.github.com>
Date: Mon, 8 Apr 2024 08:45:38 -0600
Subject: [PATCH 73/96] Fix test
---
src/registrar/tests/test_reports.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/registrar/tests/test_reports.py b/src/registrar/tests/test_reports.py
index 6299349c5..be66cb876 100644
--- a/src/registrar/tests/test_reports.py
+++ b/src/registrar/tests/test_reports.py
@@ -723,7 +723,7 @@ class HelperFunctions(MockDb):
# Test without distinct
managed_domains_sliced_at_end_date = get_sliced_domains(filter_condition)
- expected_content = [3, 4, 1, 0, 0, 0, 0, 0, 0, 0]
+ expected_content = [3, 2, 1, 0, 0, 0, 0, 0, 0, 0]
self.assertEqual(managed_domains_sliced_at_end_date, expected_content)
def test_get_sliced_requests(self):
From ab2c0974416100fd9d9e3f3c39635c52e3d2afdd Mon Sep 17 00:00:00 2001
From: zandercymatics <141044360+zandercymatics@users.noreply.github.com>
Date: Mon, 8 Apr 2024 09:18:57 -0600
Subject: [PATCH 74/96] fix bug
---
src/registrar/forms/domain.py | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/src/registrar/forms/domain.py b/src/registrar/forms/domain.py
index faf21e12e..ee1362415 100644
--- a/src/registrar/forms/domain.py
+++ b/src/registrar/forms/domain.py
@@ -181,6 +181,7 @@ NameserverFormset = formset_factory(
class ContactForm(forms.ModelForm):
"""Form for updating contacts."""
+ email = forms.EmailField(max_length=None)
class Meta:
model = Contact
fields = ["first_name", "middle_name", "last_name", "title", "email", "phone"]
@@ -204,6 +205,10 @@ class ContactForm(forms.ModelForm):
# which interferes with out input_with_errors template tag
self.fields["phone"].widget.attrs.pop("maxlength", None)
+ # Define a custom validator for the email field with a custom error message
+ email_max_length_validator = MaxLengthValidator(320, message="Response must be less than 320 characters.")
+ self.fields["email"].validators.append(email_max_length_validator)
+
for field_name in self.required:
self.fields[field_name].required = True
From 88fe4858bc07c697825b9ef5ea307e704c43ad26 Mon Sep 17 00:00:00 2001
From: zandercymatics <141044360+zandercymatics@users.noreply.github.com>
Date: Mon, 8 Apr 2024 09:44:40 -0600
Subject: [PATCH 75/96] Add logs
---
src/registrar/signals.py | 19 ++++++++++++++++++-
1 file changed, 18 insertions(+), 1 deletion(-)
diff --git a/src/registrar/signals.py b/src/registrar/signals.py
index d106f974c..aea20048a 100644
--- a/src/registrar/signals.py
+++ b/src/registrar/signals.py
@@ -103,7 +103,16 @@ def _update_org_type_from_generic_org_and_election(instance, org_map):
if generic_org_type not in org_map:
# Election board should always be reset to None if the record
# can't have one. For example, federal.
- instance.is_election_board = None
+ if instance.is_election_board is not None:
+ # This maintains data consistency.
+ # There is no avenue for this to occur in the UI,
+ # as such - this can only occur if the object is initialized in this way.
+ # Or if there are pre-existing data.
+ logger.warning(
+ "create_or_update_organization_type() -> is_election_board "
+ f"cannot exist for {generic_org_type}. Setting to None."
+ )
+ instance.is_election_board = None
instance.organization_type = generic_org_type
else:
# This can only happen with manual data tinkering, which causes these to be out of sync.
@@ -137,6 +146,14 @@ def _update_generic_org_and_election_from_org_type(instance, election_org_map, g
if current_org_type in generic_org_map:
instance.is_election_board = False
else:
+ # This maintains data consistency.
+ # There is no avenue for this to occur in the UI,
+ # as such - this can only occur if the object is initialized in this way.
+ # Or if there are pre-existing data.
+ logger.warning(
+ "create_or_update_organization_type() -> is_election_board "
+ f"cannot exist for {current_org_type}. Setting to None."
+ )
instance.is_election_board = None
From 8ce51f5bd9d0dc79b6108064bc83021086ca68fb Mon Sep 17 00:00:00 2001
From: zandercymatics <141044360+zandercymatics@users.noreply.github.com>
Date: Mon, 8 Apr 2024 10:06:52 -0600
Subject: [PATCH 76/96] Update admin.py
---
src/registrar/admin.py | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/src/registrar/admin.py b/src/registrar/admin.py
index 8a2bc2141..31a0247af 100644
--- a/src/registrar/admin.py
+++ b/src/registrar/admin.py
@@ -884,6 +884,8 @@ class DomainInformationAdmin(ListHeaderAdmin):
"Type of organization",
{
"fields": [
+ "generic_org_type",
+ "is_election_board",
"organization_type",
]
},
@@ -927,7 +929,7 @@ class DomainInformationAdmin(ListHeaderAdmin):
]
# Readonly fields for analysts and superusers
- readonly_fields = ("other_contacts",)
+ readonly_fields = ("other_contacts", "generic_org_type", "is_election_board")
# Read only that we'll leverage for CISA Analysts
analyst_readonly_fields = [
@@ -1121,6 +1123,8 @@ class DomainRequestAdmin(ListHeaderAdmin):
"Type of organization",
{
"fields": [
+ "generic_org_type",
+ "is_election_board",
"organization_type",
]
},
@@ -1164,7 +1168,7 @@ class DomainRequestAdmin(ListHeaderAdmin):
]
# Readonly fields for analysts and superusers
- readonly_fields = ("other_contacts", "current_websites", "alternative_domains")
+ readonly_fields = ("other_contacts", "current_websites", "alternative_domains", "generic_org_type", "is_election_board")
# Read only that we'll leverage for CISA Analysts
analyst_readonly_fields = [
From 0bf7e1f1a8dad7e1d96bfdb44c052257ec1fcd21 Mon Sep 17 00:00:00 2001
From: zandercymatics <141044360+zandercymatics@users.noreply.github.com>
Date: Mon, 8 Apr 2024 11:03:23 -0600
Subject: [PATCH 77/96] Fix tests
---
src/registrar/admin.py | 8 +++++++-
src/registrar/models/domain_request.py | 2 +-
src/registrar/tests/test_admin.py | 8 ++++++++
3 files changed, 16 insertions(+), 2 deletions(-)
diff --git a/src/registrar/admin.py b/src/registrar/admin.py
index 31a0247af..89efc5951 100644
--- a/src/registrar/admin.py
+++ b/src/registrar/admin.py
@@ -1168,7 +1168,13 @@ class DomainRequestAdmin(ListHeaderAdmin):
]
# Readonly fields for analysts and superusers
- readonly_fields = ("other_contacts", "current_websites", "alternative_domains", "generic_org_type", "is_election_board")
+ readonly_fields = (
+ "other_contacts",
+ "current_websites",
+ "alternative_domains",
+ "generic_org_type",
+ "is_election_board",
+ )
# Read only that we'll leverage for CISA Analysts
analyst_readonly_fields = [
diff --git a/src/registrar/models/domain_request.py b/src/registrar/models/domain_request.py
index fc2864fe4..673d765d1 100644
--- a/src/registrar/models/domain_request.py
+++ b/src/registrar/models/domain_request.py
@@ -100,7 +100,7 @@ class DomainRequest(TimeStampedModel):
class OrganizationChoices(models.TextChoices):
"""
Primary organization choices:
- For use in the request experience
+ For use in the domain request experience
Keys need to match OrgChoicesElectionOffice and OrganizationChoicesVerbose
"""
diff --git a/src/registrar/tests/test_admin.py b/src/registrar/tests/test_admin.py
index 7b810a2c5..e276da5d8 100644
--- a/src/registrar/tests/test_admin.py
+++ b/src/registrar/tests/test_admin.py
@@ -1588,6 +1588,8 @@ class TestDomainRequestAdmin(MockEppLib):
"other_contacts",
"current_websites",
"alternative_domains",
+ "generic_org_type",
+ "is_election_board",
"id",
"created_at",
"updated_at",
@@ -1637,6 +1639,8 @@ class TestDomainRequestAdmin(MockEppLib):
"other_contacts",
"current_websites",
"alternative_domains",
+ "generic_org_type",
+ "is_election_board",
"creator",
"about_your_organization",
"requested_domain",
@@ -1662,6 +1666,8 @@ class TestDomainRequestAdmin(MockEppLib):
"other_contacts",
"current_websites",
"alternative_domains",
+ "generic_org_type",
+ "is_election_board",
]
self.assertEqual(readonly_fields, expected_fields)
@@ -2259,6 +2265,8 @@ class TestDomainInformationAdmin(TestCase):
expected_fields = [
"other_contacts",
+ "generic_org_type",
+ "is_election_board",
"creator",
"type_of_work",
"more_organization_information",
From df9d0c7ac36f33706285d3f3d29daae9270e15d6 Mon Sep 17 00:00:00 2001
From: Erin <121973038+erinysong@users.noreply.github.com>
Date: Mon, 8 Apr 2024 10:14:51 -0700
Subject: [PATCH 78/96] Add user permission migration docs ot
user-permissions.md
---
docs/developer/user-permissions.md | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/docs/developer/user-permissions.md b/docs/developer/user-permissions.md
index f7c41492d..4e627b0a5 100644
--- a/docs/developer/user-permissions.md
+++ b/docs/developer/user-permissions.md
@@ -19,6 +19,18 @@ role or set of permissions that they have. We use a `UserDomainRole`
`User.domains` many-to-many relationship that works through the
`UserDomainRole` link table.
+## Migrating changes to Analyst Permissions model
+Analysts are allowed a certain set of read/write registrar permissions.
+Setting user permissions requires a migration to change the UserGroup
+and Permission models, which requires us to manually make a migration
+file for user permission changes.
+To update analyst permissions do the following:
+1. Make desired changes to analyst group permissions in user_group.py.
+2. Follow the steps in 0037_create_groups_v01.py to create a duplicate
+migration for the updated user group permissions.
+3. To migrate locally, run docker-compose up. To migrate on a sandbox,
+push the new migration onto your sandbox before migrating.
+
## Permission decorator
The Django objects that need to be permission controlled are various views.
From 51d14457929d67b426509bb07996aa9219b958ba Mon Sep 17 00:00:00 2001
From: Erin <121973038+erinysong@users.noreply.github.com>
Date: Mon, 8 Apr 2024 10:15:49 -0700
Subject: [PATCH 79/96] Revert documentation changes to migration files
---
src/registrar/migrations/0037_create_groups_v01.py | 2 +-
src/registrar/migrations/0038_create_groups_v02.py | 2 +-
src/registrar/migrations/0042_create_groups_v03.py | 2 +-
src/registrar/migrations/0044_create_groups_v04.py | 2 +-
src/registrar/migrations/0053_create_groups_v05.py | 2 +-
src/registrar/migrations/0065_create_groups_v06.py | 2 +-
src/registrar/migrations/0067_create_groups_v07.py | 2 +-
src/registrar/migrations/0075_create_groups_v08.py | 2 +-
8 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/src/registrar/migrations/0037_create_groups_v01.py b/src/registrar/migrations/0037_create_groups_v01.py
index 0c04a8b61..3540ea2f3 100644
--- a/src/registrar/migrations/0037_create_groups_v01.py
+++ b/src/registrar/migrations/0037_create_groups_v01.py
@@ -1,5 +1,5 @@
# This migration creates the create_full_access_group and create_cisa_analyst_group groups
-# It is dependent on 0036 (which populates ContentType and Permissions)
+# It is dependent on 0035 (which populates ContentType and Permissions)
# If permissions on the groups need changing, edit CISA_ANALYST_GROUP_PERMISSIONS
# in the user_group model then:
# [NOT RECOMMENDED]
diff --git a/src/registrar/migrations/0038_create_groups_v02.py b/src/registrar/migrations/0038_create_groups_v02.py
index 70d13b61a..fc61db3c0 100644
--- a/src/registrar/migrations/0038_create_groups_v02.py
+++ b/src/registrar/migrations/0038_create_groups_v02.py
@@ -1,5 +1,5 @@
# This migration creates the create_full_access_group and create_cisa_analyst_group groups
-# It is dependent on 0037 (which also updates user role permissions)
+# It is dependent on 0035 (which populates ContentType and Permissions)
# If permissions on the groups need changing, edit CISA_ANALYST_GROUP_PERMISSIONS
# in the user_group model then:
# [NOT RECOMMENDED]
diff --git a/src/registrar/migrations/0042_create_groups_v03.py b/src/registrar/migrations/0042_create_groups_v03.py
index e30841599..01b7985bf 100644
--- a/src/registrar/migrations/0042_create_groups_v03.py
+++ b/src/registrar/migrations/0042_create_groups_v03.py
@@ -1,5 +1,5 @@
# This migration creates the create_full_access_group and create_cisa_analyst_group groups
-# It is dependent on 0041 (which changes fields in domain request and domain information)
+# It is dependent on 0035 (which populates ContentType and Permissions)
# If permissions on the groups need changing, edit CISA_ANALYST_GROUP_PERMISSIONS
# in the user_group model then:
# [NOT RECOMMENDED]
diff --git a/src/registrar/migrations/0044_create_groups_v04.py b/src/registrar/migrations/0044_create_groups_v04.py
index 63cad49bb..ecb48e335 100644
--- a/src/registrar/migrations/0044_create_groups_v04.py
+++ b/src/registrar/migrations/0044_create_groups_v04.py
@@ -1,5 +1,5 @@
# This migration creates the create_full_access_group and create_cisa_analyst_group groups
-# It is dependent on 0043 (which adds an expiry date field to a domain.)
+# It is dependent on 0035 (which populates ContentType and Permissions)
# If permissions on the groups need changing, edit CISA_ANALYST_GROUP_PERMISSIONS
# in the user_group model then:
# [NOT RECOMMENDED]
diff --git a/src/registrar/migrations/0053_create_groups_v05.py b/src/registrar/migrations/0053_create_groups_v05.py
index 91e8389df..aaf74a9db 100644
--- a/src/registrar/migrations/0053_create_groups_v05.py
+++ b/src/registrar/migrations/0053_create_groups_v05.py
@@ -1,5 +1,5 @@
# This migration creates the create_full_access_group and create_cisa_analyst_group groups
-# It is dependent on 0052 (which alters fields in a domain request)
+# It is dependent on 0035 (which populates ContentType and Permissions)
# If permissions on the groups need changing, edit CISA_ANALYST_GROUP_PERMISSIONS
# in the user_group model then:
# [NOT RECOMMENDED]
diff --git a/src/registrar/migrations/0065_create_groups_v06.py b/src/registrar/migrations/0065_create_groups_v06.py
index 965dc06a8..d2cb32cee 100644
--- a/src/registrar/migrations/0065_create_groups_v06.py
+++ b/src/registrar/migrations/0065_create_groups_v06.py
@@ -1,5 +1,5 @@
# This migration creates the create_full_access_group and create_cisa_analyst_group groups
-# It is dependent on 0065 (which renames fields in domain application)
+# It is dependent on 0035 (which populates ContentType and Permissions)
# If permissions on the groups need changing, edit CISA_ANALYST_GROUP_PERMISSIONS
# in the user_group model then:
# [NOT RECOMMENDED]
diff --git a/src/registrar/migrations/0067_create_groups_v07.py b/src/registrar/migrations/0067_create_groups_v07.py
index 809738ba3..85138d4af 100644
--- a/src/registrar/migrations/0067_create_groups_v07.py
+++ b/src/registrar/migrations/0067_create_groups_v07.py
@@ -1,5 +1,5 @@
# This migration creates the create_full_access_group and create_cisa_analyst_group groups
-# It is dependent on 0066 (which updates users with permission as Verified by Staff)
+# It is dependent on 0035 (which populates ContentType and Permissions)
# If permissions on the groups need changing, edit CISA_ANALYST_GROUP_PERMISSIONS
# in the user_group model then:
# [NOT RECOMMENDED]
diff --git a/src/registrar/migrations/0075_create_groups_v08.py b/src/registrar/migrations/0075_create_groups_v08.py
index a4df52d21..b0b2ed740 100644
--- a/src/registrar/migrations/0075_create_groups_v08.py
+++ b/src/registrar/migrations/0075_create_groups_v08.py
@@ -1,5 +1,5 @@
# This migration creates the create_full_access_group and create_cisa_analyst_group groups
-# It is dependent on 0074 (which renames Domain Application and its fields)
+# It is dependent on 0035 (which populates ContentType and Permissions)
# If permissions on the groups need changing, edit CISA_ANALYST_GROUP_PERMISSIONS
# in the user_group model then:
# [NOT RECOMMENDED]
From 12f536a9107e40a0021169a8b6116b7120dd6bfe Mon Sep 17 00:00:00 2001
From: Erin <121973038+erinysong@users.noreply.github.com>
Date: Mon, 8 Apr 2024 11:24:45 -0700
Subject: [PATCH 80/96] Clean up redundant docs in user_group
---
src/registrar/models/user_group.py | 7 +------
1 file changed, 1 insertion(+), 6 deletions(-)
diff --git a/src/registrar/models/user_group.py b/src/registrar/models/user_group.py
index 3071fba11..8565ea288 100644
--- a/src/registrar/models/user_group.py
+++ b/src/registrar/models/user_group.py
@@ -7,12 +7,7 @@ logger = logging.getLogger(__name__)
class UserGroup(Group):
"""
UserGroup sets read and write permissions for superusers (who have full access)
- and analysts. To update analyst permissions do the following:
- 1. Make desired changes to analyst group permissions in user_group.py.
- 2. Follow the steps in 0037_create_groups_v01.py to create a duplicate
- migration for the updated user group permissions.
- 3. To migrate locally, run docker-compose up. To migrate on a sandbox,
- push the new migration onto your sandbox before migrating.
+ and analysts. For more details, see the dev docs for user-permissions.
"""
class Meta:
From 673a858bc3744df5f23351b550bd4b2e854353ab Mon Sep 17 00:00:00 2001
From: zandercymatics <141044360+zandercymatics@users.noreply.github.com>
Date: Mon, 8 Apr 2024 13:15:05 -0600
Subject: [PATCH 81/96] Refactor to move logic into save + helper
---
src/registrar/models/domain_information.py | 29 +++
src/registrar/models/domain_request.py | 29 +++
.../models/utility/generic_helper.py | 208 ++++++++++++++++++
src/registrar/signals.py | 187 ----------------
4 files changed, 266 insertions(+), 187 deletions(-)
diff --git a/src/registrar/models/domain_information.py b/src/registrar/models/domain_information.py
index f41e7d5c6..2ed27504c 100644
--- a/src/registrar/models/domain_information.py
+++ b/src/registrar/models/domain_information.py
@@ -2,6 +2,7 @@ from __future__ import annotations
from django.db import transaction
from registrar.models.utility.domain_helper import DomainHelper
+from registrar.models.utility.generic_helper import CreateOrUpdateOrganizationTypeHelper
from .domain_request import DomainRequest
from .utility.time_stamped_model import TimeStampedModel
@@ -235,6 +236,34 @@ class DomainInformation(TimeStampedModel):
except Exception:
return ""
+ def save(self, *args, **kwargs):
+ """Save override for custom properties"""
+
+ # Define mappings between generic org and election org.
+ # These have to be defined here, as you'd get a cyclical import error
+ # otherwise.
+
+ # For any given organization type, return the "_election" variant.
+ # For example: STATE_OR_TERRITORY => STATE_OR_TERRITORY_ELECTION
+ generic_org_map = DomainRequest.OrgChoicesElectionOffice.get_org_generic_to_org_election()
+
+ # For any given "_election" variant, return the base org type.
+ # For example: STATE_OR_TERRITORY_ELECTION => STATE_OR_TERRITORY
+ election_org_map = DomainRequest.OrgChoicesElectionOffice.get_org_election_to_org_generic()
+
+ # Manages the "organization_type" variable and keeps in sync with
+ # "is_election_office" and "generic_organization_type"
+ org_type_helper = CreateOrUpdateOrganizationTypeHelper(
+ sender=self.__class__,
+ instance=self,
+ generic_org_to_org_map=generic_org_map,
+ election_org_to_generic_org_map=election_org_map,
+ )
+
+ # Actually updates the organization_type field
+ org_type_helper.create_or_update_organization_type()
+ super().save(*args, **kwargs)
+
@classmethod
def create_from_da(cls, domain_request: DomainRequest, domain=None):
"""Takes in a DomainRequest and converts it into DomainInformation"""
diff --git a/src/registrar/models/domain_request.py b/src/registrar/models/domain_request.py
index 673d765d1..bd529f7e6 100644
--- a/src/registrar/models/domain_request.py
+++ b/src/registrar/models/domain_request.py
@@ -9,6 +9,7 @@ from django.db import models
from django_fsm import FSMField, transition # type: ignore
from django.utils import timezone
from registrar.models.domain import Domain
+from registrar.models.utility.generic_helper import CreateOrUpdateOrganizationTypeHelper
from registrar.utility.errors import FSMDomainRequestError, FSMErrorCodes
from .utility.time_stamped_model import TimeStampedModel
@@ -665,6 +666,34 @@ class DomainRequest(TimeStampedModel):
help_text="Notes about this request",
)
+ def save(self, *args, **kwargs):
+ """Save override for custom properties"""
+
+ # Define mappings between generic org and election org.
+ # These have to be defined here, as you'd get a cyclical import error
+ # otherwise.
+
+ # For any given organization type, return the "_election" variant.
+ # For example: STATE_OR_TERRITORY => STATE_OR_TERRITORY_ELECTION
+ generic_org_map = self.OrgChoicesElectionOffice.get_org_generic_to_org_election()
+
+ # For any given "_election" variant, return the base org type.
+ # For example: STATE_OR_TERRITORY_ELECTION => STATE_OR_TERRITORY
+ election_org_map = self.OrgChoicesElectionOffice.get_org_election_to_org_generic()
+
+ # Manages the "organization_type" variable and keeps in sync with
+ # "is_election_office" and "generic_organization_type"
+ org_type_helper = CreateOrUpdateOrganizationTypeHelper(
+ sender=self.__class__,
+ instance=self,
+ generic_org_to_org_map=generic_org_map,
+ election_org_to_generic_org_map=election_org_map,
+ )
+
+ # Actually updates the organization_type field
+ org_type_helper.create_or_update_organization_type()
+ super().save(*args, **kwargs)
+
def __str__(self):
try:
if self.requested_domain and self.requested_domain.name:
diff --git a/src/registrar/models/utility/generic_helper.py b/src/registrar/models/utility/generic_helper.py
index 01d4e6b33..fadca2b14 100644
--- a/src/registrar/models/utility/generic_helper.py
+++ b/src/registrar/models/utility/generic_helper.py
@@ -35,3 +35,211 @@ class Timer:
self.end = time.time()
self.duration = self.end - self.start
logger.info(f"Execution time: {self.duration} seconds")
+
+
+class CreateOrUpdateOrganizationTypeHelper:
+ """
+ A helper that manages the "organization_type" field in DomainRequest and DomainInformation
+ """
+ def __init__(self, sender, instance, generic_org_to_org_map, election_org_to_generic_org_map):
+ # The "model type"
+ self.sender = sender
+ self.instance = instance
+ self.generic_org_to_org_map = generic_org_to_org_map
+ self.election_org_to_generic_org_map = election_org_to_generic_org_map
+
+ def create_or_update_organization_type(self):
+ """The organization_type field on DomainRequest and DomainInformation is consituted from the
+ generic_org_type and is_election_board fields. To keep the organization_type
+ field up to date, we need to update it before save based off of those field
+ values.
+
+ If the instance is marked as an election board and the generic_org_type is not
+ one of the excluded types (FEDERAL, INTERSTATE, SCHOOL_DISTRICT), the
+ organization_type is set to a corresponding election variant. Otherwise, it directly
+ mirrors the generic_org_type value.
+ """
+
+ # A new record is added with organization_type not defined.
+ # This happens from the regular domain request flow.
+ is_new_instance = self.instance.id is None
+ if is_new_instance:
+ self._handle_new_instance()
+ else:
+ self._handle_existing_instance()
+
+ return self.instance
+
+ def _handle_new_instance(self):
+ # == Check for invalid conditions before proceeding == #
+ should_proceed = self._validate_new_instance()
+ if not should_proceed:
+ return None
+ # == Program flow will halt here if there is no reason to update == #
+
+ # == Update the linked values == #
+ organization_type_needs_update = self.instance.organization_type is None
+ generic_org_type_needs_update = self.instance.generic_org_type is None
+
+ # If a field is none, it indicates (per prior checks) that the
+ # related field (generic org type <-> org type) has data and we should update according to that.
+ if organization_type_needs_update:
+ self._update_org_type_from_generic_org_and_election()
+ elif generic_org_type_needs_update:
+ self._update_generic_org_and_election_from_org_type()
+
+ # Update the field
+ self._update_fields(organization_type_needs_update, generic_org_type_needs_update)
+
+ def _handle_existing_instance(self):
+ # == Init variables == #
+ # Instance is already in the database, fetch its current state
+ current_instance = self.sender.objects.get(id=self.instance.id)
+
+ # Check the new and old values
+ generic_org_type_changed = self.instance.generic_org_type != current_instance.generic_org_type
+ is_election_board_changed = self.instance.is_election_board != current_instance.is_election_board
+ organization_type_changed = self.instance.organization_type != current_instance.organization_type
+
+ # == Check for invalid conditions before proceeding == #
+ if organization_type_changed and (generic_org_type_changed or is_election_board_changed):
+ # Since organization type is linked with generic_org_type and election board,
+ # we have to update one or the other, not both.
+ # This will not happen in normal flow as it is not possible otherwise.
+ raise ValueError("Cannot update organization_type and generic_org_type simultaneously.")
+ elif not organization_type_changed and (not generic_org_type_changed and not is_election_board_changed):
+ # No values to update - do nothing
+ return None
+ # == Program flow will halt here if there is no reason to update == #
+
+ # == Update the linked values == #
+ # Find out which field needs updating
+ organization_type_needs_update = generic_org_type_changed or is_election_board_changed
+ generic_org_type_needs_update = organization_type_changed
+
+ # Update the field
+ self._update_fields(organization_type_needs_update, generic_org_type_needs_update)
+
+ def _update_fields(self, organization_type_needs_update, generic_org_type_needs_update):
+ """
+ Validates the conditions for updating organization and generic organization types.
+
+ Raises:
+ ValueError: If both organization_type_needs_update and generic_org_type_needs_update are True,
+ indicating an attempt to update both fields simultaneously, which is not allowed.
+ """
+ # We shouldn't update both of these at the same time.
+ # It is more useful to have these as seperate variables, but it does impose
+ # this restraint.
+ if organization_type_needs_update and generic_org_type_needs_update:
+ raise ValueError("Cannot update both org type and generic org type at the same time.")
+
+ if organization_type_needs_update:
+ self._update_org_type_from_generic_org_and_election()
+ elif generic_org_type_needs_update:
+ self._update_generic_org_and_election_from_org_type()
+
+ def _update_org_type_from_generic_org_and_election(self):
+ """Given a field values for generic_org_type and is_election_board, update the
+ organization_type field."""
+
+ # We convert to a string because the enum types are different.
+ generic_org_type = str(self.instance.generic_org_type)
+ if generic_org_type not in self.generic_org_to_org_map:
+ # Election board should always be reset to None if the record
+ # can't have one. For example, federal.
+ if self.instance.is_election_board is not None:
+ # This maintains data consistency.
+ # There is no avenue for this to occur in the UI,
+ # as such - this can only occur if the object is initialized in this way.
+ # Or if there are pre-existing data.
+ logger.warning(
+ "create_or_update_organization_type() -> is_election_board "
+ f"cannot exist for {generic_org_type}. Setting to None."
+ )
+ self.instance.is_election_board = None
+ self.instance.organization_type = generic_org_type
+ else:
+ # This can only happen with manual data tinkering, which causes these to be out of sync.
+ if self.instance.is_election_board is None:
+ logger.warning("create_or_update_organization_type() -> is_election_board is out of sync. Updating value.")
+ self.instance.is_election_board = False
+
+ if self.instance.is_election_board:
+ self.instance.organization_type = self.generic_org_to_org_map[generic_org_type]
+ else:
+ self.instance.organization_type = generic_org_type
+
+ def _update_generic_org_and_election_from_org_type(self):
+ """Given the field value for organization_type, update the
+ generic_org_type and is_election_board field."""
+
+ # We convert to a string because the enum types are different
+ # between OrgChoicesElectionOffice and OrganizationChoices.
+ # But their names are the same (for the most part).
+ current_org_type = str(self.instance.organization_type)
+ election_org_map = self.election_org_to_generic_org_map
+ generic_org_map = self.generic_org_to_org_map
+
+ # This essentially means: "_election" in current_org_type.
+ if current_org_type in election_org_map:
+ new_org = election_org_map[current_org_type]
+ self.instance.generic_org_type = new_org
+ self.instance.is_election_board = True
+ else:
+ self.instance.generic_org_type = current_org_type
+
+ # This basically checks if the given org type
+ # can even have an election board in the first place.
+ # For instance, federal cannot so is_election_board = None
+ if current_org_type in generic_org_map:
+ self.instance.is_election_board = False
+ else:
+ # This maintains data consistency.
+ # There is no avenue for this to occur in the UI,
+ # as such - this can only occur if the object is initialized in this way.
+ # Or if there are pre-existing data.
+ logger.warning(
+ "create_or_update_organization_type() -> is_election_board "
+ f"cannot exist for {current_org_type}. Setting to None."
+ )
+ self.instance.is_election_board = None
+
+ def _validate_new_instance(self):
+ """
+ Validates whether a new instance of DomainRequest or DomainInformation can proceed with the update
+ based on the consistency between organization_type, generic_org_type, and is_election_board.
+
+ Returns a boolean determining if execution should proceed or not.
+ """
+
+ # We conditionally accept both of these values to exist simultaneously, as long as
+ # those values do not intefere with eachother.
+ # Because this condition can only be triggered through a dev (no user flow),
+ # we throw an error if an invalid state is found here.
+ if self.instance.organization_type and self.instance.generic_org_type:
+ generic_org_type = str(self.instance.generic_org_type)
+ organization_type = str(self.instance.organization_type)
+
+ # Strip "_election" if it exists
+ mapped_org_type = self.election_org_to_generic_org_map.get(organization_type)
+
+ # Do tests on the org update for election board changes.
+ is_election_type = "_election" in organization_type
+ can_have_election_board = organization_type in self.generic_org_to_org_map
+
+ election_board_mismatch = (is_election_type != self.instance.is_election_board) and can_have_election_board
+ org_type_mismatch = mapped_org_type is not None and (generic_org_type != mapped_org_type)
+ if election_board_mismatch or org_type_mismatch:
+ message = (
+ "Cannot add organization_type and generic_org_type simultaneously "
+ "when generic_org_type, is_election_board, and organization_type values do not match."
+ )
+ raise ValueError(message)
+
+ return True
+ elif not self.instance.organization_type and not self.instance.generic_org_type:
+ return False
+ else:
+ return True
+
diff --git a/src/registrar/signals.py b/src/registrar/signals.py
index aea20048a..67d007670 100644
--- a/src/registrar/signals.py
+++ b/src/registrar/signals.py
@@ -9,193 +9,6 @@ from .models import User, Contact, DomainRequest, DomainInformation
logger = logging.getLogger(__name__)
-@receiver(pre_save, sender=DomainRequest)
-@receiver(pre_save, sender=DomainInformation)
-def create_or_update_organization_type(sender: DomainRequest | DomainInformation, instance, **kwargs):
- """The organization_type field on DomainRequest and DomainInformation is consituted from the
- generic_org_type and is_election_board fields. To keep the organization_type
- field up to date, we need to update it before save based off of those field
- values.
-
- If the instance is marked as an election board and the generic_org_type is not
- one of the excluded types (FEDERAL, INTERSTATE, SCHOOL_DISTRICT), the
- organization_type is set to a corresponding election variant. Otherwise, it directly
- mirrors the generic_org_type value.
- """
-
- # == Init variables == #
- election_org_choices = DomainRequest.OrgChoicesElectionOffice
-
- # For any given organization type, return the "_election" variant.
- # For example: STATE_OR_TERRITORY => STATE_OR_TERRITORY_ELECTION
- generic_org_to_org_map = election_org_choices.get_org_generic_to_org_election()
-
- # For any given "_election" variant, return the base org type.
- # For example: STATE_OR_TERRITORY_ELECTION => STATE_OR_TERRITORY
- election_org_to_generic_org_map = election_org_choices.get_org_election_to_org_generic()
-
- # A new record is added with organization_type not defined.
- # This happens from the regular domain request flow.
- is_new_instance = instance.id is None
-
- if is_new_instance:
-
- # == Check for invalid conditions before proceeding == #
- should_proceed = _validate_new_instance(instance, election_org_to_generic_org_map, generic_org_to_org_map)
- if not should_proceed:
- return None
- # == Program flow will halt here if there is no reason to update == #
-
- # == Update the linked values == #
- organization_type_needs_update = instance.organization_type is None
- generic_org_type_needs_update = instance.generic_org_type is None
-
- # If a field is none, it indicates (per prior checks) that the
- # related field (generic org type <-> org type) has data and we should update according to that.
- if organization_type_needs_update:
- _update_org_type_from_generic_org_and_election(instance, generic_org_to_org_map)
- elif generic_org_type_needs_update:
- _update_generic_org_and_election_from_org_type(
- instance, election_org_to_generic_org_map, generic_org_to_org_map
- )
- else:
-
- # == Init variables == #
- # Instance is already in the database, fetch its current state
- current_instance = sender.objects.get(id=instance.id)
-
- # Check the new and old values
- generic_org_type_changed = instance.generic_org_type != current_instance.generic_org_type
- is_election_board_changed = instance.is_election_board != current_instance.is_election_board
- organization_type_changed = instance.organization_type != current_instance.organization_type
-
- # == Check for invalid conditions before proceeding == #
- if organization_type_changed and (generic_org_type_changed or is_election_board_changed):
- # Since organization type is linked with generic_org_type and election board,
- # we have to update one or the other, not both.
- # This will not happen in normal flow as it is not possible otherwise.
- raise ValueError("Cannot update organization_type and generic_org_type simultaneously.")
- elif not organization_type_changed and (not generic_org_type_changed and not is_election_board_changed):
- # No values to update - do nothing
- return None
- # == Program flow will halt here if there is no reason to update == #
-
- # == Update the linked values == #
- # Find out which field needs updating
- organization_type_needs_update = generic_org_type_changed or is_election_board_changed
- generic_org_type_needs_update = organization_type_changed
-
- # Update the field
- if organization_type_needs_update:
- _update_org_type_from_generic_org_and_election(instance, generic_org_to_org_map)
- elif generic_org_type_needs_update:
- _update_generic_org_and_election_from_org_type(
- instance, election_org_to_generic_org_map, generic_org_to_org_map
- )
-
-
-def _update_org_type_from_generic_org_and_election(instance, org_map):
- """Given a field values for generic_org_type and is_election_board, update the
- organization_type field."""
-
- # We convert to a string because the enum types are different.
- generic_org_type = str(instance.generic_org_type)
- if generic_org_type not in org_map:
- # Election board should always be reset to None if the record
- # can't have one. For example, federal.
- if instance.is_election_board is not None:
- # This maintains data consistency.
- # There is no avenue for this to occur in the UI,
- # as such - this can only occur if the object is initialized in this way.
- # Or if there are pre-existing data.
- logger.warning(
- "create_or_update_organization_type() -> is_election_board "
- f"cannot exist for {generic_org_type}. Setting to None."
- )
- instance.is_election_board = None
- instance.organization_type = generic_org_type
- else:
- # This can only happen with manual data tinkering, which causes these to be out of sync.
- if instance.is_election_board is None:
- logger.warning("create_or_update_organization_type() -> is_election_board is out of sync. Updating value.")
- instance.is_election_board = False
-
- instance.organization_type = org_map[generic_org_type] if instance.is_election_board else generic_org_type
-
-
-def _update_generic_org_and_election_from_org_type(instance, election_org_map, generic_org_map):
- """Given the field value for organization_type, update the
- generic_org_type and is_election_board field."""
-
- # We convert to a string because the enum types are different
- # between OrgChoicesElectionOffice and OrganizationChoices.
- # But their names are the same (for the most part).
- current_org_type = str(instance.organization_type)
-
- # This essentially means: "_election" in current_org_type.
- if current_org_type in election_org_map:
- new_org = election_org_map[current_org_type]
- instance.generic_org_type = new_org
- instance.is_election_board = True
- else:
- instance.generic_org_type = current_org_type
-
- # This basically checks if the given org type
- # can even have an election board in the first place.
- # For instance, federal cannot so is_election_board = None
- if current_org_type in generic_org_map:
- instance.is_election_board = False
- else:
- # This maintains data consistency.
- # There is no avenue for this to occur in the UI,
- # as such - this can only occur if the object is initialized in this way.
- # Or if there are pre-existing data.
- logger.warning(
- "create_or_update_organization_type() -> is_election_board "
- f"cannot exist for {current_org_type}. Setting to None."
- )
- instance.is_election_board = None
-
-
-def _validate_new_instance(instance, election_org_to_generic_org_map, generic_org_to_org_map):
- """
- Validates whether a new instance of DomainRequest or DomainInformation can proceed with the update
- based on the consistency between organization_type, generic_org_type, and is_election_board.
-
- Returns a boolean determining if execution should proceed or not.
- """
-
- # We conditionally accept both of these values to exist simultaneously, as long as
- # those values do not intefere with eachother.
- # Because this condition can only be triggered through a dev (no user flow),
- # we throw an error if an invalid state is found here.
- if instance.organization_type and instance.generic_org_type:
- generic_org_type = str(instance.generic_org_type)
- organization_type = str(instance.organization_type)
-
- # Strip "_election" if it exists
- mapped_org_type = election_org_to_generic_org_map.get(organization_type)
-
- # Do tests on the org update for election board changes.
- is_election_type = "_election" in organization_type
- can_have_election_board = organization_type in generic_org_to_org_map
-
- election_board_mismatch = (is_election_type != instance.is_election_board) and can_have_election_board
- org_type_mismatch = mapped_org_type is not None and (generic_org_type != mapped_org_type)
- if election_board_mismatch or org_type_mismatch:
- message = (
- "Cannot add organization_type and generic_org_type simultaneously "
- "when generic_org_type, is_election_board, and organization_type values do not match."
- )
- raise ValueError(message)
-
- return True
- elif not instance.organization_type and not instance.generic_org_type:
- return False
- else:
- return True
-
-
@receiver(post_save, sender=User)
def handle_profile(sender, instance, **kwargs):
"""Method for when a User is saved.
From c26618bff155bca977b4cfbda5714509cb14baec Mon Sep 17 00:00:00 2001
From: zandercymatics <141044360+zandercymatics@users.noreply.github.com>
Date: Mon, 8 Apr 2024 13:20:05 -0600
Subject: [PATCH 82/96] Linting
---
src/registrar/models/utility/generic_helper.py | 18 ++++++++++--------
1 file changed, 10 insertions(+), 8 deletions(-)
diff --git a/src/registrar/models/utility/generic_helper.py b/src/registrar/models/utility/generic_helper.py
index fadca2b14..e332ce038 100644
--- a/src/registrar/models/utility/generic_helper.py
+++ b/src/registrar/models/utility/generic_helper.py
@@ -41,6 +41,7 @@ class CreateOrUpdateOrganizationTypeHelper:
"""
A helper that manages the "organization_type" field in DomainRequest and DomainInformation
"""
+
def __init__(self, sender, instance, generic_org_to_org_map, election_org_to_generic_org_map):
# The "model type"
self.sender = sender
@@ -67,7 +68,7 @@ class CreateOrUpdateOrganizationTypeHelper:
self._handle_new_instance()
else:
self._handle_existing_instance()
-
+
return self.instance
def _handle_new_instance(self):
@@ -87,7 +88,7 @@ class CreateOrUpdateOrganizationTypeHelper:
self._update_org_type_from_generic_org_and_election()
elif generic_org_type_needs_update:
self._update_generic_org_and_election_from_org_type()
-
+
# Update the field
self._update_fields(organization_type_needs_update, generic_org_type_needs_update)
@@ -123,7 +124,7 @@ class CreateOrUpdateOrganizationTypeHelper:
def _update_fields(self, organization_type_needs_update, generic_org_type_needs_update):
"""
Validates the conditions for updating organization and generic organization types.
-
+
Raises:
ValueError: If both organization_type_needs_update and generic_org_type_needs_update are True,
indicating an attempt to update both fields simultaneously, which is not allowed.
@@ -133,7 +134,7 @@ class CreateOrUpdateOrganizationTypeHelper:
# this restraint.
if organization_type_needs_update and generic_org_type_needs_update:
raise ValueError("Cannot update both org type and generic org type at the same time.")
-
+
if organization_type_needs_update:
self._update_org_type_from_generic_org_and_election()
elif generic_org_type_needs_update:
@@ -162,12 +163,14 @@ class CreateOrUpdateOrganizationTypeHelper:
else:
# This can only happen with manual data tinkering, which causes these to be out of sync.
if self.instance.is_election_board is None:
- logger.warning("create_or_update_organization_type() -> is_election_board is out of sync. Updating value.")
+ logger.warning(
+ "create_or_update_organization_type() -> is_election_board is out of sync. Updating value."
+ )
self.instance.is_election_board = False
if self.instance.is_election_board:
- self.instance.organization_type = self.generic_org_to_org_map[generic_org_type]
- else:
+ self.instance.organization_type = self.generic_org_to_org_map[generic_org_type]
+ else:
self.instance.organization_type = generic_org_type
def _update_generic_org_and_election_from_org_type(self):
@@ -242,4 +245,3 @@ class CreateOrUpdateOrganizationTypeHelper:
return False
else:
return True
-
From 60e370dfb7013ff9042db60d966ba04b96c06043 Mon Sep 17 00:00:00 2001
From: zandercymatics <141044360+zandercymatics@users.noreply.github.com>
Date: Mon, 8 Apr 2024 13:23:48 -0600
Subject: [PATCH 83/96] Update signals.py
---
src/registrar/signals.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/registrar/signals.py b/src/registrar/signals.py
index 67d007670..4e7768ef4 100644
--- a/src/registrar/signals.py
+++ b/src/registrar/signals.py
@@ -1,9 +1,9 @@
import logging
-from django.db.models.signals import pre_save, post_save
+from django.db.models.signals import post_save
from django.dispatch import receiver
-from .models import User, Contact, DomainRequest, DomainInformation
+from .models import User, Contact
logger = logging.getLogger(__name__)
From bf6a70e9fb7ee808d6edb80c9eba538f6731c769 Mon Sep 17 00:00:00 2001
From: Rachid Mrad
Date: Mon, 8 Apr 2024 15:31:42 -0400
Subject: [PATCH 84/96] comment
---
src/registrar/admin.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/registrar/admin.py b/src/registrar/admin.py
index 9673f7df4..18bc33db6 100644
--- a/src/registrar/admin.py
+++ b/src/registrar/admin.py
@@ -1192,6 +1192,8 @@ class DomainRequestAdmin(ListHeaderAdmin):
filter_horizontal = ("current_websites", "alternative_domains", "other_contacts")
# Table ordering
+ # NOTE: This impacts the select2 dropdowns (combobox)
+ # Currentl, there's only one for requests on DomainInfo
ordering = ["-submission_date", "requested_domain__name"]
change_form_template = "django/admin/domain_request_change_form.html"
From f73fe9f0421393a014117c6809b42b19e50f292d Mon Sep 17 00:00:00 2001
From: zandercymatics <141044360+zandercymatics@users.noreply.github.com>
Date: Mon, 8 Apr 2024 13:41:46 -0600
Subject: [PATCH 85/96] Move test cases
---
src/registrar/tests/test_models.py | 306 ++++++++++++++++++++++++++++
src/registrar/tests/test_signals.py | 306 ----------------------------
2 files changed, 306 insertions(+), 306 deletions(-)
diff --git a/src/registrar/tests/test_models.py b/src/registrar/tests/test_models.py
index c7fe5f94c..d535c9370 100644
--- a/src/registrar/tests/test_models.py
+++ b/src/registrar/tests/test_models.py
@@ -1161,3 +1161,309 @@ class TestContact(TestCase):
# test for a contact which is assigned as an authorizing official on a domain request
self.assertFalse(self.contact_as_ao.has_more_than_one_join("authorizing_official"))
self.assertTrue(self.contact_as_ao.has_more_than_one_join("submitted_domain_requests"))
+
+
+class TestDomainRequestCustomSave(TestCase):
+ """Tests custom save behaviour on the DomainRequest object"""
+
+ def tearDown(self):
+ DomainRequest.objects.all().delete()
+ super().tearDown()
+
+ def test_create_or_update_organization_type_new_instance(self):
+ """Test create_or_update_organization_type when creating a new instance"""
+ domain_request = completed_domain_request(
+ status=DomainRequest.DomainRequestStatus.STARTED,
+ name="started.gov",
+ generic_org_type=DomainRequest.OrganizationChoices.CITY,
+ is_election_board=True,
+ )
+
+ self.assertEqual(domain_request.organization_type, DomainRequest.OrgChoicesElectionOffice.CITY_ELECTION)
+
+ def test_create_or_update_organization_type_new_instance_federal_does_nothing(self):
+ """Test if create_or_update_organization_type does nothing when creating a new instance for federal"""
+ domain_request = completed_domain_request(
+ status=DomainRequest.DomainRequestStatus.STARTED,
+ name="started.gov",
+ generic_org_type=DomainRequest.OrganizationChoices.FEDERAL,
+ is_election_board=True,
+ )
+ self.assertEqual(domain_request.organization_type, DomainRequest.OrgChoicesElectionOffice.FEDERAL)
+ self.assertEqual(domain_request.is_election_board, None)
+
+ def test_create_or_update_organization_type_existing_instance_updates_election_board(self):
+ """Test create_or_update_organization_type for an existing instance."""
+ domain_request = completed_domain_request(
+ status=DomainRequest.DomainRequestStatus.STARTED,
+ name="started.gov",
+ generic_org_type=DomainRequest.OrganizationChoices.CITY,
+ is_election_board=False,
+ )
+ domain_request.is_election_board = True
+ domain_request.save()
+
+ self.assertEqual(domain_request.is_election_board, True)
+ self.assertEqual(domain_request.organization_type, DomainRequest.OrgChoicesElectionOffice.CITY_ELECTION)
+
+ # Try reverting the election board value
+ domain_request.is_election_board = False
+ domain_request.save()
+
+ self.assertEqual(domain_request.is_election_board, False)
+ self.assertEqual(domain_request.organization_type, DomainRequest.OrgChoicesElectionOffice.CITY)
+
+ # Try reverting setting an invalid value for election board (should revert to False)
+ domain_request.is_election_board = None
+ domain_request.save()
+
+ self.assertEqual(domain_request.is_election_board, False)
+ self.assertEqual(domain_request.organization_type, DomainRequest.OrgChoicesElectionOffice.CITY)
+
+ def test_create_or_update_organization_type_existing_instance_updates_generic_org_type(self):
+ """Test create_or_update_organization_type when modifying generic_org_type on an existing instance."""
+ domain_request = completed_domain_request(
+ status=DomainRequest.DomainRequestStatus.STARTED,
+ name="started.gov",
+ generic_org_type=DomainRequest.OrganizationChoices.CITY,
+ is_election_board=True,
+ )
+
+ domain_request.generic_org_type = DomainRequest.OrganizationChoices.INTERSTATE
+ domain_request.save()
+
+ # Election board should be None because interstate cannot have an election board.
+ self.assertEqual(domain_request.is_election_board, None)
+ self.assertEqual(domain_request.organization_type, DomainRequest.OrgChoicesElectionOffice.INTERSTATE)
+
+ # Try changing the org Type to something that CAN have an election board.
+ domain_request_tribal = completed_domain_request(
+ status=DomainRequest.DomainRequestStatus.STARTED,
+ name="startedTribal.gov",
+ generic_org_type=DomainRequest.OrganizationChoices.TRIBAL,
+ is_election_board=True,
+ )
+ self.assertEqual(
+ domain_request_tribal.organization_type, DomainRequest.OrgChoicesElectionOffice.TRIBAL_ELECTION
+ )
+
+ # Change the org type
+ domain_request_tribal.generic_org_type = DomainRequest.OrganizationChoices.STATE_OR_TERRITORY
+ domain_request_tribal.save()
+
+ self.assertEqual(domain_request_tribal.is_election_board, True)
+ self.assertEqual(
+ domain_request_tribal.organization_type, DomainRequest.OrgChoicesElectionOffice.STATE_OR_TERRITORY_ELECTION
+ )
+
+ def test_create_or_update_organization_type_no_update(self):
+ """Test create_or_update_organization_type when there are no values to update."""
+
+ # Test for when both generic_org_type and organization_type is declared,
+ # and are both non-election board
+ domain_request = completed_domain_request(
+ status=DomainRequest.DomainRequestStatus.STARTED,
+ name="started.gov",
+ generic_org_type=DomainRequest.OrganizationChoices.CITY,
+ is_election_board=False,
+ )
+ domain_request.save()
+ self.assertEqual(domain_request.organization_type, DomainRequest.OrgChoicesElectionOffice.CITY)
+ self.assertEqual(domain_request.is_election_board, False)
+ self.assertEqual(domain_request.generic_org_type, DomainRequest.OrganizationChoices.CITY)
+
+ # Test for when both generic_org_type and organization_type is declared,
+ # and are both election board
+ domain_request_election = completed_domain_request(
+ status=DomainRequest.DomainRequestStatus.STARTED,
+ name="startedElection.gov",
+ generic_org_type=DomainRequest.OrganizationChoices.CITY,
+ is_election_board=True,
+ organization_type=DomainRequest.OrgChoicesElectionOffice.CITY_ELECTION,
+ )
+
+ self.assertEqual(
+ domain_request_election.organization_type, DomainRequest.OrgChoicesElectionOffice.CITY_ELECTION
+ )
+ self.assertEqual(domain_request_election.is_election_board, True)
+ self.assertEqual(domain_request_election.generic_org_type, DomainRequest.OrganizationChoices.CITY)
+
+ # Modify an unrelated existing value for both, and ensure that everything is still consistent
+ domain_request.city = "Fudge"
+ domain_request_election.city = "Caramel"
+ domain_request.save()
+ domain_request_election.save()
+
+ self.assertEqual(domain_request.city, "Fudge")
+ self.assertEqual(domain_request_election.city, "Caramel")
+
+ # Test for non-election
+ self.assertEqual(domain_request.organization_type, DomainRequest.OrgChoicesElectionOffice.CITY)
+ self.assertEqual(domain_request.is_election_board, False)
+ self.assertEqual(domain_request.generic_org_type, DomainRequest.OrganizationChoices.CITY)
+
+ # Test for election
+ self.assertEqual(
+ domain_request_election.organization_type, DomainRequest.OrgChoicesElectionOffice.CITY_ELECTION
+ )
+ self.assertEqual(domain_request_election.is_election_board, True)
+ self.assertEqual(domain_request_election.generic_org_type, DomainRequest.OrganizationChoices.CITY)
+
+
+class TestDomainInformationCustomSave(TestCase):
+ """Tests custom save behaviour on the DomainInformation object"""
+
+ def tearDown(self):
+ DomainInformation.objects.all().delete()
+ DomainRequest.objects.all().delete()
+ Domain.objects.all().delete()
+ super().tearDown()
+
+ def test_create_or_update_organization_type_new_instance(self):
+ """Test create_or_update_organization_type when creating a new instance"""
+ domain_request = completed_domain_request(
+ status=DomainRequest.DomainRequestStatus.STARTED,
+ name="started.gov",
+ generic_org_type=DomainRequest.OrganizationChoices.CITY,
+ is_election_board=True,
+ )
+
+ domain_information = DomainInformation.create_from_da(domain_request)
+ self.assertEqual(domain_information.organization_type, DomainRequest.OrgChoicesElectionOffice.CITY_ELECTION)
+
+ def test_create_or_update_organization_type_new_instance_federal_does_nothing(self):
+ """Test if create_or_update_organization_type does nothing when creating a new instance for federal"""
+ domain_request = completed_domain_request(
+ status=DomainRequest.DomainRequestStatus.STARTED,
+ name="started.gov",
+ generic_org_type=DomainRequest.OrganizationChoices.FEDERAL,
+ is_election_board=True,
+ )
+
+ domain_information = DomainInformation.create_from_da(domain_request)
+ self.assertEqual(domain_information.organization_type, DomainRequest.OrgChoicesElectionOffice.FEDERAL)
+ self.assertEqual(domain_information.is_election_board, None)
+
+ def test_create_or_update_organization_type_existing_instance_updates_election_board(self):
+ """Test create_or_update_organization_type for an existing instance."""
+ domain_request = completed_domain_request(
+ status=DomainRequest.DomainRequestStatus.STARTED,
+ name="started.gov",
+ generic_org_type=DomainRequest.OrganizationChoices.CITY,
+ is_election_board=False,
+ )
+ domain_information = DomainInformation.create_from_da(domain_request)
+ domain_information.is_election_board = True
+ domain_information.save()
+
+ self.assertEqual(domain_information.is_election_board, True)
+ self.assertEqual(domain_information.organization_type, DomainRequest.OrgChoicesElectionOffice.CITY_ELECTION)
+
+ # Try reverting the election board value
+ domain_information.is_election_board = False
+ domain_information.save()
+ domain_information.refresh_from_db()
+
+ self.assertEqual(domain_information.is_election_board, False)
+ self.assertEqual(domain_information.organization_type, DomainRequest.OrgChoicesElectionOffice.CITY)
+
+ # Try reverting setting an invalid value for election board (should revert to False)
+ domain_information.is_election_board = None
+ domain_information.save()
+
+ self.assertEqual(domain_information.is_election_board, False)
+ self.assertEqual(domain_information.organization_type, DomainRequest.OrgChoicesElectionOffice.CITY)
+
+ def test_create_or_update_organization_type_existing_instance_updates_generic_org_type(self):
+ """Test create_or_update_organization_type when modifying generic_org_type on an existing instance."""
+ domain_request = completed_domain_request(
+ status=DomainRequest.DomainRequestStatus.STARTED,
+ name="started.gov",
+ generic_org_type=DomainRequest.OrganizationChoices.CITY,
+ is_election_board=True,
+ )
+ domain_information = DomainInformation.create_from_da(domain_request)
+
+ domain_information.generic_org_type = DomainRequest.OrganizationChoices.INTERSTATE
+ domain_information.save()
+
+ # Election board should be None because interstate cannot have an election board.
+ self.assertEqual(domain_information.is_election_board, None)
+ self.assertEqual(domain_information.organization_type, DomainRequest.OrgChoicesElectionOffice.INTERSTATE)
+
+ # Try changing the org Type to something that CAN have an election board.
+ domain_request_tribal = completed_domain_request(
+ status=DomainRequest.DomainRequestStatus.STARTED,
+ name="startedTribal.gov",
+ generic_org_type=DomainRequest.OrganizationChoices.TRIBAL,
+ is_election_board=True,
+ )
+ domain_information_tribal = DomainInformation.create_from_da(domain_request_tribal)
+ self.assertEqual(
+ domain_information_tribal.organization_type, DomainRequest.OrgChoicesElectionOffice.TRIBAL_ELECTION
+ )
+
+ # Change the org type
+ domain_information_tribal.generic_org_type = DomainRequest.OrganizationChoices.STATE_OR_TERRITORY
+ domain_information_tribal.save()
+
+ self.assertEqual(domain_information_tribal.is_election_board, True)
+ self.assertEqual(
+ domain_information_tribal.organization_type,
+ DomainRequest.OrgChoicesElectionOffice.STATE_OR_TERRITORY_ELECTION,
+ )
+
+ def test_create_or_update_organization_type_no_update(self):
+ """Test create_or_update_organization_type when there are no values to update."""
+
+ # Test for when both generic_org_type and organization_type is declared,
+ # and are both non-election board
+ domain_request = completed_domain_request(
+ status=DomainRequest.DomainRequestStatus.STARTED,
+ name="started.gov",
+ generic_org_type=DomainRequest.OrganizationChoices.CITY,
+ is_election_board=False,
+ )
+ domain_information = DomainInformation.create_from_da(domain_request)
+ domain_information.save()
+ self.assertEqual(domain_information.organization_type, DomainRequest.OrgChoicesElectionOffice.CITY)
+ self.assertEqual(domain_information.is_election_board, False)
+ self.assertEqual(domain_information.generic_org_type, DomainRequest.OrganizationChoices.CITY)
+
+ # Test for when both generic_org_type and organization_type is declared,
+ # and are both election board
+ domain_request_election = completed_domain_request(
+ status=DomainRequest.DomainRequestStatus.STARTED,
+ name="startedElection.gov",
+ generic_org_type=DomainRequest.OrganizationChoices.CITY,
+ is_election_board=True,
+ organization_type=DomainRequest.OrgChoicesElectionOffice.CITY_ELECTION,
+ )
+ domain_information_election = DomainInformation.create_from_da(domain_request_election)
+
+ self.assertEqual(
+ domain_information_election.organization_type, DomainRequest.OrgChoicesElectionOffice.CITY_ELECTION
+ )
+ self.assertEqual(domain_information_election.is_election_board, True)
+ self.assertEqual(domain_information_election.generic_org_type, DomainRequest.OrganizationChoices.CITY)
+
+ # Modify an unrelated existing value for both, and ensure that everything is still consistent
+ domain_information.city = "Fudge"
+ domain_information_election.city = "Caramel"
+ domain_information.save()
+ domain_information_election.save()
+
+ self.assertEqual(domain_information.city, "Fudge")
+ self.assertEqual(domain_information_election.city, "Caramel")
+
+ # Test for non-election
+ self.assertEqual(domain_information.organization_type, DomainRequest.OrgChoicesElectionOffice.CITY)
+ self.assertEqual(domain_information.is_election_board, False)
+ self.assertEqual(domain_information.generic_org_type, DomainRequest.OrganizationChoices.CITY)
+
+ # Test for election
+ self.assertEqual(
+ domain_information_election.organization_type, DomainRequest.OrgChoicesElectionOffice.CITY_ELECTION
+ )
+ self.assertEqual(domain_information_election.is_election_board, True)
+ self.assertEqual(domain_information_election.generic_org_type, DomainRequest.OrganizationChoices.CITY)
diff --git a/src/registrar/tests/test_signals.py b/src/registrar/tests/test_signals.py
index e950f39fb..7af6012a9 100644
--- a/src/registrar/tests/test_signals.py
+++ b/src/registrar/tests/test_signals.py
@@ -99,309 +99,3 @@ class TestUserPostSave(TestCase):
self.assertEqual(actual.last_name, self.last_name)
self.assertEqual(actual.email, self.email)
self.assertEqual(actual.phone, self.phone)
-
-
-class TestDomainRequestSignals(TestCase):
- """Tests hooked signals on the DomainRequest object"""
-
- def tearDown(self):
- DomainRequest.objects.all().delete()
- super().tearDown()
-
- def test_create_or_update_organization_type_new_instance(self):
- """Test create_or_update_organization_type when creating a new instance"""
- domain_request = completed_domain_request(
- status=DomainRequest.DomainRequestStatus.STARTED,
- name="started.gov",
- generic_org_type=DomainRequest.OrganizationChoices.CITY,
- is_election_board=True,
- )
-
- self.assertEqual(domain_request.organization_type, DomainRequest.OrgChoicesElectionOffice.CITY_ELECTION)
-
- def test_create_or_update_organization_type_new_instance_federal_does_nothing(self):
- """Test if create_or_update_organization_type does nothing when creating a new instance for federal"""
- domain_request = completed_domain_request(
- status=DomainRequest.DomainRequestStatus.STARTED,
- name="started.gov",
- generic_org_type=DomainRequest.OrganizationChoices.FEDERAL,
- is_election_board=True,
- )
- self.assertEqual(domain_request.organization_type, DomainRequest.OrgChoicesElectionOffice.FEDERAL)
- self.assertEqual(domain_request.is_election_board, None)
-
- def test_create_or_update_organization_type_existing_instance_updates_election_board(self):
- """Test create_or_update_organization_type for an existing instance."""
- domain_request = completed_domain_request(
- status=DomainRequest.DomainRequestStatus.STARTED,
- name="started.gov",
- generic_org_type=DomainRequest.OrganizationChoices.CITY,
- is_election_board=False,
- )
- domain_request.is_election_board = True
- domain_request.save()
-
- self.assertEqual(domain_request.is_election_board, True)
- self.assertEqual(domain_request.organization_type, DomainRequest.OrgChoicesElectionOffice.CITY_ELECTION)
-
- # Try reverting the election board value
- domain_request.is_election_board = False
- domain_request.save()
-
- self.assertEqual(domain_request.is_election_board, False)
- self.assertEqual(domain_request.organization_type, DomainRequest.OrgChoicesElectionOffice.CITY)
-
- # Try reverting setting an invalid value for election board (should revert to False)
- domain_request.is_election_board = None
- domain_request.save()
-
- self.assertEqual(domain_request.is_election_board, False)
- self.assertEqual(domain_request.organization_type, DomainRequest.OrgChoicesElectionOffice.CITY)
-
- def test_create_or_update_organization_type_existing_instance_updates_generic_org_type(self):
- """Test create_or_update_organization_type when modifying generic_org_type on an existing instance."""
- domain_request = completed_domain_request(
- status=DomainRequest.DomainRequestStatus.STARTED,
- name="started.gov",
- generic_org_type=DomainRequest.OrganizationChoices.CITY,
- is_election_board=True,
- )
-
- domain_request.generic_org_type = DomainRequest.OrganizationChoices.INTERSTATE
- domain_request.save()
-
- # Election board should be None because interstate cannot have an election board.
- self.assertEqual(domain_request.is_election_board, None)
- self.assertEqual(domain_request.organization_type, DomainRequest.OrgChoicesElectionOffice.INTERSTATE)
-
- # Try changing the org Type to something that CAN have an election board.
- domain_request_tribal = completed_domain_request(
- status=DomainRequest.DomainRequestStatus.STARTED,
- name="startedTribal.gov",
- generic_org_type=DomainRequest.OrganizationChoices.TRIBAL,
- is_election_board=True,
- )
- self.assertEqual(
- domain_request_tribal.organization_type, DomainRequest.OrgChoicesElectionOffice.TRIBAL_ELECTION
- )
-
- # Change the org type
- domain_request_tribal.generic_org_type = DomainRequest.OrganizationChoices.STATE_OR_TERRITORY
- domain_request_tribal.save()
-
- self.assertEqual(domain_request_tribal.is_election_board, True)
- self.assertEqual(
- domain_request_tribal.organization_type, DomainRequest.OrgChoicesElectionOffice.STATE_OR_TERRITORY_ELECTION
- )
-
- def test_create_or_update_organization_type_no_update(self):
- """Test create_or_update_organization_type when there are no values to update."""
-
- # Test for when both generic_org_type and organization_type is declared,
- # and are both non-election board
- domain_request = completed_domain_request(
- status=DomainRequest.DomainRequestStatus.STARTED,
- name="started.gov",
- generic_org_type=DomainRequest.OrganizationChoices.CITY,
- is_election_board=False,
- )
- domain_request.save()
- self.assertEqual(domain_request.organization_type, DomainRequest.OrgChoicesElectionOffice.CITY)
- self.assertEqual(domain_request.is_election_board, False)
- self.assertEqual(domain_request.generic_org_type, DomainRequest.OrganizationChoices.CITY)
-
- # Test for when both generic_org_type and organization_type is declared,
- # and are both election board
- domain_request_election = completed_domain_request(
- status=DomainRequest.DomainRequestStatus.STARTED,
- name="startedElection.gov",
- generic_org_type=DomainRequest.OrganizationChoices.CITY,
- is_election_board=True,
- organization_type=DomainRequest.OrgChoicesElectionOffice.CITY_ELECTION,
- )
-
- self.assertEqual(
- domain_request_election.organization_type, DomainRequest.OrgChoicesElectionOffice.CITY_ELECTION
- )
- self.assertEqual(domain_request_election.is_election_board, True)
- self.assertEqual(domain_request_election.generic_org_type, DomainRequest.OrganizationChoices.CITY)
-
- # Modify an unrelated existing value for both, and ensure that everything is still consistent
- domain_request.city = "Fudge"
- domain_request_election.city = "Caramel"
- domain_request.save()
- domain_request_election.save()
-
- self.assertEqual(domain_request.city, "Fudge")
- self.assertEqual(domain_request_election.city, "Caramel")
-
- # Test for non-election
- self.assertEqual(domain_request.organization_type, DomainRequest.OrgChoicesElectionOffice.CITY)
- self.assertEqual(domain_request.is_election_board, False)
- self.assertEqual(domain_request.generic_org_type, DomainRequest.OrganizationChoices.CITY)
-
- # Test for election
- self.assertEqual(
- domain_request_election.organization_type, DomainRequest.OrgChoicesElectionOffice.CITY_ELECTION
- )
- self.assertEqual(domain_request_election.is_election_board, True)
- self.assertEqual(domain_request_election.generic_org_type, DomainRequest.OrganizationChoices.CITY)
-
-
-class TestDomainInformationSignals(TestCase):
- """Tests hooked signals on the DomainRequest object"""
-
- def tearDown(self):
- DomainInformation.objects.all().delete()
- DomainRequest.objects.all().delete()
- Domain.objects.all().delete()
- super().tearDown()
-
- def test_create_or_update_organization_type_new_instance(self):
- """Test create_or_update_organization_type when creating a new instance"""
- domain_request = completed_domain_request(
- status=DomainRequest.DomainRequestStatus.STARTED,
- name="started.gov",
- generic_org_type=DomainRequest.OrganizationChoices.CITY,
- is_election_board=True,
- )
-
- domain_information = DomainInformation.create_from_da(domain_request)
- self.assertEqual(domain_information.organization_type, DomainRequest.OrgChoicesElectionOffice.CITY_ELECTION)
-
- def test_create_or_update_organization_type_new_instance_federal_does_nothing(self):
- """Test if create_or_update_organization_type does nothing when creating a new instance for federal"""
- domain_request = completed_domain_request(
- status=DomainRequest.DomainRequestStatus.STARTED,
- name="started.gov",
- generic_org_type=DomainRequest.OrganizationChoices.FEDERAL,
- is_election_board=True,
- )
-
- domain_information = DomainInformation.create_from_da(domain_request)
- self.assertEqual(domain_information.organization_type, DomainRequest.OrgChoicesElectionOffice.FEDERAL)
- self.assertEqual(domain_information.is_election_board, None)
-
- def test_create_or_update_organization_type_existing_instance_updates_election_board(self):
- """Test create_or_update_organization_type for an existing instance."""
- domain_request = completed_domain_request(
- status=DomainRequest.DomainRequestStatus.STARTED,
- name="started.gov",
- generic_org_type=DomainRequest.OrganizationChoices.CITY,
- is_election_board=False,
- )
- domain_information = DomainInformation.create_from_da(domain_request)
- domain_information.is_election_board = True
- domain_information.save()
-
- self.assertEqual(domain_information.is_election_board, True)
- self.assertEqual(domain_information.organization_type, DomainRequest.OrgChoicesElectionOffice.CITY_ELECTION)
-
- # Try reverting the election board value
- domain_information.is_election_board = False
- domain_information.save()
- domain_information.refresh_from_db()
-
- self.assertEqual(domain_information.is_election_board, False)
- self.assertEqual(domain_information.organization_type, DomainRequest.OrgChoicesElectionOffice.CITY)
-
- # Try reverting setting an invalid value for election board (should revert to False)
- domain_information.is_election_board = None
- domain_information.save()
-
- self.assertEqual(domain_information.is_election_board, False)
- self.assertEqual(domain_information.organization_type, DomainRequest.OrgChoicesElectionOffice.CITY)
-
- def test_create_or_update_organization_type_existing_instance_updates_generic_org_type(self):
- """Test create_or_update_organization_type when modifying generic_org_type on an existing instance."""
- domain_request = completed_domain_request(
- status=DomainRequest.DomainRequestStatus.STARTED,
- name="started.gov",
- generic_org_type=DomainRequest.OrganizationChoices.CITY,
- is_election_board=True,
- )
- domain_information = DomainInformation.create_from_da(domain_request)
-
- domain_information.generic_org_type = DomainRequest.OrganizationChoices.INTERSTATE
- domain_information.save()
-
- # Election board should be None because interstate cannot have an election board.
- self.assertEqual(domain_information.is_election_board, None)
- self.assertEqual(domain_information.organization_type, DomainRequest.OrgChoicesElectionOffice.INTERSTATE)
-
- # Try changing the org Type to something that CAN have an election board.
- domain_request_tribal = completed_domain_request(
- status=DomainRequest.DomainRequestStatus.STARTED,
- name="startedTribal.gov",
- generic_org_type=DomainRequest.OrganizationChoices.TRIBAL,
- is_election_board=True,
- )
- domain_information_tribal = DomainInformation.create_from_da(domain_request_tribal)
- self.assertEqual(
- domain_information_tribal.organization_type, DomainRequest.OrgChoicesElectionOffice.TRIBAL_ELECTION
- )
-
- # Change the org type
- domain_information_tribal.generic_org_type = DomainRequest.OrganizationChoices.STATE_OR_TERRITORY
- domain_information_tribal.save()
-
- self.assertEqual(domain_information_tribal.is_election_board, True)
- self.assertEqual(
- domain_information_tribal.organization_type,
- DomainRequest.OrgChoicesElectionOffice.STATE_OR_TERRITORY_ELECTION,
- )
-
- def test_create_or_update_organization_type_no_update(self):
- """Test create_or_update_organization_type when there are no values to update."""
-
- # Test for when both generic_org_type and organization_type is declared,
- # and are both non-election board
- domain_request = completed_domain_request(
- status=DomainRequest.DomainRequestStatus.STARTED,
- name="started.gov",
- generic_org_type=DomainRequest.OrganizationChoices.CITY,
- is_election_board=False,
- )
- domain_information = DomainInformation.create_from_da(domain_request)
- domain_information.save()
- self.assertEqual(domain_information.organization_type, DomainRequest.OrgChoicesElectionOffice.CITY)
- self.assertEqual(domain_information.is_election_board, False)
- self.assertEqual(domain_information.generic_org_type, DomainRequest.OrganizationChoices.CITY)
-
- # Test for when both generic_org_type and organization_type is declared,
- # and are both election board
- domain_request_election = completed_domain_request(
- status=DomainRequest.DomainRequestStatus.STARTED,
- name="startedElection.gov",
- generic_org_type=DomainRequest.OrganizationChoices.CITY,
- is_election_board=True,
- organization_type=DomainRequest.OrgChoicesElectionOffice.CITY_ELECTION,
- )
- domain_information_election = DomainInformation.create_from_da(domain_request_election)
-
- self.assertEqual(
- domain_information_election.organization_type, DomainRequest.OrgChoicesElectionOffice.CITY_ELECTION
- )
- self.assertEqual(domain_information_election.is_election_board, True)
- self.assertEqual(domain_information_election.generic_org_type, DomainRequest.OrganizationChoices.CITY)
-
- # Modify an unrelated existing value for both, and ensure that everything is still consistent
- domain_information.city = "Fudge"
- domain_information_election.city = "Caramel"
- domain_information.save()
- domain_information_election.save()
-
- self.assertEqual(domain_information.city, "Fudge")
- self.assertEqual(domain_information_election.city, "Caramel")
-
- # Test for non-election
- self.assertEqual(domain_information.organization_type, DomainRequest.OrgChoicesElectionOffice.CITY)
- self.assertEqual(domain_information.is_election_board, False)
- self.assertEqual(domain_information.generic_org_type, DomainRequest.OrganizationChoices.CITY)
-
- # Test for election
- self.assertEqual(
- domain_information_election.organization_type, DomainRequest.OrgChoicesElectionOffice.CITY_ELECTION
- )
- self.assertEqual(domain_information_election.is_election_board, True)
- self.assertEqual(domain_information_election.generic_org_type, DomainRequest.OrganizationChoices.CITY)
From 94e62e7520776199bd22cb3dc3c89a35df862124 Mon Sep 17 00:00:00 2001
From: Erin <121973038+erinysong@users.noreply.github.com>
Date: Mon, 8 Apr 2024 14:48:04 -0700
Subject: [PATCH 86/96] Add suggested content detail to user permissions docs
---
docs/developer/user-permissions.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/developer/user-permissions.md b/docs/developer/user-permissions.md
index 4e627b0a5..4919c02ff 100644
--- a/docs/developer/user-permissions.md
+++ b/docs/developer/user-permissions.md
@@ -26,8 +26,8 @@ and Permission models, which requires us to manually make a migration
file for user permission changes.
To update analyst permissions do the following:
1. Make desired changes to analyst group permissions in user_group.py.
-2. Follow the steps in 0037_create_groups_v01.py to create a duplicate
-migration for the updated user group permissions.
+2. Follow the steps in the migration file0037_create_groups_v01.py to
+create a duplicate migration for the updated user group permissions.
3. To migrate locally, run docker-compose up. To migrate on a sandbox,
push the new migration onto your sandbox before migrating.
From 9a87d24d6e955905f18271d1036adee9b93e1c2e Mon Sep 17 00:00:00 2001
From: zandercymatics <141044360+zandercymatics@users.noreply.github.com>
Date: Mon, 8 Apr 2024 20:34:53 -0600
Subject: [PATCH 87/96] Update generic_helper.py
---
src/registrar/models/utility/generic_helper.py | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/src/registrar/models/utility/generic_helper.py b/src/registrar/models/utility/generic_helper.py
index e332ce038..77a9fd45e 100644
--- a/src/registrar/models/utility/generic_helper.py
+++ b/src/registrar/models/utility/generic_helper.py
@@ -189,7 +189,7 @@ class CreateOrUpdateOrganizationTypeHelper:
new_org = election_org_map[current_org_type]
self.instance.generic_org_type = new_org
self.instance.is_election_board = True
- else:
+ elif self.instance.generic_org_type is not None:
self.instance.generic_org_type = current_org_type
# This basically checks if the given org type
@@ -207,6 +207,12 @@ class CreateOrUpdateOrganizationTypeHelper:
f"cannot exist for {current_org_type}. Setting to None."
)
self.instance.is_election_board = None
+ else:
+ # if self.instance.organization_type is set to None, then this means
+ # we should clear the related fields.
+ # This will not occur if it just is None (i.e. default), only if it is set to be so.
+ self.instance.is_election_board = None
+ self.instance.generic_org_type = None
def _validate_new_instance(self):
"""
From 06b34f8ed865d6723557b3887b31cd61968f118c Mon Sep 17 00:00:00 2001
From: zandercymatics <141044360+zandercymatics@users.noreply.github.com>
Date: Mon, 8 Apr 2024 20:38:08 -0600
Subject: [PATCH 88/96] Update generic_helper.py
---
src/registrar/models/utility/generic_helper.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/registrar/models/utility/generic_helper.py b/src/registrar/models/utility/generic_helper.py
index 77a9fd45e..32f767ede 100644
--- a/src/registrar/models/utility/generic_helper.py
+++ b/src/registrar/models/utility/generic_helper.py
@@ -189,7 +189,7 @@ class CreateOrUpdateOrganizationTypeHelper:
new_org = election_org_map[current_org_type]
self.instance.generic_org_type = new_org
self.instance.is_election_board = True
- elif self.instance.generic_org_type is not None:
+ elif self.instance.organization_type is not None:
self.instance.generic_org_type = current_org_type
# This basically checks if the given org type
From d27ab1634623ebea36bad37e35ced26ab69d9448 Mon Sep 17 00:00:00 2001
From: zandercymatics <141044360+zandercymatics@users.noreply.github.com>
Date: Tue, 9 Apr 2024 08:08:46 -0600
Subject: [PATCH 89/96] Linting
---
src/registrar/tests/test_signals.py | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/src/registrar/tests/test_signals.py b/src/registrar/tests/test_signals.py
index 7af6012a9..e796bd12a 100644
--- a/src/registrar/tests/test_signals.py
+++ b/src/registrar/tests/test_signals.py
@@ -1,7 +1,6 @@
from django.test import TestCase
from django.contrib.auth import get_user_model
-from registrar.models import Contact, DomainRequest, Domain, DomainInformation
-from registrar.tests.common import completed_domain_request
+from registrar.models import Contact
class TestUserPostSave(TestCase):
From 2b2d71847b741297521fa20c75ba37b160f6d374 Mon Sep 17 00:00:00 2001
From: zandercymatics <141044360+zandercymatics@users.noreply.github.com>
Date: Tue, 9 Apr 2024 08:17:09 -0600
Subject: [PATCH 90/96] Update domain.py
---
src/registrar/forms/domain.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/registrar/forms/domain.py b/src/registrar/forms/domain.py
index ee1362415..7b0ac2956 100644
--- a/src/registrar/forms/domain.py
+++ b/src/registrar/forms/domain.py
@@ -182,6 +182,7 @@ class ContactForm(forms.ModelForm):
"""Form for updating contacts."""
email = forms.EmailField(max_length=None)
+
class Meta:
model = Contact
fields = ["first_name", "middle_name", "last_name", "title", "email", "phone"]
From 7443703ddad64b68b968fe82e6fbcd3c711744b6 Mon Sep 17 00:00:00 2001
From: zandercymatics <141044360+zandercymatics@users.noreply.github.com>
Date: Tue, 9 Apr 2024 08:44:44 -0600
Subject: [PATCH 91/96] Fix max length
---
.../migrations/0082_alter_contact_email.py | 18 ++++++++++++++++++
src/registrar/models/contact.py | 1 +
2 files changed, 19 insertions(+)
create mode 100644 src/registrar/migrations/0082_alter_contact_email.py
diff --git a/src/registrar/migrations/0082_alter_contact_email.py b/src/registrar/migrations/0082_alter_contact_email.py
new file mode 100644
index 000000000..866fbc266
--- /dev/null
+++ b/src/registrar/migrations/0082_alter_contact_email.py
@@ -0,0 +1,18 @@
+# Generated by Django 4.2.10 on 2024-04-09 14:44
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("registrar", "0081_create_groups_v10"),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name="contact",
+ name="email",
+ field=models.EmailField(blank=True, db_index=True, max_length=320, null=True),
+ ),
+ ]
diff --git a/src/registrar/models/contact.py b/src/registrar/models/contact.py
index d3de5a293..6bc20ebeb 100644
--- a/src/registrar/models/contact.py
+++ b/src/registrar/models/contact.py
@@ -40,6 +40,7 @@ class Contact(TimeStampedModel):
null=True,
blank=True,
db_index=True,
+ max_length=320,
)
phone = PhoneNumberField(
null=True,
From 25e737821a213d9b04ca90e5182750dd93c4402d Mon Sep 17 00:00:00 2001
From: zandercymatics <141044360+zandercymatics@users.noreply.github.com>
Date: Tue, 9 Apr 2024 10:10:11 -0600
Subject: [PATCH 92/96] Update domain.py
---
src/registrar/forms/domain.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/registrar/forms/domain.py b/src/registrar/forms/domain.py
index 7b0ac2956..e2f9b4eb9 100644
--- a/src/registrar/forms/domain.py
+++ b/src/registrar/forms/domain.py
@@ -314,8 +314,8 @@ class DomainSecurityEmailForm(forms.Form):
},
validators=[
MaxLengthValidator(
- 320,
- message="Response must be less than 320 characters.",
+ 254,
+ message="Response must be less than 254 characters.",
)
],
)
From ec371f1844c8ed1deef7388656ff935bcbfd529e Mon Sep 17 00:00:00 2001
From: zandercymatics <141044360+zandercymatics@users.noreply.github.com>
Date: Tue, 9 Apr 2024 10:13:27 -0600
Subject: [PATCH 93/96] Fix max length
---
src/registrar/forms/domain.py | 4 ++--
.../migrations/0082_alter_contact_email.py | 18 ------------------
src/registrar/models/public_contact.py | 6 +++++-
3 files changed, 7 insertions(+), 21 deletions(-)
delete mode 100644 src/registrar/migrations/0082_alter_contact_email.py
diff --git a/src/registrar/forms/domain.py b/src/registrar/forms/domain.py
index e2f9b4eb9..7b0ac2956 100644
--- a/src/registrar/forms/domain.py
+++ b/src/registrar/forms/domain.py
@@ -314,8 +314,8 @@ class DomainSecurityEmailForm(forms.Form):
},
validators=[
MaxLengthValidator(
- 254,
- message="Response must be less than 254 characters.",
+ 320,
+ message="Response must be less than 320 characters.",
)
],
)
diff --git a/src/registrar/migrations/0082_alter_contact_email.py b/src/registrar/migrations/0082_alter_contact_email.py
deleted file mode 100644
index 866fbc266..000000000
--- a/src/registrar/migrations/0082_alter_contact_email.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# Generated by Django 4.2.10 on 2024-04-09 14:44
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ("registrar", "0081_create_groups_v10"),
- ]
-
- operations = [
- migrations.AlterField(
- model_name="contact",
- name="email",
- field=models.EmailField(blank=True, db_index=True, max_length=320, null=True),
- ),
- ]
diff --git a/src/registrar/models/public_contact.py b/src/registrar/models/public_contact.py
index f9dea3f02..42266bf4c 100644
--- a/src/registrar/models/public_contact.py
+++ b/src/registrar/models/public_contact.py
@@ -69,7 +69,11 @@ class PublicContact(TimeStampedModel):
pc = models.CharField(null=False, help_text="Contact's postal code")
cc = models.CharField(null=False, help_text="Contact's country code")
email = models.EmailField(null=False, help_text="Contact's email address")
- voice = models.CharField(null=False, help_text="Contact's phone number. Must be in ITU.E164.2005 format")
+ voice = models.CharField(
+ null=False,
+ help_text="Contact's phone number. Must be in ITU.E164.2005 format",
+ max_length=320,
+ )
fax = models.CharField(
null=True,
blank=True,
From 82c31616464fe1e72e5d6a40d124c489fe7de908 Mon Sep 17 00:00:00 2001
From: zandercymatics <141044360+zandercymatics@users.noreply.github.com>
Date: Tue, 9 Apr 2024 10:22:41 -0600
Subject: [PATCH 94/96] Typo
---
...contact_email_alter_publiccontact_email.py | 23 +++++++++++++++++++
src/registrar/models/public_contact.py | 8 +++----
2 files changed, 27 insertions(+), 4 deletions(-)
create mode 100644 src/registrar/migrations/0082_alter_contact_email_alter_publiccontact_email.py
diff --git a/src/registrar/migrations/0082_alter_contact_email_alter_publiccontact_email.py b/src/registrar/migrations/0082_alter_contact_email_alter_publiccontact_email.py
new file mode 100644
index 000000000..fb0740e9f
--- /dev/null
+++ b/src/registrar/migrations/0082_alter_contact_email_alter_publiccontact_email.py
@@ -0,0 +1,23 @@
+# Generated by Django 4.2.10 on 2024-04-09 16:22
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("registrar", "0081_create_groups_v10"),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name="contact",
+ name="email",
+ field=models.EmailField(blank=True, db_index=True, max_length=320, null=True),
+ ),
+ migrations.AlterField(
+ model_name="publiccontact",
+ name="email",
+ field=models.EmailField(help_text="Contact's email address", max_length=320),
+ ),
+ ]
diff --git a/src/registrar/models/public_contact.py b/src/registrar/models/public_contact.py
index 42266bf4c..6b91ea7cb 100644
--- a/src/registrar/models/public_contact.py
+++ b/src/registrar/models/public_contact.py
@@ -68,12 +68,12 @@ class PublicContact(TimeStampedModel):
sp = models.CharField(null=False, help_text="Contact's state or province")
pc = models.CharField(null=False, help_text="Contact's postal code")
cc = models.CharField(null=False, help_text="Contact's country code")
- email = models.EmailField(null=False, help_text="Contact's email address")
- voice = models.CharField(
+ email = models.EmailField(
null=False,
- help_text="Contact's phone number. Must be in ITU.E164.2005 format",
- max_length=320,
+ help_text="Contact's email address",
+ max_length=320
)
+ voice = models.CharField(null=False, help_text="Contact's phone number. Must be in ITU.E164.2005 format")
fax = models.CharField(
null=True,
blank=True,
From 940234b0c9b856cd5642cebd99469ac08ff121ab Mon Sep 17 00:00:00 2001
From: zandercymatics <141044360+zandercymatics@users.noreply.github.com>
Date: Tue, 9 Apr 2024 14:38:00 -0600
Subject: [PATCH 95/96] linting
---
src/registrar/models/public_contact.py | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
diff --git a/src/registrar/models/public_contact.py b/src/registrar/models/public_contact.py
index 6b91ea7cb..00d065404 100644
--- a/src/registrar/models/public_contact.py
+++ b/src/registrar/models/public_contact.py
@@ -68,11 +68,7 @@ class PublicContact(TimeStampedModel):
sp = models.CharField(null=False, help_text="Contact's state or province")
pc = models.CharField(null=False, help_text="Contact's postal code")
cc = models.CharField(null=False, help_text="Contact's country code")
- email = models.EmailField(
- null=False,
- help_text="Contact's email address",
- max_length=320
- )
+ email = models.EmailField(null=False, help_text="Contact's email address", max_length=320)
voice = models.CharField(null=False, help_text="Contact's phone number. Must be in ITU.E164.2005 format")
fax = models.CharField(
null=True,
From 23ac0d14924d42775b247bd18a05a68d83a376d1 Mon Sep 17 00:00:00 2001
From: zandercymatics <141044360+zandercymatics@users.noreply.github.com>
Date: Tue, 9 Apr 2024 14:43:52 -0600
Subject: [PATCH 96/96] Fix migration after merge
---
...py => 0083_alter_contact_email_alter_publiccontact_email.py} | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
rename src/registrar/migrations/{0082_alter_contact_email_alter_publiccontact_email.py => 0083_alter_contact_email_alter_publiccontact_email.py} (88%)
diff --git a/src/registrar/migrations/0082_alter_contact_email_alter_publiccontact_email.py b/src/registrar/migrations/0083_alter_contact_email_alter_publiccontact_email.py
similarity index 88%
rename from src/registrar/migrations/0082_alter_contact_email_alter_publiccontact_email.py
rename to src/registrar/migrations/0083_alter_contact_email_alter_publiccontact_email.py
index fb0740e9f..c58c09656 100644
--- a/src/registrar/migrations/0082_alter_contact_email_alter_publiccontact_email.py
+++ b/src/registrar/migrations/0083_alter_contact_email_alter_publiccontact_email.py
@@ -6,7 +6,7 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
- ("registrar", "0081_create_groups_v10"),
+ ("registrar", "0082_domaininformation_organization_type_and_more"),
]
operations = [