diff --git a/src/registrar/assets/sass/_theme/_admin.scss b/src/registrar/assets/sass/_theme/_admin.scss index 680c7cdf4..b4b590acb 100644 --- a/src/registrar/assets/sass/_theme/_admin.scss +++ b/src/registrar/assets/sass/_theme/_admin.scss @@ -112,12 +112,20 @@ html[data-theme="light"] { .change-list .usa-table--borderless thead th, .change-list .usa-table thead td, .change-list .usa-table thead th, + .change-form .usa-table, + .change-form .usa-table--striped tbody tr:nth-child(odd) td, + .change-form .usa-table--borderless thead th, + .change-form .usa-table thead td, + .change-form .usa-table thead th, body.dashboard, body.change-list, body.change-form, .analytics { color: var(--body-fg); } + .usa-table td { + background-color: transparent; + } } // Firefox needs this to be specifically set @@ -127,11 +135,20 @@ html[data-theme="dark"] { .change-list .usa-table--borderless thead th, .change-list .usa-table thead td, .change-list .usa-table thead th, + .change-form .usa-table, + .change-form .usa-table--striped tbody tr:nth-child(odd) td, + .change-form .usa-table--borderless thead th, + .change-form .usa-table thead td, + .change-form .usa-table thead th, body.dashboard, body.change-list, - body.change-form { + body.change-form, + .analytics { color: var(--body-fg); } + .usa-table td { + background-color: transparent; + } } #branding h1 a:link, #branding h1 a:visited { diff --git a/src/registrar/models/domain_request.py b/src/registrar/models/domain_request.py index 17bd9a100..75fbadc3e 100644 --- a/src/registrar/models/domain_request.py +++ b/src/registrar/models/domain_request.py @@ -16,12 +16,21 @@ from .utility.time_stamped_model import TimeStampedModel from ..utility.email import send_templated_email, EmailSendingError from itertools import chain +from auditlog.models import AuditlogHistoryField # type: ignore + logger = logging.getLogger(__name__) class DomainRequest(TimeStampedModel): """A registrant's domain request for a new domain.""" + # https://django-auditlog.readthedocs.io/en/latest/usage.html#object-history + # If we note any performace degradation due to this addition, + # we can query the auditlogs table in admin.py and add the results to + # extra_context in the change_view method for DomainRequestAdmin. + # This is the more straightforward way so trying it first. + history = AuditlogHistoryField() + # Constants for choice fields class DomainRequestStatus(models.TextChoices): STARTED = "started", "Started" 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 ea8e7579f..c7bb6325e 100644 --- a/src/registrar/templates/django/admin/includes/detail_table_fieldset.html +++ b/src/registrar/templates/django/admin/includes/detail_table_fieldset.html @@ -67,7 +67,35 @@ This is using a custom implementation fieldset.html (see admin/fieldset.html) {% endblock field_readonly %} {% block after_help_text %} - {% if field.field.name == "creator" %} + {% if field.field.name == "status" and original_object.history.count > 0 %} +
+ +
+ + + + + + + + + + {% for log_entry in original_object.history.all %} + {% for key, value in log_entry.changes_display_dict.items %} + {% if key == "status" %} + + + + + + {% endif %} + {% endfor %} + {% endfor %} + +
StatusUserChanged at
{{ value.1|default:"None" }}{{ log_entry.actor|default:"None" }}{{ log_entry.timestamp|default:"None" }}
+
+
+ {% elif field.field.name == "creator" %}
{% include "django/admin/includes/contact_detail_list.html" with user=original_object.creator no_title_top_padding=field.is_readonly user_verification_type=original_object.creator.get_verification_type_display%} diff --git a/src/registrar/tests/test_admin.py b/src/registrar/tests/test_admin.py index b42667e01..cf00994be 100644 --- a/src/registrar/tests/test_admin.py +++ b/src/registrar/tests/test_admin.py @@ -888,6 +888,96 @@ class TestDomainRequestAdmin(MockEppLib): self.test_helper.assert_response_contains_distinct_values(response, expected_values) @less_console_noise_decorator + def test_status_logs(self): + """ + Tests that the status changes are shown in a table on the domain request change form, + accurately and in chronological order. + """ + + # Create a fake domain request and domain + domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.STARTED) + + p = "adminpass" + self.client.login(username="superuser", password=p) + response = self.client.get( + "/admin/registrar/domainrequest/{}/change/".format(domain_request.pk), + follow=True, + ) + + # Make sure the page loaded, and that we're on the right page + self.assertEqual(response.status_code, 200) + self.assertContains(response, domain_request.requested_domain.name) + + # Table will contain one row for Started + self.assertContains(response, "Started", count=1) + self.assertNotContains(response, "Submitted") + + domain_request.submit() + domain_request.save() + + response = self.client.get( + "/admin/registrar/domainrequest/{}/change/".format(domain_request.pk), + follow=True, + ) + + # Table will contain and extra row for Submitted + self.assertContains(response, "Started", count=1) + self.assertContains(response, "Submitted", count=1) + + domain_request.in_review() + domain_request.save() + + response = self.client.get( + "/admin/registrar/domainrequest/{}/change/".format(domain_request.pk), + follow=True, + ) + + # Table will contain and extra row for In review + self.assertContains(response, "Started", count=1) + self.assertContains(response, "Submitted", count=1) + self.assertContains(response, "In review", count=1) + + domain_request.action_needed() + domain_request.save() + + response = self.client.get( + "/admin/registrar/domainrequest/{}/change/".format(domain_request.pk), + follow=True, + ) + + # Table will contain and extra row for Action needed + self.assertContains(response, "Started", count=1) + self.assertContains(response, "Submitted", count=1) + self.assertContains(response, "In review", count=1) + self.assertContains(response, "Action needed", count=1) + + domain_request.in_review() + domain_request.save() + + response = self.client.get( + "/admin/registrar/domainrequest/{}/change/".format(domain_request.pk), + follow=True, + ) + + # Define the expected sequence of status changes + expected_status_changes = [ + "In review", + "Action needed", + "In review", + "Submitted", + "Started", + ] + + # Test for the order of status changes + for status_change in expected_status_changes: + self.assertContains(response, status_change, html=True) + + # Table now contains 2 rows for Approved + self.assertContains(response, "Started", count=1) + self.assertContains(response, "Submitted", count=1) + self.assertContains(response, "In review", count=2) + self.assertContains(response, "Action needed", count=1) + def test_collaspe_toggle_button_markup(self): """ Tests for the correct collapse toggle button markup @@ -906,7 +996,6 @@ class TestDomainRequestAdmin(MockEppLib): # Make sure the page loaded, and that we're on the right page self.assertEqual(response.status_code, 200) self.assertContains(response, domain_request.requested_domain.name) - self.test_helper.assertContains(response, "Show details") @less_console_noise_decorator