mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-05-17 18:09:25 +02:00
Merge branch 'za/2595-update-domain-request-view-only-pages' into za/2596-view-only-domain-request-page
This commit is contained in:
commit
2012ac0108
7 changed files with 248 additions and 42 deletions
|
@ -11,6 +11,7 @@ from registrar.models.federal_agency import FederalAgency
|
|||
from registrar.models.utility.generic_helper import CreateOrUpdateOrganizationTypeHelper
|
||||
from registrar.utility.errors import FSMDomainRequestError, FSMErrorCodes
|
||||
from registrar.utility.constants import BranchChoices
|
||||
from auditlog.models import LogEntry
|
||||
|
||||
from .utility.time_stamped_model import TimeStampedModel
|
||||
from ..utility.email import send_templated_email, EmailSendingError
|
||||
|
@ -576,11 +577,25 @@ class DomainRequest(TimeStampedModel):
|
|||
verbose_name="last updated on",
|
||||
help_text="Date of the last status update",
|
||||
)
|
||||
|
||||
notes = models.TextField(
|
||||
null=True,
|
||||
blank=True,
|
||||
)
|
||||
|
||||
def get_first_status_set_date(self, status):
|
||||
"""Returns the date when the domain request was first set to the given status."""
|
||||
log_entry = (
|
||||
LogEntry.objects.filter(content_type__model="domainrequest", object_pk=self.pk, changes__status__1=status)
|
||||
.order_by("-timestamp")
|
||||
.first()
|
||||
)
|
||||
return log_entry.timestamp.date() if log_entry else None
|
||||
|
||||
def get_first_status_started_date(self):
|
||||
"""Returns the date when the domain request was put into the status "started" for the first time"""
|
||||
return self.get_first_status_set_date(DomainRequest.DomainRequestStatus.STARTED)
|
||||
|
||||
@classmethod
|
||||
def get_statuses_that_send_emails(cls):
|
||||
"""Returns a list of statuses that send an email to the user"""
|
||||
|
@ -1138,6 +1153,11 @@ class DomainRequest(TimeStampedModel):
|
|||
data[field.name] = field.value_from_object(self)
|
||||
return data
|
||||
|
||||
def get_formatted_cisa_rep_name(self):
|
||||
"""Returns the cisa representatives name in Western order."""
|
||||
names = [n for n in [self.cisa_representative_first_name, self.cisa_representative_last_name] if n]
|
||||
return " ".join(names) if names else "Unknown"
|
||||
|
||||
def _is_federal_complete(self):
|
||||
# Federal -> "Federal government branch" page can't be empty + Federal Agency selection can't be None
|
||||
return not (self.federal_type is None or self.federal_agency is None)
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
import time
|
||||
import logging
|
||||
from urllib.parse import urlparse, urlunparse, urlencode
|
||||
from django.urls import resolve, Resolver404
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -315,3 +316,21 @@ def convert_queryset_to_dict(queryset, is_model=True, key="id"):
|
|||
request_dict = {value[key]: value for value in queryset}
|
||||
|
||||
return request_dict
|
||||
|
||||
|
||||
def get_url_name(path):
|
||||
"""
|
||||
Given a URL path, returns the corresponding URL name defined in urls.py.
|
||||
|
||||
Args:
|
||||
path (str): The URL path to resolve.
|
||||
|
||||
Returns:
|
||||
str or None: The URL name if it exists, otherwise None.
|
||||
"""
|
||||
try:
|
||||
match = resolve(path)
|
||||
return match.url_name
|
||||
except Resolver404:
|
||||
logger.error(f"No matching URL name found for path: {path}")
|
||||
return None
|
||||
|
|
|
@ -8,33 +8,30 @@
|
|||
{% block content %}
|
||||
<main id="main-content" class="grid-container">
|
||||
<div class="grid-col desktop:grid-offset-2 desktop:grid-col-8">
|
||||
{% comment %}
|
||||
TODO: Uncomment in #2596
|
||||
{% if portfolio %}
|
||||
{% url 'domain-requests' as url %}
|
||||
{% else %}
|
||||
{% url 'home' as url %}
|
||||
{% endif %}
|
||||
<nav class="usa-breadcrumb padding-top-0" aria-label="Domain request breadcrumb">
|
||||
<ol class="usa-breadcrumb__list">
|
||||
<li class="usa-breadcrumb__list-item">
|
||||
{% if portfolio %}
|
||||
<a href="{{ url }}" class="usa-breadcrumb__link"><span>Domain requests</span></a>
|
||||
{% else %}
|
||||
<a href="{{ url }}" class="usa-breadcrumb__link"><span>Manage your domains</span></a>
|
||||
{% endif %}
|
||||
</li>
|
||||
<li class="usa-breadcrumb__list-item usa-current" aria-current="page">
|
||||
<span>{{ DomainRequest.requested_domain.name }}</span
|
||||
>
|
||||
{% if not DomainRequest.requested_domain and DomainRequest.status == DomainRequest.DomainRequestStatus.STARTED %}
|
||||
<span>New domain request</span>
|
||||
{% else %}
|
||||
<span>{{ DomainRequest.requested_domain.name }}</span>
|
||||
{% endif %}
|
||||
</li>
|
||||
</ol>
|
||||
</nav>
|
||||
{% else %}{% endcomment %}
|
||||
{% url 'home' as url %}
|
||||
<a href="{{ url }}" class="breadcrumb__back">
|
||||
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img">
|
||||
<use xlink:href="{% static 'img/sprite.svg' %}#arrow_back"></use>
|
||||
</svg>
|
||||
|
||||
<p class="margin-left-05 margin-top-0 margin-bottom-0 line-height-sans-1">
|
||||
Back to manage your domains
|
||||
</p>
|
||||
</a>
|
||||
{% comment %} {% endif %}{% endcomment %}
|
||||
<h1>Domain request for {{ DomainRequest.requested_domain.name }}</h1>
|
||||
<div
|
||||
class="usa-summary-box dotgov-status-box margin-top-3 padding-left-2"
|
||||
|
@ -48,18 +45,63 @@
|
|||
<span class="text-bold text-primary-darker">
|
||||
Status:
|
||||
</span>
|
||||
{% if DomainRequest.status == 'approved' %} Approved
|
||||
{% elif DomainRequest.status == 'in review' %} In review
|
||||
{% elif DomainRequest.status == 'rejected' %} Rejected
|
||||
{% elif DomainRequest.status == 'submitted' %} Submitted
|
||||
{% elif DomainRequest.status == 'ineligible' %} Ineligible
|
||||
{% else %}ERROR Please contact technical support/dev
|
||||
{% endif %}
|
||||
{{ DomainRequest.get_status_display|default:"ERROR Please contact technical support/dev" }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
<p><b class="review__step__name">Last updated:</b> {{DomainRequest.updated_at|date:"F j, Y"}}</p>
|
||||
|
||||
{% with statuses=DomainRequest.DomainRequestStatus last_submitted=DomainRequest.last_submitted_date|date:"F j, Y" first_submitted=DomainRequest.first_submitted_date|date:"F j, Y" last_status_update=DomainRequest.last_status_update|date:"F j, Y" %}
|
||||
{% comment %}
|
||||
These are intentionally seperated this way.
|
||||
There is some code repetition, but it gives us more flexibility rather than a dense reduction.
|
||||
Leave it this way until we've solidified our requirements.
|
||||
{% endcomment %}
|
||||
{% if DomainRequest.status == statuses.STARTED %}
|
||||
{% with first_started_date=DomainRequest.get_first_status_started_date|date:"F j, Y" %}
|
||||
<p class="margin-top-1">
|
||||
{% comment %}
|
||||
A newly created domain request will not have a value for last_status update.
|
||||
This is because the status never really updated.
|
||||
However, if this somehow goes back to started we can default to displaying that new date.
|
||||
{% endcomment %}
|
||||
<b class="review__step__name">Started on:</b> {{last_status_update|default:first_started_date}}
|
||||
</p>
|
||||
{% endwith %}
|
||||
{% elif DomainRequest.status == statuses.SUBMITTED %}
|
||||
<p class="margin-top-1 margin-bottom-1">
|
||||
<b class="review__step__name">Submitted on:</b> {{last_submitted|default:first_submitted }}
|
||||
</p>
|
||||
<p class="margin-top-1">
|
||||
<b class="review__step__name">Last updated on:</b> {{DomainRequest.updated_at|date:"F j, Y"}}
|
||||
</p>
|
||||
{% elif DomainRequest.status == statuses.ACTION_NEEDED %}
|
||||
<p class="margin-top-1 margin-bottom-1">
|
||||
<b class="review__step__name">Submitted on:</b> {{last_submitted|default:first_submitted }}
|
||||
</p>
|
||||
<p class="margin-top-1">
|
||||
<b class="review__step__name">Last updated on:</b> {{DomainRequest.updated_at|date:"F j, Y"}}
|
||||
</p>
|
||||
{% elif DomainRequest.status == statuses.REJECTED %}
|
||||
<p class="margin-top-1 margin-bottom-1">
|
||||
<b class="review__step__name">Submitted on:</b> {{last_submitted|default:first_submitted }}
|
||||
</p>
|
||||
<p class="margin-top-1">
|
||||
<b class="review__step__name">Rejected on:</b> {{last_status_update}}
|
||||
</p>
|
||||
{% elif DomainRequest.status == statuses.WITHDRAWN %}
|
||||
<p class="margin-top-1 margin-bottom-1">
|
||||
<b class="review__step__name">Submitted on:</b> {{last_submitted|default:first_submitted }}
|
||||
</p>
|
||||
<p class="margin-top-1">
|
||||
<b class="review__step__name">Withdrawn on:</b> {{last_status_update}}
|
||||
</p>
|
||||
{% else %}
|
||||
{% comment %} Shown for in_review, approved, ineligible {% endcomment %}
|
||||
<p class="margin-top-1">
|
||||
<b class="review__step__name">Last updated on:</b> {{DomainRequest.updated_at|date:"F j, Y"}}
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
{% if DomainRequest.status != 'rejected' %}
|
||||
<p>{% include "includes/domain_request.html" %}</p>
|
||||
|
@ -67,6 +109,7 @@
|
|||
Withdraw request</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
|
||||
<div class="grid-col desktop:grid-offset-2 maxw-tablet">
|
||||
|
@ -141,8 +184,8 @@
|
|||
{% if DomainRequest %}
|
||||
<h3 class="register-form-review-header">CISA Regional Representative</h3>
|
||||
<ul class="usa-list usa-list--unstyled margin-top-0">
|
||||
{% if domain_request.cisa_representative_first_name %}
|
||||
{{domain_request.cisa_representative_first_name}} {{domain_request.cisa_representative_last_name}}
|
||||
{% if DomainRequest.cisa_representative_first_name %}
|
||||
{{ DomainRequest.get_formatted_cisa_rep_name }}
|
||||
{% else %}
|
||||
No
|
||||
{% endif %}
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
{% else %}
|
||||
{% url 'no-portfolio-domains' as url %}
|
||||
{% endif %}
|
||||
<a href="{{ url }}" class="usa-nav-link{% if 'domain'|in_path:request.path %} usa-current{% endif %}">
|
||||
<a href="{{ url }}" class="usa-nav-link{% if path|is_domain_subpage %} usa-current{% endif %}">
|
||||
Domains
|
||||
</a>
|
||||
</li>
|
||||
|
@ -59,7 +59,7 @@
|
|||
{% url 'domain-requests' as url %}
|
||||
<button
|
||||
type="button"
|
||||
class="usa-accordion__button usa-nav__link{% if 'request'|in_path:request.path %} usa-current{% endif %}"
|
||||
class="usa-accordion__button usa-nav__link{% if path|is_domain_request_subpage %} usa-current{% endif %}"
|
||||
aria-expanded="false"
|
||||
aria-controls="basic-nav-section-two"
|
||||
>
|
||||
|
@ -80,13 +80,13 @@
|
|||
<!-- user has view but no edit permissions -->
|
||||
{% elif has_any_requests_portfolio_permission %}
|
||||
{% url 'domain-requests' as url %}
|
||||
<a href="{{ url }}" class="usa-nav-link{% if 'request'|in_path:request.path %} usa-current{% endif %}">
|
||||
<a href="{{ url }}" class="usa-nav-link{% if path|is_domain_request_subpage %} usa-current{% endif %}">
|
||||
Domain requests
|
||||
</a>
|
||||
<!-- user does not have permissions -->
|
||||
{% else %}
|
||||
{% url 'no-portfolio-requests' as url %}
|
||||
<a href="{{ url }}" class="usa-nav-link{% if 'request'|in_path:request.path %} usa-current{% endif %}">
|
||||
<a href="{{ url }}" class="usa-nav-link{% if path|is_domain_request_subpage %} usa-current{% endif %}">
|
||||
Domain requests
|
||||
</a>
|
||||
{% endif %}
|
||||
|
@ -104,7 +104,7 @@
|
|||
<li class="usa-nav__primary-item">
|
||||
{% url 'organization' as url %}
|
||||
<!-- Move the padding from the a to the span so that the descenders do not get cut off -->
|
||||
<a href="{{ url }}" class="usa-nav-link padding-y-0 {% if request.path == '/organization/' %} usa-current{% endif %}">
|
||||
<a href="{{ url }}" class="usa-nav-link padding-y-0 {% if path|is_portfolio_subpage %} usa-current{% endif %}">
|
||||
<span class="ellipsis ellipsis--23 ellipsis--desktop-50 padding-y-1 desktop:padding-y-2">
|
||||
{{ portfolio.organization_name }}
|
||||
</span>
|
||||
|
|
|
@ -3,6 +3,9 @@ from django import template
|
|||
import re
|
||||
from registrar.models.domain_request import DomainRequest
|
||||
from phonenumber_field.phonenumber import PhoneNumber
|
||||
from registrar.views.domain_request import DomainRequestWizard
|
||||
|
||||
from registrar.models.utility.generic_helper import get_url_name
|
||||
|
||||
register = template.Library()
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -174,3 +177,65 @@ def has_contact_info(user):
|
|||
@register.filter
|
||||
def model_name_lowercase(instance):
|
||||
return instance.__class__.__name__.lower()
|
||||
|
||||
|
||||
@register.filter(name="is_domain_subpage")
|
||||
def is_domain_subpage(path):
|
||||
"""Checks if the given page is a subpage of domains.
|
||||
Takes a path name, like '/domains/'."""
|
||||
# Since our pages aren't unified under a common path, we need this approach for now.
|
||||
url_names = [
|
||||
"domains",
|
||||
"no-portfolio-domains",
|
||||
"domain",
|
||||
"domain-users",
|
||||
"domain-dns",
|
||||
"domain-dns-nameservers",
|
||||
"domain-dns-dnssec",
|
||||
"domain-dns-dnssec-dsdata",
|
||||
"domain-your-contact-information",
|
||||
"domain-org-name-address",
|
||||
"domain-senior-official",
|
||||
"domain-security-email",
|
||||
"domain-users-add",
|
||||
"domain-request-delete",
|
||||
"domain-user-delete",
|
||||
"invitation-delete",
|
||||
]
|
||||
return get_url_name(path) in url_names
|
||||
|
||||
|
||||
@register.filter(name="is_domain_request_subpage")
|
||||
def is_domain_request_subpage(path):
|
||||
"""Checks if the given page is a subpage of domain requests.
|
||||
Takes a path name, like '/requests/'."""
|
||||
# Since our pages aren't unified under a common path, we need this approach for now.
|
||||
url_names = [
|
||||
"domain-requests",
|
||||
"no-portfolio-requests",
|
||||
"domain-request-status",
|
||||
"domain-request-withdraw-confirmation",
|
||||
"domain-request-withdrawn",
|
||||
"domain-request-delete",
|
||||
]
|
||||
|
||||
# The domain request wizard pages don't have a defined path,
|
||||
# so we need to check directly on it.
|
||||
wizard_paths = [
|
||||
DomainRequestWizard.EDIT_URL_NAME,
|
||||
DomainRequestWizard.URL_NAMESPACE,
|
||||
DomainRequestWizard.NEW_URL_NAME,
|
||||
]
|
||||
return get_url_name(path) in url_names or any(wizard in path for wizard in wizard_paths)
|
||||
|
||||
|
||||
@register.filter(name="is_portfolio_subpage")
|
||||
def is_portfolio_subpage(path):
|
||||
"""Checks if the given page is a subpage of portfolio.
|
||||
Takes a path name, like '/organization/'."""
|
||||
# Since our pages aren't unified under a common path, we need this approach for now.
|
||||
url_names = [
|
||||
"organization",
|
||||
"senior-official",
|
||||
]
|
||||
return get_url_name(path) in url_names
|
||||
|
|
|
@ -9,6 +9,9 @@ from registrar.templatetags.custom_filters import (
|
|||
find_index,
|
||||
slice_after,
|
||||
contains_checkbox,
|
||||
is_domain_request_subpage,
|
||||
is_domain_subpage,
|
||||
is_portfolio_subpage,
|
||||
)
|
||||
|
||||
|
||||
|
@ -90,3 +93,18 @@ class CustomFiltersTestCase(TestCase):
|
|||
]
|
||||
result = contains_checkbox(html_list)
|
||||
self.assertFalse(result) # Expecting False
|
||||
|
||||
def test_is_domain_subpage(self):
|
||||
"""Tests if the path is recognized as a domain subpage."""
|
||||
self.assertTrue(is_domain_subpage("/domains/"))
|
||||
self.assertFalse(is_domain_subpage("/"))
|
||||
|
||||
def test_is_domain_request_subpage(self):
|
||||
"""Tests if the path is recognized as a domain request subpage."""
|
||||
self.assertTrue(is_domain_request_subpage("/requests/"))
|
||||
self.assertFalse(is_domain_request_subpage("/"))
|
||||
|
||||
def test_is_portfolio_subpage(self):
|
||||
"""Tests if the path is recognized as a portfolio subpage."""
|
||||
self.assertTrue(is_portfolio_subpage("/organization/"))
|
||||
self.assertFalse(is_portfolio_subpage("/"))
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
from unittest import skip
|
||||
from unittest.mock import Mock
|
||||
|
||||
from unittest.mock import Mock, patch
|
||||
from datetime import datetime
|
||||
from django.utils import timezone
|
||||
from django.conf import settings
|
||||
from django.urls import reverse
|
||||
from api.tests.common import less_console_noise_decorator
|
||||
|
@ -56,6 +57,46 @@ class DomainRequestTests(TestWithUser, WebTest):
|
|||
intro_page = self.app.get(reverse("domain-request:"))
|
||||
self.assertContains(intro_page, "You’re about to start your .gov domain request")
|
||||
|
||||
@less_console_noise_decorator
|
||||
def test_template_status_display(self):
|
||||
"""Tests the display of status-related information in the template."""
|
||||
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.SUBMITTED, user=self.user)
|
||||
domain_request.last_submitted_date = datetime.now()
|
||||
domain_request.save()
|
||||
response = self.app.get(f"/domain-request/{domain_request.id}")
|
||||
self.assertContains(response, "Submitted on:")
|
||||
self.assertContains(response, domain_request.last_submitted_date.strftime("%B %-d, %Y"))
|
||||
|
||||
@patch.object(DomainRequest, "get_first_status_set_date")
|
||||
def test_get_first_status_started_date(self, mock_get_first_status_set_date):
|
||||
"""Tests retrieval of the first date the status was set to 'started'."""
|
||||
|
||||
# Set the mock to return a fixed date
|
||||
fixed_date = timezone.datetime(2023, 1, 1).date()
|
||||
mock_get_first_status_set_date.return_value = fixed_date
|
||||
|
||||
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.STARTED, user=self.user)
|
||||
domain_request.last_status_update = None
|
||||
domain_request.save()
|
||||
|
||||
response = self.app.get(f"/domain-request/{domain_request.id}")
|
||||
# Ensure that the date is still set to None
|
||||
self.assertIsNone(domain_request.last_status_update)
|
||||
print(response)
|
||||
# We should still grab a date for this field in this event - but it should come from the audit log instead
|
||||
self.assertContains(response, "Started on:")
|
||||
self.assertContains(response, fixed_date.strftime("%B %-d, %Y"))
|
||||
|
||||
# If a status date is set, we display that instead
|
||||
domain_request.last_status_update = datetime.now()
|
||||
domain_request.save()
|
||||
|
||||
response = self.app.get(f"/domain-request/{domain_request.id}")
|
||||
|
||||
# We should still grab a date for this field in this event - but it should come from the audit log instead
|
||||
self.assertContains(response, "Started on:")
|
||||
self.assertContains(response, domain_request.last_status_update.strftime("%B %-d, %Y"))
|
||||
|
||||
@less_console_noise_decorator
|
||||
def test_domain_request_form_intro_is_skipped_when_edit_access(self):
|
||||
"""Tests that user is NOT presented with intro acknowledgement page when accessed through 'edit'"""
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue