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 %}
+
+
+
+
+
+
+
Status
+
User
+
Changed at
+
+
+
+ {% for log_entry in original_object.history.all %}
+ {% for key, value in log_entry.changes_display_dict.items %}
+ {% if key == "status" %}
+
+
{{ value.1|default:"None" }}
+
{{ log_entry.actor|default:"None" }}
+
{{ log_entry.timestamp|default:"None" }}
+
+ {% endif %}
+ {% endfor %}
+ {% endfor %}
+
+
+
+
+ {% 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, "
",
+ ]
+
+ # 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