Update migrations

This commit is contained in:
Erin Song 2024-08-29 11:35:00 -07:00
commit 1bfc2dfa79
No known key found for this signature in database
25 changed files with 220 additions and 70 deletions

View file

@ -816,3 +816,25 @@ Example: `cf ssh getgov-za`
| | Parameter | Description | | | Parameter | Description |
|:-:|:-------------------------- |:-----------------------------------------------------------------------------------| |:-:|:-------------------------- |:-----------------------------------------------------------------------------------|
| 1 | **federal_cio_csv_path** | Specifies where the federal CIO csv is | | 1 | **federal_cio_csv_path** | Specifies where the federal CIO csv is |
## Populate Domain Request Dates
This section outlines how to run the populate_domain_request_dates script
### Running on sandboxes
#### Step 1: Login to CloudFoundry
```cf login -a api.fr.cloud.gov --sso```
#### Step 2: SSH into your environment
```cf ssh getgov-{space}```
Example: `cf ssh getgov-za`
#### Step 3: Create a shell instance
```/tmp/lifecycle/shell```
#### Step 4: Running the script
```./manage.py populate_domain_request_dates```
### Running locally
```docker-compose exec app ./manage.py populate_domain_request_dates```

View file

@ -1691,7 +1691,9 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
# Columns # Columns
list_display = [ list_display = [
"requested_domain", "requested_domain",
"submission_date", "first_submitted_date",
"last_submitted_date",
"last_status_update",
"status", "status",
"generic_org_type", "generic_org_type",
"federal_type", "federal_type",
@ -1889,7 +1891,7 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
# Table ordering # Table ordering
# NOTE: This impacts the select2 dropdowns (combobox) # NOTE: This impacts the select2 dropdowns (combobox)
# Currentl, there's only one for requests on DomainInfo # Currentl, there's only one for requests on DomainInfo
ordering = ["-submission_date", "requested_domain__name"] ordering = ["-last_submitted_date", "requested_domain__name"]
change_form_template = "django/admin/domain_request_change_form.html" change_form_template = "django/admin/domain_request_change_form.html"

View file

@ -1599,7 +1599,7 @@ document.addEventListener('DOMContentLoaded', function() {
const domainName = request.requested_domain ? request.requested_domain : `New domain request <br><span class="text-base font-body-xs">(${utcDateString(request.created_at)})</span>`; const domainName = request.requested_domain ? request.requested_domain : `New domain request <br><span class="text-base font-body-xs">(${utcDateString(request.created_at)})</span>`;
const actionUrl = request.action_url; const actionUrl = request.action_url;
const actionLabel = request.action_label; const actionLabel = request.action_label;
const submissionDate = request.submission_date ? new Date(request.submission_date).toLocaleDateString('en-US', options) : `<span class="text-base">Not submitted</span>`; const submissionDate = request.last_submitted_date ? new Date(request.last_submitted_date).toLocaleDateString('en-US', options) : `<span class="text-base">Not submitted</span>`;
// Even if the request is not deletable, we may need this empty string for the td if the deletable column is displayed // Even if the request is not deletable, we may need this empty string for the td if the deletable column is displayed
let modalTrigger = ''; let modalTrigger = '';
@ -1699,7 +1699,7 @@ document.addEventListener('DOMContentLoaded', function() {
<th scope="row" role="rowheader" data-label="Domain name"> <th scope="row" role="rowheader" data-label="Domain name">
${domainName} ${domainName}
</th> </th>
<td data-sort-value="${new Date(request.submission_date).getTime()}" data-label="Date submitted"> <td data-sort-value="${new Date(request.last_submitted_date).getTime()}" data-label="Date submitted">
${submissionDate} ${submissionDate}
</td> </td>
<td data-label="Status"> <td data-label="Status">

View file

@ -94,7 +94,7 @@ class DomainRequestFixture:
# TODO for a future ticket: Allow for more than just "federal" here # TODO for a future ticket: Allow for more than just "federal" here
da.generic_org_type = app["generic_org_type"] if "generic_org_type" in app else "federal" da.generic_org_type = app["generic_org_type"] if "generic_org_type" in app else "federal"
da.submission_date = fake.date() da.last_submitted_date = fake.date()
da.federal_type = ( da.federal_type = (
app["federal_type"] app["federal_type"]
if "federal_type" in app if "federal_type" in app

View file

@ -0,0 +1,45 @@
import logging
from django.core.management import BaseCommand
from registrar.management.commands.utility.terminal_helper import PopulateScriptTemplate, TerminalColors
from registrar.models import DomainRequest
from auditlog.models import LogEntry
logger = logging.getLogger(__name__)
class Command(BaseCommand, PopulateScriptTemplate):
help = "Loops through each domain request object and populates the last_status_update and first_submitted_date"
def handle(self, **kwargs):
"""Loops through each DomainRequest object and populates
its last_status_update and first_submitted_date values"""
self.mass_update_records(DomainRequest, None, ["last_status_update", "first_submitted_date"])
def update_record(self, record: DomainRequest):
"""Defines how we update the first_submitted_date and last_status_update fields"""
# Retrieve and order audit log entries by timestamp in descending order
audit_log_entries = LogEntry.objects.filter(object_pk=record.pk).order_by("-timestamp")
# Loop through logs in descending order to find most recent status change
for log_entry in audit_log_entries:
if "status" in log_entry.changes_dict:
record.last_status_update = log_entry.timestamp.date()
break
# Loop through logs in ascending order to find first submission
for log_entry in audit_log_entries.reverse():
status = log_entry.changes_dict.get("status")
if status and status[1] == "submitted":
record.first_submitted_date = log_entry.timestamp.date()
break
logger.info(
f"""{TerminalColors.OKCYAN}Updating {record} =>
first submitted date: {record.first_submitted_date},
last status update: {record.last_status_update}{TerminalColors.ENDC}
"""
)
def should_skip_record(self, record) -> bool:
# make sure the record had some kind of history
return not LogEntry.objects.filter(object_pk=record.pk).exists()

View file

@ -86,7 +86,7 @@ class PopulateScriptTemplate(ABC):
You must define update_record before you can use this function. You must define update_record before you can use this function.
""" """
records = object_class.objects.filter(**filter_conditions) records = object_class.objects.filter(**filter_conditions) if filter_conditions else object_class.objects.all()
readable_class_name = self.get_class_name(object_class) readable_class_name = self.get_class_name(object_class)
# Code execution will stop here if the user prompts "N" # Code execution will stop here if the user prompts "N"

View file

@ -0,0 +1,47 @@
# Generated by Django 4.2.10 on 2024-08-16 15:28
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("registrar", "0119_remove_user_portfolio_and_more"),
]
operations = [
migrations.RenameField(
model_name="domainrequest",
old_name="submission_date",
new_name="last_submitted_date",
),
migrations.AlterField(
model_name="domainrequest",
name="last_submitted_date",
field=models.DateField(
blank=True, default=None, help_text="Date last submitted", null=True, verbose_name="last submitted on"
),
),
migrations.AddField(
model_name="domainrequest",
name="first_submitted_date",
field=models.DateField(
blank=True,
default=None,
help_text="Date initially submitted",
null=True,
verbose_name="first submitted on",
),
),
migrations.AddField(
model_name="domainrequest",
name="last_status_update",
field=models.DateField(
blank=True,
default=None,
help_text="Date of the last status update",
null=True,
verbose_name="last updated on",
),
),
]

View file

@ -8,7 +8,7 @@ import django.db.models.deletion
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
("registrar", "0119_remove_user_portfolio_and_more"), ("registrar", "0120_add_domainrequest_submission_dates"),
] ]
operations = [ operations = [

View file

@ -550,15 +550,32 @@ class DomainRequest(TimeStampedModel):
help_text="Acknowledged .gov acceptable use policy", help_text="Acknowledged .gov acceptable use policy",
) )
# submission date records when domain request is submitted # Records when the domain request was first submitted
submission_date = models.DateField( first_submitted_date = models.DateField(
null=True, null=True,
blank=True, blank=True,
default=None, default=None,
verbose_name="submitted at", verbose_name="first submitted on",
help_text="Date submitted", help_text="Date initially submitted",
) )
# Records when domain request was last submitted
last_submitted_date = models.DateField(
null=True,
blank=True,
default=None,
verbose_name="last submitted on",
help_text="Date last submitted",
)
# Records when domain request status was last updated by an admin or analyst
last_status_update = models.DateField(
null=True,
blank=True,
default=None,
verbose_name="last updated on",
help_text="Date of the last status update",
)
notes = models.TextField( notes = models.TextField(
null=True, null=True,
blank=True, blank=True,
@ -608,6 +625,9 @@ class DomainRequest(TimeStampedModel):
self.sync_organization_type() self.sync_organization_type()
self.sync_yes_no_form_fields() self.sync_yes_no_form_fields()
if self._cached_status != self.status:
self.last_status_update = timezone.now().date()
super().save(*args, **kwargs) super().save(*args, **kwargs)
# Handle the action needed email. # Handle the action needed email.
@ -787,8 +807,12 @@ class DomainRequest(TimeStampedModel):
if not DraftDomain.string_could_be_domain(self.requested_domain.name): if not DraftDomain.string_could_be_domain(self.requested_domain.name):
raise ValueError("Requested domain is not a valid domain name.") raise ValueError("Requested domain is not a valid domain name.")
# Update submission_date to today # if the domain has not been submitted before this must be the first time
self.submission_date = timezone.now().date() if not self.first_submitted_date:
self.first_submitted_date = timezone.now().date()
# Update last_submitted_date to today
self.last_submitted_date = timezone.now().date()
self.save() self.save()
# Limit email notifications to transitions from Started and Withdrawn # Limit email notifications to transitions from Started and Withdrawn

View file

@ -4,7 +4,7 @@ Hi, {{ recipient.first_name }}.
We've identified an action that youll need to complete before we continue reviewing your .gov domain request. We've identified an action that youll need to complete before we continue reviewing your .gov domain request.
DOMAIN REQUESTED: {{ domain_request.requested_domain.name }} DOMAIN REQUESTED: {{ domain_request.requested_domain.name }}
REQUEST RECEIVED ON: {{ domain_request.submission_date|date }} REQUEST RECEIVED ON: {{ domain_request.last_submitted_date|date }}
STATUS: Action needed STATUS: Action needed
---------------------------------------------------------------- ----------------------------------------------------------------

View file

@ -4,7 +4,7 @@ Hi, {{ recipient.first_name }}.
We've identified an action that youll need to complete before we continue reviewing your .gov domain request. We've identified an action that youll need to complete before we continue reviewing your .gov domain request.
DOMAIN REQUESTED: {{ domain_request.requested_domain.name }} DOMAIN REQUESTED: {{ domain_request.requested_domain.name }}
REQUEST RECEIVED ON: {{ domain_request.submission_date|date }} REQUEST RECEIVED ON: {{ domain_request.last_submitted_date|date }}
STATUS: Action needed STATUS: Action needed
---------------------------------------------------------------- ----------------------------------------------------------------

View file

@ -4,7 +4,7 @@ Hi, {{ recipient.first_name }}.
We've identified an action that youll need to complete before we continue reviewing your .gov domain request. We've identified an action that youll need to complete before we continue reviewing your .gov domain request.
DOMAIN REQUESTED: {{ domain_request.requested_domain.name }} DOMAIN REQUESTED: {{ domain_request.requested_domain.name }}
REQUEST RECEIVED ON: {{ domain_request.submission_date|date }} REQUEST RECEIVED ON: {{ domain_request.last_submitted_date|date }}
STATUS: Action needed STATUS: Action needed
---------------------------------------------------------------- ----------------------------------------------------------------

View file

@ -4,7 +4,7 @@ Hi, {{ recipient.first_name }}.
We've identified an action that youll need to complete before we continue reviewing your .gov domain request. We've identified an action that youll need to complete before we continue reviewing your .gov domain request.
DOMAIN REQUESTED: {{ domain_request.requested_domain.name }} DOMAIN REQUESTED: {{ domain_request.requested_domain.name }}
REQUEST RECEIVED ON: {{ domain_request.submission_date|date }} REQUEST RECEIVED ON: {{ domain_request.last_submitted_date|date }}
STATUS: Action needed STATUS: Action needed
---------------------------------------------------------------- ----------------------------------------------------------------

View file

@ -4,7 +4,7 @@ Hi, {{ recipient.first_name }}.
Your .gov domain request has been withdrawn and will not be reviewed by our team. Your .gov domain request has been withdrawn and will not be reviewed by our team.
DOMAIN REQUESTED: {{ domain_request.requested_domain.name }} DOMAIN REQUESTED: {{ domain_request.requested_domain.name }}
REQUEST RECEIVED ON: {{ domain_request.submission_date|date }} REQUEST RECEIVED ON: {{ domain_request.last_submitted_date|date }}
STATUS: Withdrawn STATUS: Withdrawn
---------------------------------------------------------------- ----------------------------------------------------------------

View file

@ -4,7 +4,7 @@ Hi, {{ recipient.first_name }}.
Congratulations! Your .gov domain request has been approved. Congratulations! Your .gov domain request has been approved.
DOMAIN REQUESTED: {{ domain_request.requested_domain.name }} DOMAIN REQUESTED: {{ domain_request.requested_domain.name }}
REQUEST RECEIVED ON: {{ domain_request.submission_date|date }} REQUEST RECEIVED ON: {{ domain_request.last_submitted_date|date }}
STATUS: Approved STATUS: Approved
You can manage your approved domain on the .gov registrar <https://manage.get.gov>. You can manage your approved domain on the .gov registrar <https://manage.get.gov>.

View file

@ -4,7 +4,7 @@ Hi, {{ recipient.first_name }}.
Your .gov domain request has been rejected. Your .gov domain request has been rejected.
DOMAIN REQUESTED: {{ domain_request.requested_domain.name }} DOMAIN REQUESTED: {{ domain_request.requested_domain.name }}
REQUEST RECEIVED ON: {{ domain_request.submission_date|date }} REQUEST RECEIVED ON: {{ domain_request.last_submitted_date|date }}
STATUS: Rejected STATUS: Rejected
---------------------------------------------------------------- ----------------------------------------------------------------

View file

@ -4,7 +4,7 @@ Hi, {{ recipient.first_name }}.
We received your .gov domain request. We received your .gov domain request.
DOMAIN REQUESTED: {{ domain_request.requested_domain.name }} DOMAIN REQUESTED: {{ domain_request.requested_domain.name }}
REQUEST RECEIVED ON: {{ domain_request.submission_date|date }} REQUEST RECEIVED ON: {{ domain_request.last_submitted_date|date }}
STATUS: Submitted STATUS: Submitted
---------------------------------------------------------------- ----------------------------------------------------------------

View file

@ -45,7 +45,7 @@
<thead> <thead>
<tr> <tr>
<th data-sortable="requested_domain__name" scope="col" role="columnheader">Domain name</th> <th data-sortable="requested_domain__name" scope="col" role="columnheader">Domain name</th>
<th data-sortable="submission_date" scope="col" role="columnheader">Date submitted</th> <th data-sortable="last_submitted_date" scope="col" role="columnheader">Date submitted</th>
<th data-sortable="status" scope="col" role="columnheader">Status</th> <th data-sortable="status" scope="col" role="columnheader">Status</th>
<th scope="col" role="columnheader"><span class="usa-sr-only">Action</span></th> <th scope="col" role="columnheader"><span class="usa-sr-only">Action</span></th>
<!-- AJAX will conditionally add a th for delete actions --> <!-- AJAX will conditionally add a th for delete actions -->

View file

@ -775,13 +775,13 @@ class MockDb(TestCase):
cls.domain_request_3.alternative_domains.add(website, website_2) cls.domain_request_3.alternative_domains.add(website, website_2)
cls.domain_request_3.current_websites.add(website_3, website_4) cls.domain_request_3.current_websites.add(website_3, website_4)
cls.domain_request_3.cisa_representative_email = "test@igorville.com" cls.domain_request_3.cisa_representative_email = "test@igorville.com"
cls.domain_request_3.submission_date = get_time_aware_date(datetime(2024, 4, 2)) cls.domain_request_3.last_submitted_date = get_time_aware_date(datetime(2024, 4, 2))
cls.domain_request_3.save() cls.domain_request_3.save()
cls.domain_request_4.submission_date = get_time_aware_date(datetime(2024, 4, 2)) cls.domain_request_4.last_submitted_date = get_time_aware_date(datetime(2024, 4, 2))
cls.domain_request_4.save() cls.domain_request_4.save()
cls.domain_request_6.submission_date = get_time_aware_date(datetime(2024, 4, 2)) cls.domain_request_6.last_submitted_date = get_time_aware_date(datetime(2024, 4, 2))
cls.domain_request_6.save() cls.domain_request_6.save()
@classmethod @classmethod

View file

@ -448,7 +448,7 @@ class TestDomainRequestAdmin(MockEppLib):
# Assert that our sort works correctly # Assert that our sort works correctly
self.test_helper.assert_table_sorted( self.test_helper.assert_table_sorted(
"11", "13",
( (
"creator__first_name", "creator__first_name",
"creator__last_name", "creator__last_name",
@ -457,7 +457,7 @@ class TestDomainRequestAdmin(MockEppLib):
# Assert that sorting in reverse works correctly # Assert that sorting in reverse works correctly
self.test_helper.assert_table_sorted( self.test_helper.assert_table_sorted(
"-11", "-13",
( (
"-creator__first_name", "-creator__first_name",
"-creator__last_name", "-creator__last_name",
@ -480,7 +480,7 @@ class TestDomainRequestAdmin(MockEppLib):
# Assert that our sort works correctly # Assert that our sort works correctly
self.test_helper.assert_table_sorted( self.test_helper.assert_table_sorted(
"12", "14",
( (
"investigator__first_name", "investigator__first_name",
"investigator__last_name", "investigator__last_name",
@ -489,7 +489,7 @@ class TestDomainRequestAdmin(MockEppLib):
# Assert that sorting in reverse works correctly # Assert that sorting in reverse works correctly
self.test_helper.assert_table_sorted( self.test_helper.assert_table_sorted(
"-12", "-14",
( (
"-investigator__first_name", "-investigator__first_name",
"-investigator__last_name", "-investigator__last_name",
@ -502,7 +502,7 @@ class TestDomainRequestAdmin(MockEppLib):
@less_console_noise_decorator @less_console_noise_decorator
def test_default_sorting_in_domain_requests_list(self): def test_default_sorting_in_domain_requests_list(self):
""" """
Make sure the default sortin in on the domain requests list page is reverse submission_date Make sure the default sortin in on the domain requests list page is reverse last_submitted_date
then alphabetical requested_domain then alphabetical requested_domain
""" """
@ -512,12 +512,12 @@ class TestDomainRequestAdmin(MockEppLib):
for name in ["ccc.gov", "bbb.gov", "eee.gov", "aaa.gov", "zzz.gov", "ddd.gov"] for name in ["ccc.gov", "bbb.gov", "eee.gov", "aaa.gov", "zzz.gov", "ddd.gov"]
] ]
domain_requests[0].submission_date = timezone.make_aware(datetime(2024, 10, 16)) domain_requests[0].last_submitted_date = timezone.make_aware(datetime(2024, 10, 16))
domain_requests[1].submission_date = timezone.make_aware(datetime(2001, 10, 16)) domain_requests[1].last_submitted_date = timezone.make_aware(datetime(2001, 10, 16))
domain_requests[2].submission_date = timezone.make_aware(datetime(1980, 10, 16)) domain_requests[2].last_submitted_date = timezone.make_aware(datetime(1980, 10, 16))
domain_requests[3].submission_date = timezone.make_aware(datetime(1998, 10, 16)) domain_requests[3].last_submitted_date = timezone.make_aware(datetime(1998, 10, 16))
domain_requests[4].submission_date = timezone.make_aware(datetime(2013, 10, 16)) domain_requests[4].last_submitted_date = timezone.make_aware(datetime(2013, 10, 16))
domain_requests[5].submission_date = timezone.make_aware(datetime(1980, 10, 16)) domain_requests[5].last_submitted_date = timezone.make_aware(datetime(1980, 10, 16))
# Save the modified domain requests to update their attributes in the database # Save the modified domain requests to update their attributes in the database
for domain_request in domain_requests: for domain_request in domain_requests:
@ -1649,7 +1649,9 @@ class TestDomainRequestAdmin(MockEppLib):
"cisa_representative_last_name", "cisa_representative_last_name",
"has_cisa_representative", "has_cisa_representative",
"is_policy_acknowledged", "is_policy_acknowledged",
"submission_date", "first_submitted_date",
"last_submitted_date",
"last_status_update",
"notes", "notes",
"alternative_domains", "alternative_domains",
] ]

View file

@ -768,7 +768,7 @@ class HelperFunctions(MockDbForSharedTests):
with less_console_noise(): with less_console_noise():
filter_condition = { filter_condition = {
"status": DomainRequest.DomainRequestStatus.SUBMITTED, "status": DomainRequest.DomainRequestStatus.SUBMITTED,
"submission_date__lte": self.end_date, "last_submitted_date__lte": self.end_date,
} }
submitted_requests_sliced_at_end_date = DomainRequestExport.get_sliced_requests(filter_condition) submitted_requests_sliced_at_end_date = DomainRequestExport.get_sliced_requests(filter_condition)
expected_content = [3, 2, 0, 0, 0, 0, 1, 0, 0, 1] expected_content = [3, 2, 0, 0, 0, 0, 1, 0, 0, 1]

View file

@ -25,91 +25,91 @@ class GetRequestsJsonTest(TestWithUser, WebTest):
DomainRequest.objects.create( DomainRequest.objects.create(
creator=cls.user, creator=cls.user,
requested_domain=lamb_chops, requested_domain=lamb_chops,
submission_date="2024-01-01", last_submitted_date="2024-01-01",
status=DomainRequest.DomainRequestStatus.STARTED, status=DomainRequest.DomainRequestStatus.STARTED,
created_at="2024-01-01", created_at="2024-01-01",
), ),
DomainRequest.objects.create( DomainRequest.objects.create(
creator=cls.user, creator=cls.user,
requested_domain=short_ribs, requested_domain=short_ribs,
submission_date="2024-02-01", last_submitted_date="2024-02-01",
status=DomainRequest.DomainRequestStatus.WITHDRAWN, status=DomainRequest.DomainRequestStatus.WITHDRAWN,
created_at="2024-02-01", created_at="2024-02-01",
), ),
DomainRequest.objects.create( DomainRequest.objects.create(
creator=cls.user, creator=cls.user,
requested_domain=beef_chuck, requested_domain=beef_chuck,
submission_date="2024-03-01", last_submitted_date="2024-03-01",
status=DomainRequest.DomainRequestStatus.REJECTED, status=DomainRequest.DomainRequestStatus.REJECTED,
created_at="2024-03-01", created_at="2024-03-01",
), ),
DomainRequest.objects.create( DomainRequest.objects.create(
creator=cls.user, creator=cls.user,
requested_domain=stew_beef, requested_domain=stew_beef,
submission_date="2024-04-01", last_submitted_date="2024-04-01",
status=DomainRequest.DomainRequestStatus.STARTED, status=DomainRequest.DomainRequestStatus.STARTED,
created_at="2024-04-01", created_at="2024-04-01",
), ),
DomainRequest.objects.create( DomainRequest.objects.create(
creator=cls.user, creator=cls.user,
requested_domain=None, requested_domain=None,
submission_date="2024-05-01", last_submitted_date="2024-05-01",
status=DomainRequest.DomainRequestStatus.STARTED, status=DomainRequest.DomainRequestStatus.STARTED,
created_at="2024-05-01", created_at="2024-05-01",
), ),
DomainRequest.objects.create( DomainRequest.objects.create(
creator=cls.user, creator=cls.user,
requested_domain=None, requested_domain=None,
submission_date="2024-06-01", last_submitted_date="2024-06-01",
status=DomainRequest.DomainRequestStatus.WITHDRAWN, status=DomainRequest.DomainRequestStatus.WITHDRAWN,
created_at="2024-06-01", created_at="2024-06-01",
), ),
DomainRequest.objects.create( DomainRequest.objects.create(
creator=cls.user, creator=cls.user,
requested_domain=None, requested_domain=None,
submission_date="2024-07-01", last_submitted_date="2024-07-01",
status=DomainRequest.DomainRequestStatus.REJECTED, status=DomainRequest.DomainRequestStatus.REJECTED,
created_at="2024-07-01", created_at="2024-07-01",
), ),
DomainRequest.objects.create( DomainRequest.objects.create(
creator=cls.user, creator=cls.user,
requested_domain=None, requested_domain=None,
submission_date="2024-08-01", last_submitted_date="2024-08-01",
status=DomainRequest.DomainRequestStatus.STARTED, status=DomainRequest.DomainRequestStatus.STARTED,
created_at="2024-08-01", created_at="2024-08-01",
), ),
DomainRequest.objects.create( DomainRequest.objects.create(
creator=cls.user, creator=cls.user,
requested_domain=None, requested_domain=None,
submission_date="2024-09-01", last_submitted_date="2024-09-01",
status=DomainRequest.DomainRequestStatus.STARTED, status=DomainRequest.DomainRequestStatus.STARTED,
created_at="2024-09-01", created_at="2024-09-01",
), ),
DomainRequest.objects.create( DomainRequest.objects.create(
creator=cls.user, creator=cls.user,
requested_domain=None, requested_domain=None,
submission_date="2024-10-01", last_submitted_date="2024-10-01",
status=DomainRequest.DomainRequestStatus.WITHDRAWN, status=DomainRequest.DomainRequestStatus.WITHDRAWN,
created_at="2024-10-01", created_at="2024-10-01",
), ),
DomainRequest.objects.create( DomainRequest.objects.create(
creator=cls.user, creator=cls.user,
requested_domain=None, requested_domain=None,
submission_date="2024-11-01", last_submitted_date="2024-11-01",
status=DomainRequest.DomainRequestStatus.REJECTED, status=DomainRequest.DomainRequestStatus.REJECTED,
created_at="2024-11-01", created_at="2024-11-01",
), ),
DomainRequest.objects.create( DomainRequest.objects.create(
creator=cls.user, creator=cls.user,
requested_domain=None, requested_domain=None,
submission_date="2024-11-02", last_submitted_date="2024-11-02",
status=DomainRequest.DomainRequestStatus.WITHDRAWN, status=DomainRequest.DomainRequestStatus.WITHDRAWN,
created_at="2024-11-02", created_at="2024-11-02",
), ),
DomainRequest.objects.create( DomainRequest.objects.create(
creator=cls.user, creator=cls.user,
requested_domain=None, requested_domain=None,
submission_date="2024-12-01", last_submitted_date="2024-12-01",
status=DomainRequest.DomainRequestStatus.APPROVED, status=DomainRequest.DomainRequestStatus.APPROVED,
created_at="2024-12-01", created_at="2024-12-01",
), ),
@ -138,7 +138,7 @@ class GetRequestsJsonTest(TestWithUser, WebTest):
# Extract fields from response # Extract fields from response
requested_domains = [request["requested_domain"] for request in data["domain_requests"]] requested_domains = [request["requested_domain"] for request in data["domain_requests"]]
submission_dates = [request["submission_date"] for request in data["domain_requests"]] last_submitted_dates = [request["last_submitted_date"] for request in data["domain_requests"]]
statuses = [request["status"] for request in data["domain_requests"]] statuses = [request["status"] for request in data["domain_requests"]]
created_ats = [request["created_at"] for request in data["domain_requests"]] created_ats = [request["created_at"] for request in data["domain_requests"]]
ids = [request["id"] for request in data["domain_requests"]] ids = [request["id"] for request in data["domain_requests"]]
@ -154,7 +154,7 @@ class GetRequestsJsonTest(TestWithUser, WebTest):
self.domain_requests[i].requested_domain.name if self.domain_requests[i].requested_domain else None, self.domain_requests[i].requested_domain.name if self.domain_requests[i].requested_domain else None,
requested_domains[i], requested_domains[i],
) )
self.assertEqual(self.domain_requests[i].submission_date, submission_dates[i]) self.assertEqual(self.domain_requests[i].last_submitted_date, last_submitted_dates[i])
self.assertEqual(self.domain_requests[i].get_status_display(), statuses[i]) self.assertEqual(self.domain_requests[i].get_status_display(), statuses[i])
self.assertEqual( self.assertEqual(
parse_datetime(self.domain_requests[i].created_at.isoformat()), parse_datetime(created_ats[i]) parse_datetime(self.domain_requests[i].created_at.isoformat()), parse_datetime(created_ats[i])
@ -287,26 +287,30 @@ class GetRequestsJsonTest(TestWithUser, WebTest):
def test_sorting(self): def test_sorting(self):
"""test that sorting works properly on the result set""" """test that sorting works properly on the result set"""
response = self.app.get(reverse("get_domain_requests_json"), {"sort_by": "submission_date", "order": "desc"}) response = self.app.get(
reverse("get_domain_requests_json"), {"sort_by": "last_submitted_date", "order": "desc"}
)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
data = response.json data = response.json
# Check if sorted by submission_date in descending order # Check if sorted by last_submitted_date in descending order
submission_dates = [req["submission_date"] for req in data["domain_requests"]] last_submitted_dates = [req["last_submitted_date"] for req in data["domain_requests"]]
self.assertEqual(submission_dates, sorted(submission_dates, reverse=True)) self.assertEqual(last_submitted_dates, sorted(last_submitted_dates, reverse=True))
response = self.app.get(reverse("get_domain_requests_json"), {"sort_by": "submission_date", "order": "asc"}) response = self.app.get(reverse("get_domain_requests_json"), {"sort_by": "last_submitted_date", "order": "asc"})
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
data = response.json data = response.json
# Check if sorted by submission_date in ascending order # Check if sorted by last_submitted_date in ascending order
submission_dates = [req["submission_date"] for req in data["domain_requests"]] last_submitted_dates = [req["last_submitted_date"] for req in data["domain_requests"]]
self.assertEqual(submission_dates, sorted(submission_dates)) self.assertEqual(last_submitted_dates, sorted(last_submitted_dates))
def test_filter_approved_excluded(self): def test_filter_approved_excluded(self):
"""test that approved requests are excluded from result set.""" """test that approved requests are excluded from result set."""
# sort in reverse chronological order of submission date, since most recent request is approved # sort in reverse chronological order of submission date, since most recent request is approved
response = self.app.get(reverse("get_domain_requests_json"), {"sort_by": "submission_date", "order": "desc"}) response = self.app.get(
reverse("get_domain_requests_json"), {"sort_by": "last_submitted_date", "order": "desc"}
)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
data = response.json data = response.json

View file

@ -1235,7 +1235,9 @@ class DomainRequestExport(BaseExport):
"State/territory": model.get("state_territory"), "State/territory": model.get("state_territory"),
"Request purpose": model.get("purpose"), "Request purpose": model.get("purpose"),
"CISA regional representative": model.get("cisa_representative_email"), "CISA regional representative": model.get("cisa_representative_email"),
"Submitted at": model.get("submission_date"), "Last submitted date": model.get("last_submitted_date"),
"First submitted date": model.get("first_submitted_date"),
"Last status update": model.get("last_status_update"),
} }
row = [FIELDS.get(column, "") for column in columns] row = [FIELDS.get(column, "") for column in columns]
@ -1279,8 +1281,8 @@ class DomainRequestGrowth(DomainRequestExport):
end_date_formatted = format_end_date(end_date) end_date_formatted = format_end_date(end_date)
return Q( return Q(
status=DomainRequest.DomainRequestStatus.SUBMITTED, status=DomainRequest.DomainRequestStatus.SUBMITTED,
submission_date__lte=end_date_formatted, last_submitted_date__lte=end_date_formatted,
submission_date__gte=start_date_formatted, last_submitted_date__gte=start_date_formatted,
) )
@classmethod @classmethod
@ -1304,7 +1306,9 @@ class DomainRequestDataFull(DomainRequestExport):
""" """
return [ return [
"Domain request", "Domain request",
"Submitted at", "Last submitted date",
"First submitted date",
"Last status update",
"Status", "Status",
"Domain type", "Domain type",
"Federal type", "Federal type",

View file

@ -46,7 +46,7 @@ def get_domain_requests_json(request):
domain_requests_data = [ domain_requests_data = [
{ {
"requested_domain": domain_request.requested_domain.name if domain_request.requested_domain else None, "requested_domain": domain_request.requested_domain.name if domain_request.requested_domain else None,
"submission_date": domain_request.submission_date, "last_submitted_date": domain_request.last_submitted_date,
"status": domain_request.get_status_display(), "status": domain_request.get_status_display(),
"created_at": format(domain_request.created_at, "c"), # Serialize to ISO 8601 "created_at": format(domain_request.created_at, "c"), # Serialize to ISO 8601
"id": domain_request.id, "id": domain_request.id,

View file

@ -26,7 +26,7 @@ class AnalyticsView(View):
created_at__gt=thirty_days_ago, status=models.DomainRequest.DomainRequestStatus.APPROVED created_at__gt=thirty_days_ago, status=models.DomainRequest.DomainRequestStatus.APPROVED
) )
avg_approval_time = last_30_days_approved_applications.annotate( avg_approval_time = last_30_days_approved_applications.annotate(
approval_time=F("approved_domain__created_at") - F("submission_date") approval_time=F("approved_domain__created_at") - F("last_submitted_date")
).aggregate(Avg("approval_time"))["approval_time__avg"] ).aggregate(Avg("approval_time"))["approval_time__avg"]
# Format the timedelta to display only days # Format the timedelta to display only days
if avg_approval_time is not None: if avg_approval_time is not None:
@ -104,11 +104,11 @@ class AnalyticsView(View):
filter_submitted_requests_start_date = { filter_submitted_requests_start_date = {
"status": models.DomainRequest.DomainRequestStatus.SUBMITTED, "status": models.DomainRequest.DomainRequestStatus.SUBMITTED,
"submission_date__lte": start_date_formatted, "last_submitted_date__lte": start_date_formatted,
} }
filter_submitted_requests_end_date = { filter_submitted_requests_end_date = {
"status": models.DomainRequest.DomainRequestStatus.SUBMITTED, "status": models.DomainRequest.DomainRequestStatus.SUBMITTED,
"submission_date__lte": end_date_formatted, "last_submitted_date__lte": end_date_formatted,
} }
submitted_requests_sliced_at_start_date = csv_export.DomainRequestExport.get_sliced_requests( submitted_requests_sliced_at_start_date = csv_export.DomainRequestExport.get_sliced_requests(
filter_submitted_requests_start_date filter_submitted_requests_start_date