mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-05-31 09:43:54 +02:00
resolved merge
This commit is contained in:
commit
59c3a1e535
46 changed files with 1759 additions and 1134 deletions
|
@ -9,10 +9,10 @@ We use [django-waffle](https://waffle.readthedocs.io/en/stable/) for our feature
|
|||
3. Click `Add waffle flag`.
|
||||
4. Add the model as you would normally. Refer to waffle's documentation [regarding attributes](https://waffle.readthedocs.io/en/stable/types/flag.html#flag-attributes) for more information on them.
|
||||
|
||||
### Enabling the profile_feature flag
|
||||
### Enabling a feature flag
|
||||
1. On the app, navigate to `\admin`.
|
||||
2. Under models, click `Waffle flags`.
|
||||
3. Click the `profile_feature` record. This should exist by default, if not - create one with that name.
|
||||
3. Click the featue flag record. This should exist by default, if not - create one with that name.
|
||||
4. (Important) Set the field `Everyone` to `Unknown`. This field overrides all other settings when set to anything else.
|
||||
5. Configure the settings as you see fit.
|
||||
|
||||
|
|
|
@ -126,7 +126,15 @@ class AvailableAPITest(MockEppLib):
|
|||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.user = get_user_model().objects.create(username="username")
|
||||
username = "test_user"
|
||||
first_name = "First"
|
||||
last_name = "Last"
|
||||
email = "info@example.com"
|
||||
title = "title"
|
||||
phone = "8080102431"
|
||||
self.user = get_user_model().objects.create(
|
||||
username=username, title=title, first_name=first_name, last_name=last_name, email=email, phone=phone
|
||||
)
|
||||
|
||||
def test_available_get(self):
|
||||
self.client.force_login(self.user)
|
||||
|
|
|
@ -1977,11 +1977,7 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
|||
so we should display that information using this function.
|
||||
|
||||
"""
|
||||
|
||||
if hasattr(obj, "creator"):
|
||||
recipient = obj.creator
|
||||
else:
|
||||
recipient = None
|
||||
|
||||
# Displays a warning in admin when an email cannot be sent
|
||||
if recipient and recipient.email:
|
||||
|
@ -2186,7 +2182,6 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
|||
extra_context["filtered_audit_log_entries"] = filtered_audit_log_entries
|
||||
emails = self.get_all_action_needed_reason_emails(obj)
|
||||
extra_context["action_needed_reason_emails"] = json.dumps(emails)
|
||||
extra_context["has_profile_feature_flag"] = flag_is_active(request, "profile_feature")
|
||||
|
||||
# Denote if an action needed email was sent or not
|
||||
email_sent = request.session.get("action_needed_email_sent", False)
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,7 +1,8 @@
|
|||
@use "uswds-core" as *;
|
||||
@use "cisa_colors" as *;
|
||||
|
||||
/* Make "placeholder" links visually obvious */
|
||||
// Used on: TODO links
|
||||
// Used on: NONE
|
||||
a[href$="todo"]::after {
|
||||
background-color: yellow;
|
||||
color: color(blue-80v);
|
||||
|
@ -9,10 +10,14 @@ a[href$="todo"]::after {
|
|||
font-style: italic;
|
||||
}
|
||||
|
||||
// Used on: profile
|
||||
// Note: Is this needed?
|
||||
a.usa-link.usa-link--always-blue {
|
||||
color: #{$dhs-blue};
|
||||
}
|
||||
|
||||
// Used on: breadcrumbs
|
||||
// Note: This could potentially be simplified and use usa-button--with-icon
|
||||
a.breadcrumb__back {
|
||||
display:flex;
|
||||
align-items: center;
|
||||
|
@ -28,10 +33,18 @@ a.breadcrumb__back {
|
|||
}
|
||||
}
|
||||
|
||||
// Remove anchor buttons' underline
|
||||
a.usa-button {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
// Unstyled anchor buttons
|
||||
a.usa-button--unstyled:visited {
|
||||
color: color('primary');
|
||||
}
|
||||
|
||||
// Disabled anchor buttons
|
||||
// NOTE: Not used
|
||||
a.usa-button.disabled-link {
|
||||
background-color: #ccc !important;
|
||||
color: #454545 !important
|
||||
|
@ -58,6 +71,8 @@ a.usa-button--unstyled.disabled-link:focus {
|
|||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
// Disabled buttons
|
||||
// Used on: Domain managers, disabled logo on profile
|
||||
.usa-button--unstyled.disabled-button,
|
||||
.usa-button--unstyled.disabled-button:hover,
|
||||
.usa-button--unstyled.disabled-button:focus {
|
||||
|
@ -66,6 +81,16 @@ a.usa-button--unstyled.disabled-link:focus {
|
|||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
// Unstyled variant for reverse out?
|
||||
// Used on: NONE
|
||||
.usa-button--unstyled--white,
|
||||
.usa-button--unstyled--white:hover,
|
||||
.usa-button--unstyled--white:focus,
|
||||
.usa-button--unstyled--white:active {
|
||||
color: color('white');
|
||||
}
|
||||
|
||||
// Solid anchor buttons
|
||||
a.usa-button:not(.usa-button--unstyled, .usa-button--outline) {
|
||||
color: color('white');
|
||||
}
|
||||
|
@ -77,6 +102,7 @@ a.usa-button:not(.usa-button--unstyled, .usa-button--outline):active {
|
|||
color: color('white');
|
||||
}
|
||||
|
||||
// Outline anchor buttons
|
||||
a.usa-button--outline,
|
||||
a.usa-button--outline:visited {
|
||||
box-shadow: inset 0 0 0 2px color('primary');
|
||||
|
@ -94,10 +120,22 @@ a.usa-button--outline:active {
|
|||
color: color('primary-darker');
|
||||
}
|
||||
|
||||
// Used on: Domain request withdraw confirmation
|
||||
a.withdraw {
|
||||
background-color: color('error');
|
||||
}
|
||||
|
||||
a.withdraw:hover,
|
||||
a.withdraw:focus {
|
||||
background-color: color('error-dark');
|
||||
}
|
||||
|
||||
a.withdraw:active {
|
||||
background-color: color('error-darker');
|
||||
}
|
||||
|
||||
// Used on: Domain request status
|
||||
//NOTE: Revise to BEM convention usa-button--outline-secondary
|
||||
a.withdraw_outline,
|
||||
a.withdraw_outline:visited {
|
||||
box-shadow: inset 0 0 0 2px color('error');
|
||||
|
@ -115,19 +153,8 @@ a.withdraw_outline:active {
|
|||
color: color('error-darker');
|
||||
}
|
||||
|
||||
a.withdraw:hover,
|
||||
a.withdraw:focus {
|
||||
background-color: color('error-dark');
|
||||
}
|
||||
|
||||
a.withdraw:active {
|
||||
background-color: color('error-darker');
|
||||
}
|
||||
|
||||
a.usa-button--unstyled:visited {
|
||||
color: color('primary');
|
||||
}
|
||||
|
||||
// Used on: Domain request submit
|
||||
.dotgov-button--green {
|
||||
background-color: color('success-dark');
|
||||
|
||||
|
@ -140,15 +167,8 @@ a.usa-button--unstyled:visited {
|
|||
}
|
||||
}
|
||||
|
||||
.usa-button--unstyled--white,
|
||||
.usa-button--unstyled--white:hover,
|
||||
.usa-button--unstyled--white:focus,
|
||||
.usa-button--unstyled--white:active {
|
||||
color: color('white');
|
||||
}
|
||||
|
||||
// Cancel button used on the
|
||||
// DNSSEC main page
|
||||
// Cancel button
|
||||
// Used on: DNSSEC main page
|
||||
// We want to center this button on mobile
|
||||
// and add some extra left margin on tablet+
|
||||
.usa-button--cancel {
|
||||
|
@ -175,6 +195,8 @@ a.usa-button--unstyled:visited {
|
|||
}
|
||||
}
|
||||
|
||||
// Used on: Profile page, toggleable fields
|
||||
// Note: Could potentially be cleaned up by using usa-button--with-icon
|
||||
// We need to deviate from some default USWDS styles here
|
||||
// in this particular case, so we have to override this.
|
||||
.usa-form .usa-button.readonly-edit-button {
|
||||
|
@ -186,6 +208,7 @@ a.usa-button--unstyled:visited {
|
|||
}
|
||||
}
|
||||
|
||||
//Used on: Domains and Requests tables
|
||||
.usa-button--filter {
|
||||
width: auto;
|
||||
// For mobile stacking
|
||||
|
@ -201,6 +224,8 @@ a.usa-button--unstyled:visited {
|
|||
}
|
||||
}
|
||||
|
||||
// Buttons with nested icons
|
||||
// Note: Can be simplified by adding usa-link--icon to anchors in tables
|
||||
.dotgov-table a,
|
||||
.usa-link--icon,
|
||||
.usa-button--with-icon {
|
||||
|
@ -232,6 +257,9 @@ a .usa-icon,
|
|||
width: 1.5em;
|
||||
}
|
||||
|
||||
// Red, for delete buttons
|
||||
// Used on: All delete buttons
|
||||
// Note: Can be simplified by adding text-secondary to delete anchors in tables
|
||||
button.text-secondary,
|
||||
button.text-secondary:hover,
|
||||
.dotgov-table a.text-secondary {
|
||||
|
|
|
@ -85,7 +85,7 @@ legend.float-left-tablet + button.float-right-tablet {
|
|||
|
||||
.read-only-label {
|
||||
font-size: size('body', 'sm');
|
||||
color: color('primary');
|
||||
color: color('primary-dark');
|
||||
margin-bottom: units(0.5);
|
||||
}
|
||||
|
||||
|
|
|
@ -245,7 +245,6 @@ TEMPLATES = [
|
|||
"registrar.context_processors.is_production",
|
||||
"registrar.context_processors.org_user_status",
|
||||
"registrar.context_processors.add_path_to_context",
|
||||
"registrar.context_processors.add_has_profile_feature_flag_to_context",
|
||||
"registrar.context_processors.portfolio_permissions",
|
||||
"registrar.context_processors.is_widescreen_mode",
|
||||
],
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
from django.conf import settings
|
||||
from waffle.decorators import flag_is_active
|
||||
|
||||
|
||||
def language_code(request):
|
||||
|
@ -54,10 +53,6 @@ def add_path_to_context(request):
|
|||
return {"path": getattr(request, "path", None)}
|
||||
|
||||
|
||||
def add_has_profile_feature_flag_to_context(request):
|
||||
return {"has_profile_feature_flag": flag_is_active(request, "profile_feature")}
|
||||
|
||||
|
||||
def portfolio_permissions(request):
|
||||
"""Make portfolio permissions for the request user available in global context"""
|
||||
portfolio_context = {
|
||||
|
|
|
@ -0,0 +1,229 @@
|
|||
# Generated by Django 4.2.10 on 2024-09-11 21:00
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("registrar", "0127_remove_domaininformation_submitter_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="domaininformation",
|
||||
name="state_territory",
|
||||
field=models.CharField(
|
||||
blank=True,
|
||||
choices=[
|
||||
("AL", "Alabama (AL)"),
|
||||
("AK", "Alaska (AK)"),
|
||||
("AS", "American Samoa (AS)"),
|
||||
("AZ", "Arizona (AZ)"),
|
||||
("AR", "Arkansas (AR)"),
|
||||
("CA", "California (CA)"),
|
||||
("CO", "Colorado (CO)"),
|
||||
("CT", "Connecticut (CT)"),
|
||||
("DE", "Delaware (DE)"),
|
||||
("DC", "District of Columbia (DC)"),
|
||||
("FL", "Florida (FL)"),
|
||||
("GA", "Georgia (GA)"),
|
||||
("GU", "Guam (GU)"),
|
||||
("HI", "Hawaii (HI)"),
|
||||
("ID", "Idaho (ID)"),
|
||||
("IL", "Illinois (IL)"),
|
||||
("IN", "Indiana (IN)"),
|
||||
("IA", "Iowa (IA)"),
|
||||
("KS", "Kansas (KS)"),
|
||||
("KY", "Kentucky (KY)"),
|
||||
("LA", "Louisiana (LA)"),
|
||||
("ME", "Maine (ME)"),
|
||||
("MD", "Maryland (MD)"),
|
||||
("MA", "Massachusetts (MA)"),
|
||||
("MI", "Michigan (MI)"),
|
||||
("MN", "Minnesota (MN)"),
|
||||
("MS", "Mississippi (MS)"),
|
||||
("MO", "Missouri (MO)"),
|
||||
("MT", "Montana (MT)"),
|
||||
("NE", "Nebraska (NE)"),
|
||||
("NV", "Nevada (NV)"),
|
||||
("NH", "New Hampshire (NH)"),
|
||||
("NJ", "New Jersey (NJ)"),
|
||||
("NM", "New Mexico (NM)"),
|
||||
("NY", "New York (NY)"),
|
||||
("NC", "North Carolina (NC)"),
|
||||
("ND", "North Dakota (ND)"),
|
||||
("MP", "Northern Mariana Islands (MP)"),
|
||||
("OH", "Ohio (OH)"),
|
||||
("OK", "Oklahoma (OK)"),
|
||||
("OR", "Oregon (OR)"),
|
||||
("PA", "Pennsylvania (PA)"),
|
||||
("PR", "Puerto Rico (PR)"),
|
||||
("RI", "Rhode Island (RI)"),
|
||||
("SC", "South Carolina (SC)"),
|
||||
("SD", "South Dakota (SD)"),
|
||||
("TN", "Tennessee (TN)"),
|
||||
("TX", "Texas (TX)"),
|
||||
("UM", "United States Minor Outlying Islands (UM)"),
|
||||
("UT", "Utah (UT)"),
|
||||
("VT", "Vermont (VT)"),
|
||||
("VI", "Virgin Islands (VI)"),
|
||||
("VA", "Virginia (VA)"),
|
||||
("WA", "Washington (WA)"),
|
||||
("WV", "West Virginia (WV)"),
|
||||
("WI", "Wisconsin (WI)"),
|
||||
("WY", "Wyoming (WY)"),
|
||||
("AA", "Armed Forces Americas (AA)"),
|
||||
("AE", "Armed Forces Africa, Canada, Europe, Middle East (AE)"),
|
||||
("AP", "Armed Forces Pacific (AP)"),
|
||||
],
|
||||
max_length=2,
|
||||
null=True,
|
||||
verbose_name="state, territory, or military post",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="domainrequest",
|
||||
name="state_territory",
|
||||
field=models.CharField(
|
||||
blank=True,
|
||||
choices=[
|
||||
("AL", "Alabama (AL)"),
|
||||
("AK", "Alaska (AK)"),
|
||||
("AS", "American Samoa (AS)"),
|
||||
("AZ", "Arizona (AZ)"),
|
||||
("AR", "Arkansas (AR)"),
|
||||
("CA", "California (CA)"),
|
||||
("CO", "Colorado (CO)"),
|
||||
("CT", "Connecticut (CT)"),
|
||||
("DE", "Delaware (DE)"),
|
||||
("DC", "District of Columbia (DC)"),
|
||||
("FL", "Florida (FL)"),
|
||||
("GA", "Georgia (GA)"),
|
||||
("GU", "Guam (GU)"),
|
||||
("HI", "Hawaii (HI)"),
|
||||
("ID", "Idaho (ID)"),
|
||||
("IL", "Illinois (IL)"),
|
||||
("IN", "Indiana (IN)"),
|
||||
("IA", "Iowa (IA)"),
|
||||
("KS", "Kansas (KS)"),
|
||||
("KY", "Kentucky (KY)"),
|
||||
("LA", "Louisiana (LA)"),
|
||||
("ME", "Maine (ME)"),
|
||||
("MD", "Maryland (MD)"),
|
||||
("MA", "Massachusetts (MA)"),
|
||||
("MI", "Michigan (MI)"),
|
||||
("MN", "Minnesota (MN)"),
|
||||
("MS", "Mississippi (MS)"),
|
||||
("MO", "Missouri (MO)"),
|
||||
("MT", "Montana (MT)"),
|
||||
("NE", "Nebraska (NE)"),
|
||||
("NV", "Nevada (NV)"),
|
||||
("NH", "New Hampshire (NH)"),
|
||||
("NJ", "New Jersey (NJ)"),
|
||||
("NM", "New Mexico (NM)"),
|
||||
("NY", "New York (NY)"),
|
||||
("NC", "North Carolina (NC)"),
|
||||
("ND", "North Dakota (ND)"),
|
||||
("MP", "Northern Mariana Islands (MP)"),
|
||||
("OH", "Ohio (OH)"),
|
||||
("OK", "Oklahoma (OK)"),
|
||||
("OR", "Oregon (OR)"),
|
||||
("PA", "Pennsylvania (PA)"),
|
||||
("PR", "Puerto Rico (PR)"),
|
||||
("RI", "Rhode Island (RI)"),
|
||||
("SC", "South Carolina (SC)"),
|
||||
("SD", "South Dakota (SD)"),
|
||||
("TN", "Tennessee (TN)"),
|
||||
("TX", "Texas (TX)"),
|
||||
("UM", "United States Minor Outlying Islands (UM)"),
|
||||
("UT", "Utah (UT)"),
|
||||
("VT", "Vermont (VT)"),
|
||||
("VI", "Virgin Islands (VI)"),
|
||||
("VA", "Virginia (VA)"),
|
||||
("WA", "Washington (WA)"),
|
||||
("WV", "West Virginia (WV)"),
|
||||
("WI", "Wisconsin (WI)"),
|
||||
("WY", "Wyoming (WY)"),
|
||||
("AA", "Armed Forces Americas (AA)"),
|
||||
("AE", "Armed Forces Africa, Canada, Europe, Middle East (AE)"),
|
||||
("AP", "Armed Forces Pacific (AP)"),
|
||||
],
|
||||
max_length=2,
|
||||
null=True,
|
||||
verbose_name="state, territory, or military post",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="portfolio",
|
||||
name="state_territory",
|
||||
field=models.CharField(
|
||||
blank=True,
|
||||
choices=[
|
||||
("AL", "Alabama (AL)"),
|
||||
("AK", "Alaska (AK)"),
|
||||
("AS", "American Samoa (AS)"),
|
||||
("AZ", "Arizona (AZ)"),
|
||||
("AR", "Arkansas (AR)"),
|
||||
("CA", "California (CA)"),
|
||||
("CO", "Colorado (CO)"),
|
||||
("CT", "Connecticut (CT)"),
|
||||
("DE", "Delaware (DE)"),
|
||||
("DC", "District of Columbia (DC)"),
|
||||
("FL", "Florida (FL)"),
|
||||
("GA", "Georgia (GA)"),
|
||||
("GU", "Guam (GU)"),
|
||||
("HI", "Hawaii (HI)"),
|
||||
("ID", "Idaho (ID)"),
|
||||
("IL", "Illinois (IL)"),
|
||||
("IN", "Indiana (IN)"),
|
||||
("IA", "Iowa (IA)"),
|
||||
("KS", "Kansas (KS)"),
|
||||
("KY", "Kentucky (KY)"),
|
||||
("LA", "Louisiana (LA)"),
|
||||
("ME", "Maine (ME)"),
|
||||
("MD", "Maryland (MD)"),
|
||||
("MA", "Massachusetts (MA)"),
|
||||
("MI", "Michigan (MI)"),
|
||||
("MN", "Minnesota (MN)"),
|
||||
("MS", "Mississippi (MS)"),
|
||||
("MO", "Missouri (MO)"),
|
||||
("MT", "Montana (MT)"),
|
||||
("NE", "Nebraska (NE)"),
|
||||
("NV", "Nevada (NV)"),
|
||||
("NH", "New Hampshire (NH)"),
|
||||
("NJ", "New Jersey (NJ)"),
|
||||
("NM", "New Mexico (NM)"),
|
||||
("NY", "New York (NY)"),
|
||||
("NC", "North Carolina (NC)"),
|
||||
("ND", "North Dakota (ND)"),
|
||||
("MP", "Northern Mariana Islands (MP)"),
|
||||
("OH", "Ohio (OH)"),
|
||||
("OK", "Oklahoma (OK)"),
|
||||
("OR", "Oregon (OR)"),
|
||||
("PA", "Pennsylvania (PA)"),
|
||||
("PR", "Puerto Rico (PR)"),
|
||||
("RI", "Rhode Island (RI)"),
|
||||
("SC", "South Carolina (SC)"),
|
||||
("SD", "South Dakota (SD)"),
|
||||
("TN", "Tennessee (TN)"),
|
||||
("TX", "Texas (TX)"),
|
||||
("UM", "United States Minor Outlying Islands (UM)"),
|
||||
("UT", "Utah (UT)"),
|
||||
("VT", "Vermont (VT)"),
|
||||
("VI", "Virgin Islands (VI)"),
|
||||
("VA", "Virginia (VA)"),
|
||||
("WA", "Washington (WA)"),
|
||||
("WV", "West Virginia (WV)"),
|
||||
("WI", "Wisconsin (WI)"),
|
||||
("WY", "Wyoming (WY)"),
|
||||
("AA", "Armed Forces Americas (AA)"),
|
||||
("AE", "Armed Forces Africa, Canada, Europe, Middle East (AE)"),
|
||||
("AP", "Armed Forces Pacific (AP)"),
|
||||
],
|
||||
max_length=2,
|
||||
null=True,
|
||||
verbose_name="state, territory, or military post",
|
||||
),
|
||||
),
|
||||
]
|
|
@ -159,7 +159,7 @@ class DomainInformation(TimeStampedModel):
|
|||
choices=StateTerritoryChoices.choices,
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name="state / territory",
|
||||
verbose_name="state, territory, or military post",
|
||||
)
|
||||
zipcode = models.CharField(
|
||||
max_length=10,
|
||||
|
|
|
@ -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
|
||||
|
@ -422,7 +423,7 @@ class DomainRequest(TimeStampedModel):
|
|||
choices=StateTerritoryChoices.choices,
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name="state / territory",
|
||||
verbose_name="state, territory, or military post",
|
||||
)
|
||||
zipcode = models.CharField(
|
||||
max_length=10,
|
||||
|
@ -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)
|
||||
|
|
|
@ -89,7 +89,7 @@ class Portfolio(TimeStampedModel):
|
|||
choices=StateTerritoryChoices.choices,
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name="state / territory",
|
||||
verbose_name="state, territory, or military post",
|
||||
)
|
||||
|
||||
zipcode = models.CharField(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -9,7 +9,7 @@ class WaffleFlag(AbstractUserFlag):
|
|||
Custom implementation of django-waffles 'Flag' object.
|
||||
Read more here: https://waffle.readthedocs.io/en/stable/types/flag.html
|
||||
|
||||
Use this class when dealing with feature flags, such as profile_feature.
|
||||
Use this class when dealing with feature flags.
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
|
|
|
@ -72,12 +72,6 @@ class CheckUserProfileMiddleware:
|
|||
"""Runs pre-processing logic for each view. Checks for the
|
||||
finished_setup flag on the current user. If they haven't done so,
|
||||
then we redirect them to the finish setup page."""
|
||||
# Check that the user is "opted-in" to the profile feature flag
|
||||
has_profile_feature_flag = flag_is_active(request, "profile_feature")
|
||||
|
||||
# If they aren't, skip this check entirely
|
||||
if not has_profile_feature_flag:
|
||||
return None
|
||||
|
||||
if request.user.is_authenticated:
|
||||
profile_page = self.profile_page
|
||||
|
|
|
@ -77,19 +77,12 @@
|
|||
{% include "includes/summary_item.html" with title='Suborganization' value=domain.domain_info.sub_organization edit_link=url editable=is_editable|and:has_edit_suborganization_portfolio_permission %}
|
||||
{% else %}
|
||||
{% url 'domain-org-name-address' pk=domain.id as url %}
|
||||
{% include "includes/summary_item.html" with title='Organization name and mailing address' value=domain.domain_info address='true' edit_link=url editable=is_editable %}
|
||||
{% include "includes/summary_item.html" with title='Organization' value=domain.domain_info address='true' edit_link=url editable=is_editable %}
|
||||
|
||||
{% url 'domain-senior-official' pk=domain.id as url %}
|
||||
{% include "includes/summary_item.html" with title='Senior official' value=domain.domain_info.senior_official contact='true' edit_link=url editable=is_editable %}
|
||||
{% endif %}
|
||||
|
||||
|
||||
{# Conditionally display profile #}
|
||||
{% if not has_profile_feature_flag %}
|
||||
{% url 'domain-your-contact-information' pk=domain.id as url %}
|
||||
{% include "includes/summary_item.html" with title='Your contact information' value=request.user contact='true' edit_link=url editable=is_editable %}
|
||||
{% endif %}
|
||||
|
||||
{% url 'domain-security-email' pk=domain.id as url %}
|
||||
{% if security_email is not None and security_email not in hidden_security_emails%}
|
||||
{% include "includes/summary_item.html" with title='Security email' value=security_email edit_link=url editable=is_editable %}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
{# this is right after the messages block in the parent template #}
|
||||
{% include "includes/form_errors.html" with form=form %}
|
||||
|
||||
<h1>Organization name and mailing address </h1>
|
||||
<h1>Organization</h1>
|
||||
|
||||
<p>The name of your organization will be publicly listed as the domain registrant.</p>
|
||||
|
||||
|
|
|
@ -16,11 +16,9 @@
|
|||
<h2>Time to complete the form</h2>
|
||||
<p>If you have <a href="{% public_site_url 'domains/before/#information-you%E2%80%99ll-need-to-complete-the-domain-request-form' %}" target="_blank" class="usa-link">all the information you need</a>,
|
||||
completing your domain request might take around 15 minutes.</p>
|
||||
{% if has_profile_feature_flag %}
|
||||
<h2>How we’ll reach you</h2>
|
||||
<p>While reviewing your domain request, we may need to reach out with questions. We’ll also email you when we complete our review. If the contact information below is not correct, visit <a href="{% url 'user-profile' %}?redirect=domain-request:" class="usa-link">your profile</a> to make updates.</p>
|
||||
{% include "includes/profile_information.html" with user=user%}
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% block form_buttons %}
|
||||
|
|
|
@ -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">
|
||||
|
@ -100,7 +143,7 @@
|
|||
{% endif %}
|
||||
|
||||
{% if DomainRequest.organization_name %}
|
||||
{% include "includes/summary_item.html" with title='Organization name and mailing address' value=DomainRequest address='true' heading_level=heading_level %}
|
||||
{% include "includes/summary_item.html" with title='Organization' value=DomainRequest address='true' heading_level=heading_level %}
|
||||
{% endif %}
|
||||
|
||||
{% if DomainRequest.about_your_organization %}
|
||||
|
@ -130,7 +173,6 @@
|
|||
{% if DomainRequest.creator %}
|
||||
{% include "includes/summary_item.html" with title='Your contact information' value=DomainRequest.creator contact='true' heading_level=heading_level %}
|
||||
{% endif %}
|
||||
|
||||
{% if DomainRequest.other_contacts.all %}
|
||||
{% include "includes/summary_item.html" with title='Other employees from your organization' value=DomainRequest.other_contacts.all contact='true' list='true' heading_level=heading_level %}
|
||||
{% else %}
|
||||
|
@ -141,8 +183,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 %}
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
{% if not portfolio %}
|
||||
{% with url_name="domain-org-name-address" %}
|
||||
{% include "includes/domain_sidenav_item.html" with item_text="Organization name and mailing address" %}
|
||||
{% include "includes/domain_sidenav_item.html" with item_text="Organization" %}
|
||||
{% endwith %}
|
||||
{% endif %}
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ State-recognized tribe
|
|||
Election office:
|
||||
{{ domain_request.is_election_board|yesno:"Yes,No,Incomplete" }}
|
||||
{% endif %}
|
||||
Organization name and mailing address:
|
||||
Organization:
|
||||
{% spaceless %}{{ domain_request.federal_agency }}
|
||||
{{ domain_request.organization_name }}
|
||||
{{ domain_request.address_line1 }}{% if domain_request.address_line2 %}
|
||||
|
|
|
@ -23,13 +23,21 @@
|
|||
</svg>
|
||||
Reset
|
||||
</button>
|
||||
{% if portfolio %}
|
||||
<label class="usa-sr-only" for="domain-requests__search-field">Search by domain name or creator</label>
|
||||
{% else %}
|
||||
<label class="usa-sr-only" for="domain-requests__search-field">Search by domain name</label>
|
||||
{% endif %}
|
||||
<input
|
||||
class="usa-input"
|
||||
id="domain-requests__search-field"
|
||||
type="search"
|
||||
name="search"
|
||||
{% if portfolio %}
|
||||
placeholder="Search by domain name or creator"
|
||||
{% else %}
|
||||
placeholder="Search by domain name"
|
||||
{% endif %}
|
||||
/>
|
||||
<button class="usa-button" type="submit" id="domain-requests__search-field-submit">
|
||||
<img
|
||||
|
@ -42,6 +50,125 @@
|
|||
</section>
|
||||
</div>
|
||||
</div>
|
||||
{% if portfolio %}
|
||||
<div class="display-flex flex-align-center">
|
||||
<span class="margin-right-2 margin-top-neg-1 usa-prose text-base-darker">Filter by</span>
|
||||
<div class="usa-accordion usa-accordion--select margin-right-2">
|
||||
<div class="usa-accordion__heading">
|
||||
<button
|
||||
type="button"
|
||||
class="usa-button usa-button--small padding--8-8-9 usa-button--outline usa-button--filter usa-accordion__button"
|
||||
aria-expanded="false"
|
||||
aria-controls="filter-status"
|
||||
>
|
||||
<span class="filter-indicator text-bold display-none"></span> Status
|
||||
<svg class="usa-icon top-2px" aria-hidden="true" focusable="false" role="img" width="24">
|
||||
<use xlink:href="/public/img/sprite.svg#expand_more"></use>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div id="filter-status" class="usa-accordion__content usa-prose shadow-1">
|
||||
<h2>Status</h2>
|
||||
<fieldset class="usa-fieldset margin-top-0">
|
||||
<legend class="usa-legend">Select to apply <span class="sr-only">status</span> filter</legend>
|
||||
<div class="usa-checkbox">
|
||||
<input
|
||||
class="usa-checkbox__input"
|
||||
id="filter-status-started"
|
||||
type="checkbox"
|
||||
name="filter-status"
|
||||
value="started"
|
||||
/>
|
||||
<label class="usa-checkbox__label" for="filter-status-started"
|
||||
>Started</label
|
||||
>
|
||||
</div>
|
||||
<div class="usa-checkbox">
|
||||
<input
|
||||
class="usa-checkbox__input"
|
||||
id="filter-status-submitted"
|
||||
type="checkbox"
|
||||
name="filter-status"
|
||||
value="submitted"
|
||||
/>
|
||||
<label class="usa-checkbox__label" for="filter-status-submitted"
|
||||
>Submitted</label
|
||||
>
|
||||
</div>
|
||||
<div class="usa-checkbox">
|
||||
<input
|
||||
class="usa-checkbox__input"
|
||||
id="filter-status-in-review"
|
||||
type="checkbox"
|
||||
name="filter-status"
|
||||
value="in review"
|
||||
/>
|
||||
<label class="usa-checkbox__label" for="filter-status-in-review"
|
||||
>In review</label
|
||||
>
|
||||
</div>
|
||||
<div class="usa-checkbox">
|
||||
<input
|
||||
class="usa-checkbox__input"
|
||||
id="filter-status-action-needed"
|
||||
type="checkbox"
|
||||
name="filter-status"
|
||||
value="action needed"
|
||||
/>
|
||||
<label class="usa-checkbox__label" for="filter-status-action-needed"
|
||||
>Action needed</label
|
||||
>
|
||||
</div>
|
||||
<div class="usa-checkbox">
|
||||
<input
|
||||
class="usa-checkbox__input"
|
||||
id="filter-status-rejected"
|
||||
type="checkbox"
|
||||
name="filter-status"
|
||||
value="rejected"
|
||||
/>
|
||||
<label class="usa-checkbox__label" for="filter-status-rejected"
|
||||
>Rejected</label
|
||||
>
|
||||
</div>
|
||||
<div class="usa-checkbox">
|
||||
<input
|
||||
class="usa-checkbox__input"
|
||||
id="filter-status-withdrawn"
|
||||
type="checkbox"
|
||||
name="filter-status"
|
||||
value="withdrawn"
|
||||
/>
|
||||
<label class="usa-checkbox__label" for="filter-status-withdrawn"
|
||||
>Withdrawn</label
|
||||
>
|
||||
</div>
|
||||
<div class="usa-checkbox">
|
||||
<input
|
||||
class="usa-checkbox__input"
|
||||
id="filter-status-ineligible"
|
||||
type="checkbox"
|
||||
name="filter-status"
|
||||
value="ineligible"
|
||||
/>
|
||||
<label class="usa-checkbox__label" for="filter-status-ineligible"
|
||||
>Ineligible</label
|
||||
>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="usa-button usa-button--small padding--8-12-9-12 usa-button--outline usa-button--filter domain-requests__reset-filters display-none"
|
||||
>
|
||||
Clear filters
|
||||
<svg class="usa-icon top-1px" aria-hidden="true" focusable="false" role="img" width="24">
|
||||
<use xlink:href="/public/img/sprite.svg#close"></use>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="domain-requests__table-wrapper display-none usa-table-container--scrollable margin-top-0" tabindex="0">
|
||||
<table class="usa-table usa-table--borderless usa-table--stacked dotgov-table dotgov-table--stacked domain-requests__table">
|
||||
<caption class="sr-only">Your domain requests</caption>
|
||||
|
|
|
@ -64,7 +64,7 @@
|
|||
aria-expanded="false"
|
||||
aria-controls="filter-status"
|
||||
>
|
||||
<span class="domain__filter-indicator text-bold display-none"></span> Status
|
||||
<span class="filter-indicator text-bold display-none"></span> Status
|
||||
<svg class="usa-icon top-2px" aria-hidden="true" focusable="false" role="img" width="24">
|
||||
<use xlink:href="/public/img/sprite.svg#expand_more"></use>
|
||||
</svg>
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
{% if user.is_authenticated %}
|
||||
<span class="usa-nav__username ellipsis">{{ user.email }}</span>
|
||||
</li>
|
||||
{% if has_profile_feature_flag %}
|
||||
<li class="usa-nav__primary-item">
|
||||
{% url 'user-profile' as user_profile_url %}
|
||||
{% url 'finish-user-profile-setup' as finish_setup_url %}
|
||||
|
@ -24,7 +23,6 @@
|
|||
<span class="text-primary">Your profile</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li class="usa-nav__primary-item">
|
||||
<a href="{% url 'logout' %}"><span class="text-primary">Sign out</span></a>
|
||||
{% else %}
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
{% if user.is_authenticated %}
|
||||
<span class="ellipsis usa-nav__username">{{ user.email }}</span>
|
||||
</li>
|
||||
{% if has_profile_feature_flag %}
|
||||
<li class="usa-nav__secondary-item">
|
||||
{% url 'user-profile' as user_profile_url %}
|
||||
{% url 'finish-user-profile-setup' as finish_setup_url %}
|
||||
|
@ -26,7 +25,6 @@
|
|||
Your profile
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li class="usa-nav__secondary-item">
|
||||
<a class="usa-nav-link" href="{% url 'logout' %}">Sign out</a>
|
||||
{% else %}
|
||||
|
@ -42,7 +40,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 +57,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 +78,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 +102,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>
|
||||
|
|
|
@ -2,9 +2,6 @@
|
|||
<div class="usa-alert">
|
||||
<div class="usa-alert__body {% if add_body_class %}{{ add_body_class }}{% endif %} {% if is_widescreen_mode %}usa-alert__body--widescreen{% endif %}">
|
||||
<b>Attention:</b> You are on a test site.
|
||||
{% if has_profile_feature_flag %}
|
||||
The profile_feature flag is active.
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -16,15 +16,7 @@
|
|||
|
||||
{% if can_edit %}
|
||||
{% include "includes/required_fields.html" %}
|
||||
{% else %}
|
||||
<p>
|
||||
The senior official for your organization can’t be updated here.
|
||||
To suggest an update, email <a href="mailto:help@get.gov" class="usa-link">help@get.gov</a>.
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
{% if can_edit %}
|
||||
<form class="usa-form usa-form--large" method="post" novalidate id="form-container">
|
||||
<form class="usa-form usa-form--large desktop:margin-top-4" method="post" novalidate id="form-container">
|
||||
{% csrf_token %}
|
||||
{% input_with_errors form.first_name %}
|
||||
{% input_with_errors form.last_name %}
|
||||
|
@ -33,8 +25,16 @@
|
|||
<button type="submit" class="usa-button">Save</button>
|
||||
</form>
|
||||
{% elif not form.full_name.value and not form.title.value and not form.email.value %}
|
||||
<h4>No senior official was found.</h4>
|
||||
<p>
|
||||
Your senior official is a person within your organization who can authorize domain requests.
|
||||
We don't have information about your organization's senior official. To suggest an update, email <a href="mailto:help@get.gov" class="usa-link">help@get.gov</a>.
|
||||
</p>
|
||||
{% else %}
|
||||
<p>
|
||||
The senior official for your organization can’t be updated here.
|
||||
To suggest an update, email <a href="mailto:help@get.gov" class="usa-link">help@get.gov</a>.
|
||||
</p>
|
||||
<div class="desktop:margin-top-4">
|
||||
{% if form.full_name.value is not None %}
|
||||
{% include "includes/input_read_only.html" with field=form.full_name %}
|
||||
{% endif %}
|
||||
|
@ -46,4 +46,5 @@
|
|||
{% if form.email.value is not None %}
|
||||
{% include "includes/input_read_only.html" with field=form.email %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
|
|
@ -9,6 +9,10 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block portfolio_content %}
|
||||
{% block messages %}
|
||||
{% include "includes/form_messages.html" %}
|
||||
{% endblock %}
|
||||
|
||||
<div id="main-content">
|
||||
<h1 id="domains-header">Domains</h1>
|
||||
{% include "includes/domains_table.html" with portfolio=portfolio user_domain_count=user_domain_count %}
|
||||
|
|
|
@ -5,6 +5,12 @@
|
|||
{% block title %} Domains | {% endblock %}
|
||||
|
||||
{% block portfolio_content %}
|
||||
|
||||
{% block messages %}
|
||||
{% include "includes/form_messages.html" %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
<div id="main-content">
|
||||
<h1 id="domains-header">Domains</h1>
|
||||
<section class="section-outlined">
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{% extends 'portfolio_base.html' %}
|
||||
{% load static field_helpers%}
|
||||
|
||||
{% block title %}Organization mailing address | {{ portfolio.name }}{% endblock %}
|
||||
{% block title %}Organization name and mailing address | {{ portfolio.name }}{% endblock %}
|
||||
|
||||
{% load static %}
|
||||
|
||||
|
@ -19,21 +19,25 @@
|
|||
|
||||
<div class="tablet:grid-col-9" id="main-content">
|
||||
|
||||
{% block messages %}
|
||||
{% include "includes/form_messages.html" %}
|
||||
{% endblock %}
|
||||
|
||||
<h1>Organization</h1>
|
||||
|
||||
<p>The name of your federal agency will be publicly listed as the domain registrant.</p>
|
||||
<p>The name of your organization will be publicly listed as the domain registrant.</p>
|
||||
|
||||
{% if has_edit_org_portfolio_permission %}
|
||||
<p>
|
||||
The federal agency for your organization can’t be updated here.
|
||||
Your organization name can’t be updated here.
|
||||
To suggest an update, email <a href="mailto:help@get.gov" class="usa-link">help@get.gov</a>.
|
||||
</p>
|
||||
|
||||
{% include "includes/form_errors.html" with form=form %}
|
||||
{% include "includes/required_fields.html" %}
|
||||
<form class="usa-form usa-form--large" method="post" novalidate>
|
||||
<form class="usa-form usa-form--large desktop:margin-top-4" method="post" novalidate>
|
||||
{% csrf_token %}
|
||||
<h4 class="read-only-label">Federal agency</h4>
|
||||
<h4 class="read-only-label">Organization name</h4>
|
||||
<p class="read-only-value">
|
||||
{{ portfolio.federal_agency }}
|
||||
</p>
|
||||
|
@ -49,7 +53,7 @@
|
|||
</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<h4 class="read-only-label">Federal agency</h4>
|
||||
<h4 class="read-only-label">Organization name</h4>
|
||||
<p class="read-only-value">
|
||||
{{ portfolio.federal_agency }}
|
||||
</p>
|
||||
|
|
|
@ -9,6 +9,10 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block portfolio_content %}
|
||||
{% block messages %}
|
||||
{% include "includes/form_messages.html" %}
|
||||
{% endblock %}
|
||||
|
||||
<div id="main-content">
|
||||
<h1 id="domain-requests-header">Domain requests</h1>
|
||||
<div class="grid-row grid-gap">
|
||||
|
|
|
@ -6,6 +6,10 @@
|
|||
{% load static %}
|
||||
|
||||
{% block portfolio_content %}
|
||||
{% block messages %}
|
||||
{% include "includes/form_messages.html" %}
|
||||
{% endblock %}
|
||||
|
||||
<div class="grid-row grid-gap">
|
||||
<div class="tablet:grid-col-3">
|
||||
<p class="font-body-md margin-top-0 margin-bottom-2
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -535,8 +535,10 @@ class MockDb(TestCase):
|
|||
first_name = "First"
|
||||
last_name = "Last"
|
||||
email = "info@example.com"
|
||||
title = "title"
|
||||
phone = "8080102431"
|
||||
cls.user = get_user_model().objects.create(
|
||||
username=username, first_name=first_name, last_name=last_name, email=email
|
||||
username=username, first_name=first_name, last_name=last_name, email=email, title=title, phone=phone
|
||||
)
|
||||
|
||||
current_date = get_time_aware_date(datetime(2024, 4, 2))
|
||||
|
@ -845,6 +847,7 @@ def create_superuser():
|
|||
last_name="last",
|
||||
is_staff=True,
|
||||
password=p,
|
||||
phone="8003111234",
|
||||
)
|
||||
# Retrieve the group or create it if it doesn't exist
|
||||
group, _ = UserGroup.objects.get_or_create(name="full_access_group")
|
||||
|
@ -862,7 +865,9 @@ def create_user():
|
|||
first_name="first",
|
||||
last_name="last",
|
||||
is_staff=True,
|
||||
title="title",
|
||||
password=p,
|
||||
phone="8003111234",
|
||||
)
|
||||
# Retrieve the group or create it if it doesn't exist
|
||||
group, _ = UserGroup.objects.get_or_create(name="cisa_analysts_group")
|
||||
|
@ -879,7 +884,12 @@ def create_test_user():
|
|||
phone = "8003111234"
|
||||
title = "test title"
|
||||
user = get_user_model().objects.create(
|
||||
username=username, first_name=first_name, last_name=last_name, email=email, phone=phone, title=title
|
||||
username=username,
|
||||
first_name=first_name,
|
||||
last_name=last_name,
|
||||
email=email,
|
||||
phone=phone,
|
||||
title=title,
|
||||
)
|
||||
return user
|
||||
|
||||
|
|
|
@ -37,7 +37,6 @@ from .common import (
|
|||
GenericTestHelper,
|
||||
)
|
||||
from unittest.mock import patch
|
||||
from waffle.testutils import override_flag
|
||||
|
||||
from django.conf import settings
|
||||
import boto3_mocking # type: ignore
|
||||
|
@ -957,7 +956,6 @@ class TestDomainRequestAdmin(MockEppLib):
|
|||
self.transition_state_and_send_email(domain_request, DomainRequest.DomainRequestStatus.SUBMITTED)
|
||||
self.assertEqual(len(self.mock_client.EMAILS_SENT), 3)
|
||||
|
||||
@override_flag("profile_feature", True)
|
||||
@less_console_noise_decorator
|
||||
def test_save_model_sends_approved_email(self):
|
||||
"""When transitioning to approved on a domain request,
|
||||
|
|
|
@ -256,19 +256,9 @@ class TestDomainRequest(TestCase):
|
|||
|
||||
email_allowed.delete()
|
||||
|
||||
@override_flag("profile_feature", active=False)
|
||||
@less_console_noise_decorator
|
||||
def test_submit_from_started_sends_email(self):
|
||||
msg = "Create a domain request and submit it and see if email was sent."
|
||||
domain_request = completed_domain_request(user=self.dummy_user_2)
|
||||
self.check_email_sent(
|
||||
domain_request, msg, "submit", 1, expected_content="Lava", expected_email=self.dummy_user_2.email
|
||||
)
|
||||
|
||||
@override_flag("profile_feature", active=True)
|
||||
@less_console_noise_decorator
|
||||
def test_submit_from_started_sends_email_to_creator(self):
|
||||
"""Tests if, when the profile feature flag is on, we send an email to the creator"""
|
||||
"""tests that we send an email to the creator"""
|
||||
msg = "Create a domain request and submit it and see if email was sent when the feature flag is on."
|
||||
domain_request = completed_domain_request(user=self.dummy_user_2)
|
||||
self.check_email_sent(
|
||||
|
|
|
@ -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("/"))
|
||||
|
|
|
@ -494,7 +494,13 @@ class HomeTests(TestWithUser):
|
|||
phone = "8003111234"
|
||||
status = User.RESTRICTED
|
||||
restricted_user = get_user_model().objects.create(
|
||||
username=username, first_name=first_name, last_name=last_name, email=email, phone=phone, status=status
|
||||
username=username,
|
||||
first_name=first_name,
|
||||
last_name=last_name,
|
||||
email=email,
|
||||
phone=phone,
|
||||
status=status,
|
||||
title="title",
|
||||
)
|
||||
self.client.force_login(restricted_user)
|
||||
response = self.client.get("/request/", follow=True)
|
||||
|
@ -546,7 +552,6 @@ class FinishUserProfileTests(TestWithUser, WebTest):
|
|||
return page.follow() if follow else page
|
||||
|
||||
@less_console_noise_decorator
|
||||
@override_flag("profile_feature", active=True)
|
||||
def test_full_name_initial_value(self):
|
||||
"""Test that full_name initial value is empty when first_name or last_name is empty.
|
||||
This will later be displayed as "unknown" using javascript."""
|
||||
|
@ -600,8 +605,8 @@ class FinishUserProfileTests(TestWithUser, WebTest):
|
|||
incomplete_regular_user.delete()
|
||||
|
||||
@less_console_noise_decorator
|
||||
def test_new_user_with_profile_feature_on(self):
|
||||
"""Tests that a new user is redirected to the profile setup page when profile_feature is on"""
|
||||
def test_new_user(self):
|
||||
"""Tests that a new user is redirected to the profile setup page"""
|
||||
username_regular_incomplete = "test_regular_user_incomplete"
|
||||
first_name_2 = "Incomplete"
|
||||
email_2 = "unicorn@igorville.com"
|
||||
|
@ -614,12 +619,10 @@ class FinishUserProfileTests(TestWithUser, WebTest):
|
|||
)
|
||||
|
||||
self.app.set_user(incomplete_regular_user.username)
|
||||
with override_flag("profile_feature", active=True):
|
||||
# This will redirect the user to the setup page.
|
||||
# Follow implicity checks if our redirect is working.
|
||||
finish_setup_page = self.app.get(reverse("home")).follow()
|
||||
self._set_session_cookie()
|
||||
|
||||
# Assert that we're on the right page
|
||||
self.assertContains(finish_setup_page, "Finish setting up your profile")
|
||||
|
||||
|
@ -663,7 +666,6 @@ class FinishUserProfileTests(TestWithUser, WebTest):
|
|||
verification_type=User.VerificationTypeChoices.REGULAR,
|
||||
)
|
||||
self.app.set_user(incomplete_regular_user.username)
|
||||
with override_flag("profile_feature", active=True):
|
||||
# This will redirect the user to the setup page.
|
||||
# Follow implicity checks if our redirect is working.
|
||||
finish_setup_page = self.app.get(reverse("home")).follow()
|
||||
|
@ -701,8 +703,8 @@ class FinishUserProfileTests(TestWithUser, WebTest):
|
|||
incomplete_regular_user.delete()
|
||||
|
||||
@less_console_noise_decorator
|
||||
def test_new_user_goes_to_domain_request_with_profile_feature_on(self):
|
||||
"""Tests that a new user is redirected to the domain request page when profile_feature is on"""
|
||||
def test_new_user_goes_to_domain_request(self):
|
||||
"""Tests that a new user is redirected to the domain request page"""
|
||||
username_regular_incomplete = "test_regular_user_incomplete"
|
||||
first_name_2 = "Incomplete"
|
||||
email_2 = "unicorn@igorville.com"
|
||||
|
@ -714,7 +716,7 @@ class FinishUserProfileTests(TestWithUser, WebTest):
|
|||
verification_type=User.VerificationTypeChoices.REGULAR,
|
||||
)
|
||||
self.app.set_user(incomplete_regular_user.username)
|
||||
with override_flag("profile_feature", active=True):
|
||||
with override_flag("", active=True):
|
||||
# This will redirect the user to the setup page
|
||||
finish_setup_page = self.app.get(reverse("domain-request:")).follow()
|
||||
self._set_session_cookie()
|
||||
|
@ -758,25 +760,6 @@ class FinishUserProfileTests(TestWithUser, WebTest):
|
|||
self.assertContains(completed_setup_page, "You’re about to start your .gov domain request")
|
||||
incomplete_regular_user.delete()
|
||||
|
||||
@less_console_noise_decorator
|
||||
def test_new_user_with_profile_feature_off(self):
|
||||
"""Tests that a new user is not redirected to the profile setup page when profile_feature is off"""
|
||||
with override_flag("profile_feature", active=False):
|
||||
response = self.client.get("/")
|
||||
self.assertNotContains(response, "Finish setting up your profile")
|
||||
|
||||
@less_console_noise_decorator
|
||||
def test_new_user_goes_to_domain_request_with_profile_feature_off(self):
|
||||
"""Tests that a new user is redirected to the domain request page
|
||||
when profile_feature is off but not the setup page"""
|
||||
with override_flag("profile_feature", active=False):
|
||||
response = self.client.get("/request/")
|
||||
|
||||
self.assertNotContains(response, "Finish setting up your profile")
|
||||
self.assertNotContains(response, "What contact information should we use to reach you?")
|
||||
|
||||
self.assertContains(response, "You’re about to start your .gov domain request")
|
||||
|
||||
|
||||
class FinishUserProfileForOtherUsersTests(TestWithUser, WebTest):
|
||||
"""A series of tests that target the user profile page intercept for incomplete IAL1 user profiles."""
|
||||
|
@ -816,8 +799,8 @@ class FinishUserProfileForOtherUsersTests(TestWithUser, WebTest):
|
|||
return page.follow() if follow else page
|
||||
|
||||
@less_console_noise_decorator
|
||||
def test_new_user_with_profile_feature_on(self):
|
||||
"""Tests that a new user is redirected to the profile setup page when profile_feature is on,
|
||||
def test_new_user(self):
|
||||
"""Tests that a new user is redirected to the profile setup page,
|
||||
and testing that the confirmation modal is present"""
|
||||
username_other_incomplete = "test_other_user_incomplete"
|
||||
first_name_2 = "Incomplete"
|
||||
|
@ -831,7 +814,6 @@ class FinishUserProfileForOtherUsersTests(TestWithUser, WebTest):
|
|||
verification_type=User.VerificationTypeChoices.VERIFIED_BY_STAFF,
|
||||
)
|
||||
self.app.set_user(incomplete_other_user.username)
|
||||
with override_flag("profile_feature", active=True):
|
||||
# This will redirect the user to the user profile page.
|
||||
# Follow implicity checks if our redirect is working.
|
||||
user_profile_page = self.app.get(reverse("home")).follow()
|
||||
|
@ -881,9 +863,7 @@ class FinishUserProfileForOtherUsersTests(TestWithUser, WebTest):
|
|||
# NOTE: "anage" is not a typo. It is to accomodate the fact that the "m" is uppercase in one
|
||||
# instance and lowercase in the other.
|
||||
self.assertContains(save_page, "anage your domains", count=2)
|
||||
self.assertNotContains(
|
||||
save_page, "Before you can manage your domains, we need you to add contact information"
|
||||
)
|
||||
self.assertNotContains(save_page, "Before you can manage your domains, we need you to add contact information")
|
||||
# Assert that modal does not appear on subsequent submits
|
||||
self.assertNotContains(save_page, "domain registrants must maintain accurate contact information")
|
||||
|
||||
|
@ -915,113 +895,59 @@ class UserProfileTests(TestWithUser, WebTest):
|
|||
DomainInformation.objects.all().delete()
|
||||
|
||||
@less_console_noise_decorator
|
||||
def error_500_main_nav_with_profile_feature_turned_on(self):
|
||||
"""test that Your profile is in main nav of 500 error page when profile_feature is on.
|
||||
def error_500_main_nav(self):
|
||||
"""test that Your profile is in main nav of 500 error page.
|
||||
|
||||
Our treatment of 401 and 403 error page handling with that waffle feature is similar, so we
|
||||
assume that the same test results hold true for 401 and 403."""
|
||||
with override_flag("profile_feature", active=True):
|
||||
with self.assertRaises(Exception):
|
||||
response = self.client.get(reverse("home"), follow=True)
|
||||
self.assertEqual(response.status_code, 500)
|
||||
self.assertContains(response, "Your profile")
|
||||
|
||||
@less_console_noise_decorator
|
||||
def error_500_main_nav_with_profile_feature_turned_off(self):
|
||||
"""test that Your profile is not in main nav of 500 error page when profile_feature is off.
|
||||
|
||||
Our treatment of 401 and 403 error page handling with that waffle feature is similar, so we
|
||||
assume that the same test results hold true for 401 and 403."""
|
||||
with override_flag("profile_feature", active=False):
|
||||
with self.assertRaises(Exception):
|
||||
response = self.client.get(reverse("home"), follow=True)
|
||||
self.assertEqual(response.status_code, 500)
|
||||
self.assertNotContains(response, "Your profile")
|
||||
|
||||
@less_console_noise_decorator
|
||||
def test_home_page_main_nav_with_profile_feature_on(self):
|
||||
"""test that Your profile is in main nav of home page when profile_feature is on"""
|
||||
with override_flag("profile_feature", active=True):
|
||||
def test_home_page_main_nav(self):
|
||||
"""test that Your profile is in main nav of home page"""
|
||||
response = self.client.get("/", follow=True)
|
||||
self.assertContains(response, "Your profile")
|
||||
|
||||
@less_console_noise_decorator
|
||||
def test_home_page_main_nav_with_profile_feature_off(self):
|
||||
"""test that Your profile is not in main nav of home page when profile_feature is off"""
|
||||
with override_flag("profile_feature", active=False):
|
||||
response = self.client.get("/", follow=True)
|
||||
self.assertNotContains(response, "Your profile")
|
||||
|
||||
@less_console_noise_decorator
|
||||
def test_new_request_main_nav_with_profile_feature_on(self):
|
||||
"""test that Your profile is in main nav of new request when profile_feature is on"""
|
||||
with override_flag("profile_feature", active=True):
|
||||
def test_new_request_main_nav(self):
|
||||
"""test that Your profile is in main nav of new request"""
|
||||
response = self.client.get("/request/", follow=True)
|
||||
self.assertContains(response, "Your profile")
|
||||
|
||||
@less_console_noise_decorator
|
||||
def test_new_request_main_nav_with_profile_feature_off(self):
|
||||
"""test that Your profile is not in main nav of new request when profile_feature is off"""
|
||||
with override_flag("profile_feature", active=False):
|
||||
response = self.client.get("/request/", follow=True)
|
||||
self.assertNotContains(response, "Your profile")
|
||||
|
||||
@less_console_noise_decorator
|
||||
def test_user_profile_main_nav_with_profile_feature_on(self):
|
||||
"""test that Your profile is in main nav of user profile when profile_feature is on"""
|
||||
with override_flag("profile_feature", active=True):
|
||||
def test_user_profile_main_nav(self):
|
||||
"""test that Your profile is in main nav of user profile"""
|
||||
response = self.client.get("/user-profile", follow=True)
|
||||
self.assertContains(response, "Your profile")
|
||||
|
||||
@less_console_noise_decorator
|
||||
def test_user_profile_returns_404_when_feature_off(self):
|
||||
"""test that Your profile returns 404 when profile_feature is off"""
|
||||
with override_flag("profile_feature", active=False):
|
||||
response = self.client.get("/user-profile", follow=True)
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
@less_console_noise_decorator
|
||||
def test_user_profile_back_button_when_coming_from_domain_request(self):
|
||||
"""tests user profile when profile_feature is on,
|
||||
"""tests user profile,
|
||||
and when they are redirected from the domain request page"""
|
||||
with override_flag("profile_feature", active=True):
|
||||
response = self.client.get("/user-profile?redirect=domain-request:")
|
||||
self.assertContains(response, "Your profile")
|
||||
self.assertContains(response, "Go back to your domain request")
|
||||
self.assertNotContains(response, "Back to manage your domains")
|
||||
|
||||
@less_console_noise_decorator
|
||||
def test_domain_detail_profile_feature_on(self):
|
||||
"""test that domain detail view when profile_feature is on"""
|
||||
with override_flag("profile_feature", active=True):
|
||||
def test_domain_detail_contains_your_profile(self):
|
||||
"""Tests that the domain detail view contains 'your profile' rather than 'your contact information'"""
|
||||
response = self.client.get(reverse("domain", args=[self.domain.pk]))
|
||||
self.assertContains(response, "Your profile")
|
||||
self.assertNotContains(response, "Your contact information")
|
||||
|
||||
@less_console_noise_decorator
|
||||
def test_request_when_profile_feature_on(self):
|
||||
"""test that Your profile is in request page when profile feature is on"""
|
||||
|
||||
contact_user, _ = Contact.objects.get_or_create(
|
||||
first_name="Hank",
|
||||
last_name="McFakerson",
|
||||
)
|
||||
site = DraftDomain.objects.create(name="igorville.gov")
|
||||
domain_request = DomainRequest.objects.create(
|
||||
creator=self.user,
|
||||
requested_domain=site,
|
||||
status=DomainRequest.DomainRequestStatus.SUBMITTED,
|
||||
senior_official=contact_user,
|
||||
)
|
||||
with override_flag("profile_feature", active=True):
|
||||
response = self.client.get(f"/domain-request/{domain_request.id}", follow=True)
|
||||
self.assertContains(response, "Your profile")
|
||||
response = self.client.get(f"/domain-request/{domain_request.id}/withdraw", follow=True)
|
||||
self.assertContains(response, "Your profile")
|
||||
def test_domain_your_contact_information(self):
|
||||
"""test that your contact information is not accessible"""
|
||||
response = self.client.get(f"/domain/{self.domain.id}/your-contact-information", follow=True)
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
@less_console_noise_decorator
|
||||
def test_request_when_profile_feature_off(self):
|
||||
"""test that Your profile is not in request page when profile feature is off"""
|
||||
def test_profile_request_page(self):
|
||||
"""test that your profile is in request"""
|
||||
|
||||
contact_user, _ = Contact.objects.get_or_create(
|
||||
first_name="Hank",
|
||||
|
@ -1034,20 +960,16 @@ class UserProfileTests(TestWithUser, WebTest):
|
|||
status=DomainRequest.DomainRequestStatus.SUBMITTED,
|
||||
senior_official=contact_user,
|
||||
)
|
||||
with override_flag("profile_feature", active=False):
|
||||
|
||||
response = self.client.get(f"/domain-request/{domain_request.id}", follow=True)
|
||||
self.assertNotContains(response, "Your profile")
|
||||
self.assertContains(response, "Your profile")
|
||||
response = self.client.get(f"/domain-request/{domain_request.id}/withdraw", follow=True)
|
||||
self.assertNotContains(response, "Your profile")
|
||||
# cleanup
|
||||
domain_request.delete()
|
||||
site.delete()
|
||||
self.assertContains(response, "Your profile")
|
||||
|
||||
@less_console_noise_decorator
|
||||
def test_user_profile_form_submission(self):
|
||||
"""test user profile form submission"""
|
||||
self.app.set_user(self.user.username)
|
||||
with override_flag("profile_feature", active=True):
|
||||
profile_page = self.app.get(reverse("user-profile"))
|
||||
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
||||
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||
|
|
|
@ -723,7 +723,7 @@ class TestDomainManagers(TestDomainOverview):
|
|||
email_address = "mayor@igorville.gov"
|
||||
invitation, _ = DomainInvitation.objects.get_or_create(domain=self.domain, email=email_address)
|
||||
|
||||
other_user = User()
|
||||
other_user = create_user()
|
||||
other_user.save()
|
||||
self.client.force_login(other_user)
|
||||
mock_client = MagicMock()
|
||||
|
@ -737,6 +737,12 @@ class TestDomainManagers(TestDomainOverview):
|
|||
def test_domain_invitation_flow(self):
|
||||
"""Send an invitation to a new user, log in and load the dashboard."""
|
||||
email_address = "mayor@igorville.gov"
|
||||
username = "mayor"
|
||||
first_name = "First"
|
||||
last_name = "Last"
|
||||
title = "title"
|
||||
phone = "8080102431"
|
||||
title = "title"
|
||||
User.objects.filter(email=email_address).delete()
|
||||
|
||||
add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id}))
|
||||
|
@ -752,7 +758,9 @@ class TestDomainManagers(TestDomainOverview):
|
|||
add_page.form.submit()
|
||||
|
||||
# user was invited, create them
|
||||
new_user = User.objects.create(username=email_address, email=email_address)
|
||||
new_user = User.objects.create(
|
||||
username=username, email=email_address, first_name=first_name, last_name=last_name, title=title, phone=phone
|
||||
)
|
||||
# log them in to `self.app`
|
||||
self.app.set_user(new_user.username)
|
||||
# and manually call the on each login callback
|
||||
|
@ -1298,7 +1306,9 @@ class TestDomainOrganization(TestDomainOverview):
|
|||
"""Can load domain's org name and mailing address page."""
|
||||
page = self.client.get(reverse("domain-org-name-address", kwargs={"pk": self.domain.id}))
|
||||
# once on the sidebar, once in the page title, once as H1
|
||||
self.assertContains(page, "Organization name and mailing address", count=4)
|
||||
self.assertContains(page, "/org-name-address")
|
||||
self.assertContains(page, "Organization name and mailing address")
|
||||
self.assertContains(page, "Organization</h1>")
|
||||
|
||||
@less_console_noise_decorator
|
||||
def test_domain_org_name_address_content(self):
|
||||
|
@ -1607,7 +1617,7 @@ class TestDomainSuborganization(TestDomainOverview):
|
|||
|
||||
# Test for the title change
|
||||
self.assertContains(page, "Suborganization")
|
||||
self.assertNotContains(page, "Organization name")
|
||||
self.assertNotContains(page, "Organization")
|
||||
|
||||
# Test for the good value
|
||||
self.assertContains(page, "Ice Cream")
|
||||
|
|
|
@ -200,7 +200,7 @@ class TestPortfolio(WebTest):
|
|||
# Assert the response is a 200
|
||||
self.assertEqual(response.status_code, 200)
|
||||
# The label for Federal agency will always be a h4
|
||||
self.assertContains(response, '<h4 class="read-only-label">Federal agency</h4>')
|
||||
self.assertContains(response, '<h4 class="read-only-label">Organization name</h4>')
|
||||
# The read only label for city will be a h4
|
||||
self.assertContains(response, '<h4 class="read-only-label">City</h4>')
|
||||
self.assertNotContains(response, 'for="id_city"')
|
||||
|
@ -225,10 +225,10 @@ class TestPortfolio(WebTest):
|
|||
# Assert the response is a 200
|
||||
self.assertEqual(response.status_code, 200)
|
||||
# The label for Federal agency will always be a h4
|
||||
self.assertContains(response, '<h4 class="read-only-label">Federal agency</h4>')
|
||||
self.assertContains(response, '<h4 class="read-only-label">Organization name</h4>')
|
||||
# The read only label for city will be a h4
|
||||
self.assertNotContains(response, '<h4 class="read-only-label">City</h4>')
|
||||
self.assertNotContains(response, '<p class="read-only-value">Los Angeles</p>>')
|
||||
self.assertNotContains(response, '<p class="read-only-value">Los Angeles</p>')
|
||||
self.assertContains(response, 'for="id_city"')
|
||||
|
||||
@less_console_noise_decorator
|
||||
|
@ -342,9 +342,7 @@ class TestPortfolio(WebTest):
|
|||
user=self.user, portfolio=self.portfolio, additional_permissions=portfolio_additional_permissions
|
||||
)
|
||||
page = self.app.get(reverse("organization"))
|
||||
self.assertContains(
|
||||
page, "The name of your federal agency will be publicly listed as the domain registrant."
|
||||
)
|
||||
self.assertContains(page, "The name of your organization will be publicly listed as the domain registrant.")
|
||||
|
||||
@less_console_noise_decorator
|
||||
def test_domain_org_name_address_content(self):
|
||||
|
|
|
@ -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'"""
|
||||
|
@ -2206,7 +2247,6 @@ class DomainRequestTests(TestWithUser, WebTest):
|
|||
senior_official = domain_request.senior_official
|
||||
self.assertEquals("Testy2", senior_official.first_name)
|
||||
|
||||
@override_flag("profile_feature", active=True)
|
||||
@less_console_noise_decorator
|
||||
def test_edit_creator_in_place(self):
|
||||
"""When you:
|
||||
|
|
|
@ -458,3 +458,81 @@ class GetRequestsJsonTest(TestWithUser, WebTest):
|
|||
# Ensure no approved requests are included
|
||||
for domain_request in data["domain_requests"]:
|
||||
self.assertNotEqual(domain_request["status"], DomainRequest.DomainRequestStatus.APPROVED)
|
||||
|
||||
def test_search(self):
|
||||
"""Tests our search functionality. We expect that search filters on creator only when we are in a portfolio"""
|
||||
# Test search for domain name
|
||||
response = self.app.get(reverse("get_domain_requests_json"), {"search_term": "lamb"})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
data = response.json
|
||||
self.assertEqual(len(data["domain_requests"]), 1)
|
||||
|
||||
requested_domain = data["domain_requests"][0]["requested_domain"]
|
||||
self.assertEqual(requested_domain, "lamb-chops.gov")
|
||||
|
||||
# Test search for 'New domain request'
|
||||
response = self.app.get(reverse("get_domain_requests_json"), {"search_term": "new domain"})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
data = response.json
|
||||
self.assertTrue(any(req["requested_domain"] is None for req in data["domain_requests"]))
|
||||
|
||||
# Test search with portfolio (including creator search)
|
||||
self.client.force_login(self.user)
|
||||
with override_flag("organization_feature", active=True), override_flag("organization_requests", active=True):
|
||||
user_perm, _ = UserPortfolioPermission.objects.get_or_create(
|
||||
user=self.user,
|
||||
portfolio=self.portfolio,
|
||||
roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN],
|
||||
)
|
||||
response = self.app.get(
|
||||
reverse("get_domain_requests_json"), {"search_term": "info", "portfolio": self.portfolio.id}
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
data = response.json
|
||||
self.assertTrue(any(req["creator"].startswith("info") for req in data["domain_requests"]))
|
||||
|
||||
# Test search without portfolio (should not search on creator)
|
||||
with override_flag("organization_feature", active=False), override_flag("organization_requests", active=False):
|
||||
user_perm.delete()
|
||||
response = self.app.get(reverse("get_domain_requests_json"), {"search_term": "info"})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
data = response.json
|
||||
self.assertEqual(len(data["domain_requests"]), 0)
|
||||
|
||||
@override_flag("organization_feature", active=True)
|
||||
@override_flag("organization_requests", active=True)
|
||||
def test_status_filter(self):
|
||||
"""Test that status filtering works properly"""
|
||||
# Test a single status
|
||||
response = self.app.get(reverse("get_domain_requests_json"), {"status": "started"})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
data = response.json
|
||||
self.assertTrue(all(req["status"] == "Started" for req in data["domain_requests"]))
|
||||
|
||||
# Test an invalid status
|
||||
response = self.app.get(reverse("get_domain_requests_json"), {"status": "approved"})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
data = response.json
|
||||
self.assertEqual(len(data["domain_requests"]), 0)
|
||||
|
||||
@override_flag("organization_feature", active=True)
|
||||
@override_flag("organization_requests", active=True)
|
||||
def test_combined_filtering_and_sorting(self):
|
||||
"""Test that combining filters and sorting works properly"""
|
||||
user_perm, _ = UserPortfolioPermission.objects.get_or_create(
|
||||
user=self.user,
|
||||
portfolio=self.portfolio,
|
||||
roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN],
|
||||
)
|
||||
self.client.force_login(self.user)
|
||||
response = self.app.get(
|
||||
reverse("get_domain_requests_json"),
|
||||
{"search_term": "beef", "status": "started", "portfolio": self.portfolio.id},
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
data = response.json
|
||||
self.assertTrue(all("beef" in req["requested_domain"] for req in data["domain_requests"]))
|
||||
self.assertTrue(all(req["status"] == "Started" for req in data["domain_requests"]))
|
||||
created_at_dates = [req["created_at"] for req in data["domain_requests"]]
|
||||
self.assertEqual(created_at_dates, sorted(created_at_dates, reverse=True))
|
||||
user_perm.delete()
|
||||
|
|
|
@ -204,7 +204,7 @@ class DomainView(DomainBaseView):
|
|||
|
||||
|
||||
class DomainOrgNameAddressView(DomainFormBaseView):
|
||||
"""Organization name and mailing address view"""
|
||||
"""Organization view"""
|
||||
|
||||
model = Domain
|
||||
template_name = "domain_org_name_address.html"
|
||||
|
|
|
@ -82,7 +82,7 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView):
|
|||
Step.TRIBAL_GOVERNMENT: _("Tribal government"),
|
||||
Step.ORGANIZATION_FEDERAL: _("Federal government branch"),
|
||||
Step.ORGANIZATION_ELECTION: _("Election office"),
|
||||
Step.ORGANIZATION_CONTACT: _("Organization name and mailing address"),
|
||||
Step.ORGANIZATION_CONTACT: _("Organization"),
|
||||
Step.ABOUT_YOUR_ORGANIZATION: _("About your organization"),
|
||||
Step.SENIOR_OFFICIAL: _("Senior official"),
|
||||
Step.CURRENT_SITES: _("Current websites"),
|
||||
|
|
|
@ -20,6 +20,7 @@ def get_domain_requests_json(request):
|
|||
unfiltered_total = objects.count()
|
||||
|
||||
objects = apply_search(objects, request)
|
||||
objects = apply_status_filter(objects, request)
|
||||
objects = apply_sorting(objects, request)
|
||||
|
||||
paginator = Paginator(objects, 10)
|
||||
|
@ -63,6 +64,7 @@ def get_domain_request_ids_from_request(request):
|
|||
|
||||
def apply_search(queryset, request):
|
||||
search_term = request.GET.get("search_term")
|
||||
is_portfolio = request.GET.get("portfolio")
|
||||
|
||||
if search_term:
|
||||
search_term_lower = search_term.lower()
|
||||
|
@ -75,11 +77,34 @@ def apply_search(queryset, request):
|
|||
queryset = queryset.filter(
|
||||
Q(requested_domain__name__icontains=search_term) | Q(requested_domain__isnull=True)
|
||||
)
|
||||
elif is_portfolio:
|
||||
queryset = queryset.filter(
|
||||
Q(requested_domain__name__icontains=search_term)
|
||||
| Q(creator__first_name__icontains=search_term)
|
||||
| Q(creator__last_name__icontains=search_term)
|
||||
| Q(creator__email__icontains=search_term)
|
||||
)
|
||||
# For non org users
|
||||
else:
|
||||
queryset = queryset.filter(Q(requested_domain__name__icontains=search_term))
|
||||
return queryset
|
||||
|
||||
|
||||
def apply_status_filter(queryset, request):
|
||||
status_param = request.GET.get("status")
|
||||
if status_param:
|
||||
status_list = status_param.split(",")
|
||||
statuses = [status for status in status_list if status in DomainRequest.DomainRequestStatus.values]
|
||||
# Construct Q objects for statuses that can be queried through ORM
|
||||
status_query = Q()
|
||||
if statuses:
|
||||
status_query |= Q(status__in=statuses)
|
||||
# Apply the combined query
|
||||
queryset = queryset.filter(status_query)
|
||||
|
||||
return queryset
|
||||
|
||||
|
||||
def apply_sorting(queryset, request):
|
||||
sort_by = request.GET.get("sort_by", "id") # Default to 'id'
|
||||
order = request.GET.get("order", "asc") # Default to 'asc'
|
||||
|
|
|
@ -11,7 +11,6 @@ from django.urls import NoReverseMatch, reverse
|
|||
from registrar.models.user import User
|
||||
from registrar.models.utility.generic_helper import replace_url_queryparams
|
||||
from registrar.views.utility.permission_views import UserProfilePermissionView
|
||||
from waffle.decorators import waffle_flag
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -46,7 +45,6 @@ class UserProfileView(UserProfilePermissionView, FormMixin):
|
|||
|
||||
return self.render_to_response(context)
|
||||
|
||||
@waffle_flag("profile_feature") # type: ignore
|
||||
def dispatch(self, request, *args, **kwargs): # type: ignore
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue