mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-07-26 04:28:39 +02:00
Merge branch 'main' into litterbox/3280-design-review
This commit is contained in:
commit
ad2c04d330
20 changed files with 327 additions and 103 deletions
|
@ -1678,22 +1678,25 @@ class DomainInformationAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
|||
parameter_name = "converted_generic_orgs"
|
||||
|
||||
def lookups(self, request, model_admin):
|
||||
converted_generic_orgs = set()
|
||||
# Annotate the queryset to avoid Python-side iteration
|
||||
queryset = (
|
||||
DomainInformation.objects.annotate(
|
||||
converted_generic_org=Case(
|
||||
When(portfolio__organization_type__isnull=False, then="portfolio__organization_type"),
|
||||
When(portfolio__isnull=True, generic_org_type__isnull=False, then="generic_org_type"),
|
||||
default=Value(""),
|
||||
output_field=CharField(),
|
||||
)
|
||||
)
|
||||
.values_list("converted_generic_org", flat=True)
|
||||
.distinct()
|
||||
)
|
||||
|
||||
# Populate the set with tuples of (value, display value)
|
||||
for domain_info in DomainInformation.objects.all():
|
||||
converted_generic_org = domain_info.converted_generic_org_type # Actual value
|
||||
converted_generic_org_display = domain_info.converted_generic_org_type_display # Display value
|
||||
# Filter out empty results and return sorted list of unique values
|
||||
return sorted([(org, DomainRequest.OrganizationChoices.get_org_label(org)) for org in queryset if org])
|
||||
|
||||
if converted_generic_org:
|
||||
converted_generic_orgs.add((converted_generic_org, converted_generic_org_display)) # Value, Display
|
||||
|
||||
# Sort the set by display value
|
||||
return sorted(converted_generic_orgs, key=lambda x: x[1]) # x[1] is the display value
|
||||
|
||||
# Filter queryset
|
||||
def queryset(self, request, queryset):
|
||||
if self.value(): # Check if a generic org is selected in the filter
|
||||
if self.value():
|
||||
return queryset.filter(
|
||||
Q(portfolio__organization_type=self.value())
|
||||
| Q(portfolio__isnull=True, generic_org_type=self.value())
|
||||
|
@ -2031,22 +2034,25 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
|||
parameter_name = "converted_generic_orgs"
|
||||
|
||||
def lookups(self, request, model_admin):
|
||||
converted_generic_orgs = set()
|
||||
# Annotate the queryset to avoid Python-side iteration
|
||||
queryset = (
|
||||
DomainRequest.objects.annotate(
|
||||
converted_generic_org=Case(
|
||||
When(portfolio__organization_type__isnull=False, then="portfolio__organization_type"),
|
||||
When(portfolio__isnull=True, generic_org_type__isnull=False, then="generic_org_type"),
|
||||
default=Value(""),
|
||||
output_field=CharField(),
|
||||
)
|
||||
)
|
||||
.values_list("converted_generic_org", flat=True)
|
||||
.distinct()
|
||||
)
|
||||
|
||||
# Populate the set with tuples of (value, display value)
|
||||
for domain_request in DomainRequest.objects.all():
|
||||
converted_generic_org = domain_request.converted_generic_org_type # Actual value
|
||||
converted_generic_org_display = domain_request.converted_generic_org_type_display # Display value
|
||||
# Filter out empty results and return sorted list of unique values
|
||||
return sorted([(org, DomainRequest.OrganizationChoices.get_org_label(org)) for org in queryset if org])
|
||||
|
||||
if converted_generic_org:
|
||||
converted_generic_orgs.add((converted_generic_org, converted_generic_org_display)) # Value, Display
|
||||
|
||||
# Sort the set by display value
|
||||
return sorted(converted_generic_orgs, key=lambda x: x[1]) # x[1] is the display value
|
||||
|
||||
# Filter queryset
|
||||
def queryset(self, request, queryset):
|
||||
if self.value(): # Check if a generic org is selected in the filter
|
||||
if self.value():
|
||||
return queryset.filter(
|
||||
Q(portfolio__organization_type=self.value())
|
||||
| Q(portfolio__isnull=True, generic_org_type=self.value())
|
||||
|
@ -2062,24 +2068,39 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
|||
parameter_name = "converted_federal_types"
|
||||
|
||||
def lookups(self, request, model_admin):
|
||||
converted_federal_types = set()
|
||||
|
||||
# Populate the set with tuples of (value, display value)
|
||||
for domain_request in DomainRequest.objects.all():
|
||||
converted_federal_type = domain_request.converted_federal_type # Actual value
|
||||
converted_federal_type_display = domain_request.converted_federal_type_display # Display value
|
||||
|
||||
if converted_federal_type:
|
||||
converted_federal_types.add(
|
||||
(converted_federal_type, converted_federal_type_display) # Value, Display
|
||||
# Annotate the queryset for efficient filtering
|
||||
queryset = (
|
||||
DomainRequest.objects.annotate(
|
||||
converted_federal_type=Case(
|
||||
When(
|
||||
portfolio__isnull=False,
|
||||
portfolio__federal_agency__federal_type__isnull=False,
|
||||
then="portfolio__federal_agency__federal_type",
|
||||
),
|
||||
When(
|
||||
portfolio__isnull=True,
|
||||
federal_agency__federal_type__isnull=False,
|
||||
then="federal_agency__federal_type",
|
||||
),
|
||||
default=Value(""),
|
||||
output_field=CharField(),
|
||||
)
|
||||
)
|
||||
.values_list("converted_federal_type", flat=True)
|
||||
.distinct()
|
||||
)
|
||||
|
||||
# Sort the set by display value
|
||||
return sorted(converted_federal_types, key=lambda x: x[1]) # x[1] is the display value
|
||||
# Filter out empty values and return sorted unique entries
|
||||
return sorted(
|
||||
[
|
||||
(federal_type, BranchChoices.get_branch_label(federal_type))
|
||||
for federal_type in queryset
|
||||
if federal_type
|
||||
]
|
||||
)
|
||||
|
||||
# Filter queryset
|
||||
def queryset(self, request, queryset):
|
||||
if self.value(): # Check if a federal type is selected in the filter
|
||||
if self.value():
|
||||
return queryset.filter(
|
||||
Q(portfolio__federal_agency__federal_type=self.value())
|
||||
| Q(portfolio__isnull=True, federal_type=self.value())
|
||||
|
@ -3226,59 +3247,86 @@ class DomainAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
|||
parameter_name = "converted_generic_orgs"
|
||||
|
||||
def lookups(self, request, model_admin):
|
||||
converted_generic_orgs = set()
|
||||
# Annotate the queryset to avoid Python-side iteration
|
||||
queryset = (
|
||||
Domain.objects.annotate(
|
||||
converted_generic_org=Case(
|
||||
When(
|
||||
domain_info__isnull=False,
|
||||
domain_info__portfolio__organization_type__isnull=False,
|
||||
then="domain_info__portfolio__organization_type",
|
||||
),
|
||||
When(
|
||||
domain_info__isnull=False,
|
||||
domain_info__portfolio__isnull=True,
|
||||
domain_info__generic_org_type__isnull=False,
|
||||
then="domain_info__generic_org_type",
|
||||
),
|
||||
default=Value(""),
|
||||
output_field=CharField(),
|
||||
)
|
||||
)
|
||||
.values_list("converted_generic_org", flat=True)
|
||||
.distinct()
|
||||
)
|
||||
|
||||
# Populate the set with tuples of (value, display value)
|
||||
for domain_info in DomainInformation.objects.all():
|
||||
converted_generic_org = domain_info.converted_generic_org_type # Actual value
|
||||
converted_generic_org_display = domain_info.converted_generic_org_type_display # Display value
|
||||
# Filter out empty results and return sorted list of unique values
|
||||
return sorted([(org, DomainRequest.OrganizationChoices.get_org_label(org)) for org in queryset if org])
|
||||
|
||||
if converted_generic_org:
|
||||
converted_generic_orgs.add((converted_generic_org, converted_generic_org_display)) # Value, Display
|
||||
|
||||
# Sort the set by display value
|
||||
return sorted(converted_generic_orgs, key=lambda x: x[1]) # x[1] is the display value
|
||||
|
||||
# Filter queryset
|
||||
def queryset(self, request, queryset):
|
||||
if self.value(): # Check if a generic org is selected in the filter
|
||||
if self.value():
|
||||
return queryset.filter(
|
||||
Q(domain_info__portfolio__organization_type=self.value())
|
||||
| Q(domain_info__portfolio__isnull=True, domain_info__generic_org_type=self.value())
|
||||
)
|
||||
|
||||
return queryset
|
||||
|
||||
class FederalTypeFilter(admin.SimpleListFilter):
|
||||
"""Custom Federal Type filter that accomodates portfolio feature.
|
||||
If we have a portfolio, use the portfolio's federal type. If not, use the
|
||||
federal type in the Domain Information object."""
|
||||
organization in the Domain Request object."""
|
||||
|
||||
title = "federal type"
|
||||
parameter_name = "converted_federal_types"
|
||||
|
||||
def lookups(self, request, model_admin):
|
||||
converted_federal_types = set()
|
||||
|
||||
# Populate the set with tuples of (value, display value)
|
||||
for domain_info in DomainInformation.objects.all():
|
||||
converted_federal_type = domain_info.converted_federal_type # Actual value
|
||||
converted_federal_type_display = domain_info.converted_federal_type_display # Display value
|
||||
|
||||
if converted_federal_type:
|
||||
converted_federal_types.add(
|
||||
(converted_federal_type, converted_federal_type_display) # Value, Display
|
||||
# Annotate the queryset for efficient filtering
|
||||
queryset = (
|
||||
Domain.objects.annotate(
|
||||
converted_federal_type=Case(
|
||||
When(
|
||||
domain_info__isnull=False,
|
||||
domain_info__portfolio__isnull=False,
|
||||
then=F("domain_info__portfolio__federal_agency__federal_type"),
|
||||
),
|
||||
When(
|
||||
domain_info__isnull=False,
|
||||
domain_info__portfolio__isnull=True,
|
||||
domain_info__federal_type__isnull=False,
|
||||
then="domain_info__federal_agency__federal_type",
|
||||
),
|
||||
default=Value(""),
|
||||
output_field=CharField(),
|
||||
)
|
||||
)
|
||||
.values_list("converted_federal_type", flat=True)
|
||||
.distinct()
|
||||
)
|
||||
|
||||
# Sort the set by display value
|
||||
return sorted(converted_federal_types, key=lambda x: x[1]) # x[1] is the display value
|
||||
# Filter out empty values and return sorted unique entries
|
||||
return sorted(
|
||||
[
|
||||
(federal_type, BranchChoices.get_branch_label(federal_type))
|
||||
for federal_type in queryset
|
||||
if federal_type
|
||||
]
|
||||
)
|
||||
|
||||
# Filter queryset
|
||||
def queryset(self, request, queryset):
|
||||
if self.value(): # Check if a federal type is selected in the filter
|
||||
if self.value():
|
||||
return queryset.filter(
|
||||
Q(domain_info__portfolio__federal_agency__federal_type=self.value())
|
||||
| Q(domain_info__portfolio__isnull=True, domain_info__federal_agency__federal_type=self.value())
|
||||
Q(domain_info__portfolio__federal_type=self.value())
|
||||
| Q(domain_info__portfolio__isnull=True, domain_info__federal_type=self.value())
|
||||
)
|
||||
return queryset
|
||||
|
||||
|
|
|
@ -323,22 +323,50 @@ class DomainRequestFixture:
|
|||
cls._create_domain_requests(users)
|
||||
|
||||
@classmethod
|
||||
def _create_domain_requests(cls, users):
|
||||
def _create_domain_requests(cls, users): # noqa: C901
|
||||
"""Creates DomainRequests given a list of users."""
|
||||
total_domain_requests_to_make = len(users) # 100000
|
||||
|
||||
# Check if the database is already populated with the desired
|
||||
# number of entries.
|
||||
# (Prevents re-adding more entries to an already populated database,
|
||||
# which happens when restarting Docker src)
|
||||
domain_requests_already_made = DomainRequest.objects.count()
|
||||
|
||||
domain_requests_to_create = []
|
||||
for user in users:
|
||||
for request_data in cls.DOMAINREQUESTS:
|
||||
# Prepare DomainRequest objects
|
||||
try:
|
||||
domain_request = DomainRequest(
|
||||
creator=user,
|
||||
organization_name=request_data["organization_name"],
|
||||
)
|
||||
cls._set_non_foreign_key_fields(domain_request, request_data)
|
||||
cls._set_foreign_key_fields(domain_request, request_data, user)
|
||||
domain_requests_to_create.append(domain_request)
|
||||
except Exception as e:
|
||||
logger.warning(e)
|
||||
if domain_requests_already_made < total_domain_requests_to_make:
|
||||
for user in users:
|
||||
for request_data in cls.DOMAINREQUESTS:
|
||||
# Prepare DomainRequest objects
|
||||
try:
|
||||
domain_request = DomainRequest(
|
||||
creator=user,
|
||||
organization_name=request_data["organization_name"],
|
||||
)
|
||||
cls._set_non_foreign_key_fields(domain_request, request_data)
|
||||
cls._set_foreign_key_fields(domain_request, request_data, user)
|
||||
domain_requests_to_create.append(domain_request)
|
||||
except Exception as e:
|
||||
logger.warning(e)
|
||||
|
||||
num_additional_requests_to_make = (
|
||||
total_domain_requests_to_make - domain_requests_already_made - len(domain_requests_to_create)
|
||||
)
|
||||
if num_additional_requests_to_make > 0:
|
||||
for _ in range(num_additional_requests_to_make):
|
||||
random_user = random.choice(users) # nosec
|
||||
try:
|
||||
random_request_type = random.choice(cls.DOMAINREQUESTS) # nosec
|
||||
# Prepare DomainRequest objects
|
||||
domain_request = DomainRequest(
|
||||
creator=random_user,
|
||||
organization_name=random_request_type["organization_name"],
|
||||
)
|
||||
cls._set_non_foreign_key_fields(domain_request, random_request_type)
|
||||
cls._set_foreign_key_fields(domain_request, random_request_type, random_user)
|
||||
domain_requests_to_create.append(domain_request)
|
||||
except Exception as e:
|
||||
logger.warning(f"Error creating random domain request: {e}")
|
||||
|
||||
# Bulk create domain requests
|
||||
cls._bulk_create_requests(domain_requests_to_create)
|
||||
|
|
|
@ -9,6 +9,7 @@ from django.utils import timezone
|
|||
from registrar.models.domain import Domain
|
||||
from registrar.models.federal_agency import FederalAgency
|
||||
from registrar.models.utility.generic_helper import CreateOrUpdateOrganizationTypeHelper
|
||||
from registrar.models.utility.portfolio_helper import UserPortfolioPermissionChoices
|
||||
from registrar.utility.errors import FSMDomainRequestError, FSMErrorCodes
|
||||
from registrar.utility.constants import BranchChoices
|
||||
from auditlog.models import LogEntry
|
||||
|
@ -903,6 +904,7 @@ class DomainRequest(TimeStampedModel):
|
|||
email_template,
|
||||
email_template_subject,
|
||||
bcc_address="",
|
||||
cc_addresses: list[str] = [],
|
||||
context=None,
|
||||
send_email=True,
|
||||
wrap_email=False,
|
||||
|
@ -955,12 +957,20 @@ class DomainRequest(TimeStampedModel):
|
|||
|
||||
if custom_email_content:
|
||||
context["custom_email_content"] = custom_email_content
|
||||
|
||||
if self.requesting_entity_is_portfolio() or self.requesting_entity_is_suborganization():
|
||||
portfolio_view_requests_users = self.portfolio.portfolio_users_with_permissions( # type: ignore
|
||||
permissions=[UserPortfolioPermissionChoices.VIEW_ALL_REQUESTS], include_admin=True
|
||||
)
|
||||
cc_addresses = list(portfolio_view_requests_users.values_list("email", flat=True))
|
||||
|
||||
send_templated_email(
|
||||
email_template,
|
||||
email_template_subject,
|
||||
recipient.email,
|
||||
context=context,
|
||||
bcc_address=bcc_address,
|
||||
cc_addresses=cc_addresses,
|
||||
wrap_email=wrap_email,
|
||||
)
|
||||
logger.info(f"The {new_status} email sent to: {recipient.email}")
|
||||
|
|
|
@ -4,6 +4,7 @@ from registrar.models.domain_request import DomainRequest
|
|||
from registrar.models.federal_agency import FederalAgency
|
||||
from registrar.models.user import User
|
||||
from registrar.models.utility.portfolio_helper import UserPortfolioRoleChoices
|
||||
from django.db.models import Q
|
||||
|
||||
from .utility.time_stamped_model import TimeStampedModel
|
||||
|
||||
|
@ -122,6 +123,16 @@ class Portfolio(TimeStampedModel):
|
|||
if self.state_territory != self.StateTerritoryChoices.PUERTO_RICO and self.urbanization:
|
||||
self.urbanization = None
|
||||
|
||||
# If the org type is federal, and org federal agency is not blank, and is a federal agency
|
||||
# overwrite the organization name with the federal agency's agency
|
||||
if (
|
||||
self.organization_type == self.OrganizationChoices.FEDERAL
|
||||
and self.federal_agency
|
||||
and self.federal_agency != FederalAgency.get_non_federal_agency()
|
||||
and self.federal_agency.agency
|
||||
):
|
||||
self.organization_name = self.federal_agency.agency
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
@property
|
||||
|
@ -144,6 +155,25 @@ class Portfolio(TimeStampedModel):
|
|||
).values_list("user__id", flat=True)
|
||||
return User.objects.filter(id__in=admin_ids)
|
||||
|
||||
def portfolio_users_with_permissions(self, permissions=[], include_admin=False):
|
||||
"""Gets all users with specified additional permissions for this particular portfolio.
|
||||
Returns a queryset of User."""
|
||||
portfolio_users = self.portfolio_users
|
||||
if permissions:
|
||||
if include_admin:
|
||||
portfolio_users = portfolio_users.filter(
|
||||
Q(additional_permissions__overlap=permissions)
|
||||
| Q(
|
||||
roles__overlap=[
|
||||
UserPortfolioRoleChoices.ORGANIZATION_ADMIN,
|
||||
]
|
||||
),
|
||||
)
|
||||
else:
|
||||
portfolio_users = portfolio_users.filter(additional_permissions__overlap=permissions)
|
||||
user_ids = portfolio_users.values_list("user__id", flat=True)
|
||||
return User.objects.filter(id__in=user_ids)
|
||||
|
||||
# == Getters for domains == #
|
||||
def get_domains(self, order_by=None):
|
||||
"""Returns all DomainInformations associated with this portfolio"""
|
||||
|
|
|
@ -4,6 +4,7 @@ Hi, {{ recipient.first_name }}.
|
|||
We've identified an action that you’ll need to complete before we continue reviewing your .gov domain request.
|
||||
|
||||
DOMAIN REQUESTED: {{ domain_request.requested_domain.name }}
|
||||
REQUESTED BY: {{ domain_request.creator.email }}
|
||||
REQUEST RECEIVED ON: {{ domain_request.last_submitted_date|date }}
|
||||
STATUS: Action needed
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ Hi, {{ recipient.first_name }}.
|
|||
We've identified an action that you’ll need to complete before we continue reviewing your .gov domain request.
|
||||
|
||||
DOMAIN REQUESTED: {{ domain_request.requested_domain.name }}
|
||||
REQUESTED BY: {{ domain_request.creator.email }}
|
||||
REQUEST RECEIVED ON: {{ domain_request.last_submitted_date|date }}
|
||||
STATUS: Action needed
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ Hi, {{ recipient.first_name }}.
|
|||
We've identified an action that you’ll need to complete before we continue reviewing your .gov domain request.
|
||||
|
||||
DOMAIN REQUESTED: {{ domain_request.requested_domain.name }}
|
||||
REQUESTED BY: {{ domain_request.creator.email }}
|
||||
REQUEST RECEIVED ON: {{ domain_request.last_submitted_date|date }}
|
||||
STATUS: Action needed
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ Hi, {{ recipient.first_name }}.
|
|||
We've identified an action that you’ll need to complete before we continue reviewing your .gov domain request.
|
||||
|
||||
DOMAIN REQUESTED: {{ domain_request.requested_domain.name }}
|
||||
REQUESTED BY: {{ domain_request.creator.email }}
|
||||
REQUEST RECEIVED ON: {{ domain_request.last_submitted_date|date }}
|
||||
STATUS: Action needed
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ Hi, {{ recipient.first_name }}.
|
|||
Your .gov domain request has been withdrawn and will not be reviewed by our team.
|
||||
|
||||
DOMAIN REQUESTED: {{ domain_request.requested_domain.name }}
|
||||
REQUESTED BY: {{ domain_request.creator.email }}
|
||||
REQUEST RECEIVED ON: {{ domain_request.last_submitted_date|date }}
|
||||
STATUS: Withdrawn
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ Hi, {{ recipient.first_name }}.
|
|||
Congratulations! Your .gov domain request has been approved.
|
||||
|
||||
DOMAIN REQUESTED: {{ domain_request.requested_domain.name }}
|
||||
REQUESTED BY: {{ domain_request.creator.email }}
|
||||
REQUEST RECEIVED ON: {{ domain_request.last_submitted_date|date }}
|
||||
STATUS: Approved
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ Hi, {{ recipient.first_name }}.
|
|||
Your .gov domain request has been rejected.
|
||||
|
||||
DOMAIN REQUESTED: {{ domain_request.requested_domain.name }}
|
||||
REQUESTED BY: {{ domain_request.creator.email }}
|
||||
REQUEST RECEIVED ON: {{ domain_request.last_submitted_date|date }}
|
||||
STATUS: Rejected
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ Hi, {{ recipient.first_name }}.
|
|||
We received your .gov domain request.
|
||||
|
||||
DOMAIN REQUESTED: {{ domain_request.requested_domain.name }}
|
||||
REQUESTED BY: {{ domain_request.creator.email }}
|
||||
REQUEST RECEIVED ON: {{ domain_request.last_submitted_date|date }}
|
||||
STATUS: Submitted
|
||||
|
||||
|
@ -11,13 +12,15 @@ STATUS: Submitted
|
|||
|
||||
NEXT STEPS
|
||||
We’ll review your request. This review period can take 30 business days. Due to the volume of requests, the wait time is longer than usual. We appreciate your patience.
|
||||
|
||||
{% if is_org_user %}
|
||||
During our review we’ll verify that your requested domain meets our naming requirements.
|
||||
{% else %}
|
||||
During our review, we’ll verify that:
|
||||
- Your organization is eligible for a .gov domain
|
||||
- You work at the organization and/or can make requests on its behalf
|
||||
- Your requested domain meets our naming requirements
|
||||
|
||||
We’ll email you if we have questions. We’ll also email you as soon as we complete our review. You can check the status of your request at any time on the registrar. <https://manage.get.gov>
|
||||
{% endif %}
|
||||
We’ll email you if we have questions. We’ll also email you as soon as we complete our review. You can check the status of your request at any time on the registrar. <https://manage.get.gov>.
|
||||
|
||||
|
||||
NEED TO MAKE CHANGES?
|
||||
|
|
|
@ -779,7 +779,7 @@ class TestDomainAdminWithClient(TestCase):
|
|||
response = self.client.get("/admin/registrar/domain/")
|
||||
# There are 4 template references to Federal (4) plus four references in the table
|
||||
# for our actual domain_request
|
||||
self.assertContains(response, "Federal", count=57)
|
||||
self.assertContains(response, "Federal", count=56)
|
||||
# This may be a bit more robust
|
||||
self.assertContains(response, '<td class="field-converted_generic_org_type">Federal</td>', count=1)
|
||||
# Now let's make sure the long description does not exist
|
||||
|
|
|
@ -662,7 +662,7 @@ class TestDomainRequestAdmin(MockEppLib):
|
|||
response = self.client.get("/admin/registrar/domainrequest/?generic_org_type__exact=federal")
|
||||
# There are 2 template references to Federal (4) and two in the results data
|
||||
# of the request
|
||||
self.assertContains(response, "Federal", count=55)
|
||||
self.assertContains(response, "Federal", count=54)
|
||||
# This may be a bit more robust
|
||||
self.assertContains(response, '<td class="field-converted_generic_org_type">Federal</td>', count=1)
|
||||
# Now let's make sure the long description does not exist
|
||||
|
|
|
@ -2217,6 +2217,11 @@ class TestRemovePortfolios(TestCase):
|
|||
|
||||
def tearDown(self):
|
||||
self.logger_patcher.stop()
|
||||
DomainInformation.objects.all().delete()
|
||||
DomainRequest.objects.all().delete()
|
||||
Suborganization.objects.all().delete()
|
||||
Portfolio.objects.all().delete()
|
||||
User.objects.all().delete()
|
||||
|
||||
@patch("registrar.management.commands.utility.terminal_helper.TerminalHelper.query_yes_no")
|
||||
def test_delete_unlisted_portfolios(self, mock_query_yes_no):
|
||||
|
|
|
@ -2073,13 +2073,18 @@ class TestPortfolio(TestCase):
|
|||
self.user, _ = User.objects.get_or_create(
|
||||
username="intern@igorville.com", email="intern@igorville.com", first_name="Lava", last_name="World"
|
||||
)
|
||||
self.non_federal_agency, _ = FederalAgency.objects.get_or_create(agency="Non-Federal Agency")
|
||||
self.federal_agency, _ = FederalAgency.objects.get_or_create(agency="Federal Agency")
|
||||
super().setUp()
|
||||
|
||||
def tearDown(self):
|
||||
super().tearDown()
|
||||
Portfolio.objects.all().delete()
|
||||
self.federal_agency.delete()
|
||||
# not deleting non_federal_agency so as not to interfere potentially with other tests
|
||||
User.objects.all().delete()
|
||||
|
||||
@less_console_noise_decorator
|
||||
def test_urbanization_field_resets_when_not_puetro_rico(self):
|
||||
"""The urbanization field should only be populated when the state is puetro rico.
|
||||
Otherwise, this field should be empty."""
|
||||
|
@ -2100,6 +2105,7 @@ class TestPortfolio(TestCase):
|
|||
self.assertEqual(portfolio.urbanization, None)
|
||||
self.assertEqual(portfolio.state_territory, DomainRequest.StateTerritoryChoices.ALABAMA)
|
||||
|
||||
@less_console_noise_decorator
|
||||
def test_can_add_urbanization_field(self):
|
||||
"""Ensures that you can populate the urbanization field when conditions are right"""
|
||||
# Create a portfolio that cannot have this field
|
||||
|
@ -2121,6 +2127,32 @@ class TestPortfolio(TestCase):
|
|||
self.assertEqual(portfolio.urbanization, "test123")
|
||||
self.assertEqual(portfolio.state_territory, DomainRequest.StateTerritoryChoices.PUERTO_RICO)
|
||||
|
||||
@less_console_noise_decorator
|
||||
def test_organization_name_updates_for_federal_agency(self):
|
||||
# Create a Portfolio instance with a federal agency
|
||||
portfolio = Portfolio(
|
||||
creator=self.user,
|
||||
organization_type=DomainRequest.OrganizationChoices.FEDERAL,
|
||||
federal_agency=self.federal_agency,
|
||||
)
|
||||
portfolio.save()
|
||||
|
||||
# Assert that organization_name is updated to the federal agency's name
|
||||
self.assertEqual(portfolio.organization_name, "Federal Agency")
|
||||
|
||||
@less_console_noise_decorator
|
||||
def test_organization_name_does_not_update_for_non_federal_agency(self):
|
||||
# Create a Portfolio instance with a non-federal agency
|
||||
portfolio = Portfolio(
|
||||
creator=self.user,
|
||||
organization_type=DomainRequest.OrganizationChoices.FEDERAL,
|
||||
federal_agency=self.non_federal_agency,
|
||||
)
|
||||
portfolio.save()
|
||||
|
||||
# Assert that organization_name remains None
|
||||
self.assertIsNone(portfolio.organization_name)
|
||||
|
||||
|
||||
class TestAllowedEmail(TestCase):
|
||||
"""Tests our allowed email whitelist"""
|
||||
|
|
|
@ -16,7 +16,9 @@ from registrar.models import (
|
|||
AllowedEmail,
|
||||
Portfolio,
|
||||
Suborganization,
|
||||
UserPortfolioPermission,
|
||||
)
|
||||
from registrar.models.utility.portfolio_helper import UserPortfolioRoleChoices
|
||||
|
||||
import boto3_mocking
|
||||
from registrar.utility.constants import BranchChoices
|
||||
|
@ -46,6 +48,14 @@ class TestDomainRequest(TestCase):
|
|||
self.dummy_user_2, _ = User.objects.get_or_create(
|
||||
username="intern@igorville.com", email="intern@igorville.com", first_name="Lava", last_name="World"
|
||||
)
|
||||
|
||||
self.dummy_user_3, _ = User.objects.get_or_create(
|
||||
username="portfolioadmin@igorville.com",
|
||||
email="portfolioadmin@igorville.com",
|
||||
first_name="Portfolio",
|
||||
last_name="Admin",
|
||||
)
|
||||
|
||||
self.started_domain_request = completed_domain_request(
|
||||
status=DomainRequest.DomainRequestStatus.STARTED,
|
||||
name="started.gov",
|
||||
|
@ -273,7 +283,14 @@ class TestDomainRequest(TestCase):
|
|||
self.assertEqual(domain_request.status, domain_request.DomainRequestStatus.SUBMITTED)
|
||||
|
||||
def check_email_sent(
|
||||
self, domain_request, msg, action, expected_count, expected_content=None, expected_email="mayor@igorville.com"
|
||||
self,
|
||||
domain_request,
|
||||
msg,
|
||||
action,
|
||||
expected_count,
|
||||
expected_content=None,
|
||||
expected_email="mayor@igorville.com",
|
||||
expected_cc=[],
|
||||
):
|
||||
"""Check if an email was sent after performing an action."""
|
||||
email_allowed, _ = AllowedEmail.objects.get_or_create(email=expected_email)
|
||||
|
@ -292,6 +309,11 @@ class TestDomainRequest(TestCase):
|
|||
]
|
||||
self.assertEqual(len(sent_emails), expected_count)
|
||||
|
||||
if expected_cc:
|
||||
sent_cc_adddresses = sent_emails[0]["kwargs"]["Destination"]["CcAddresses"]
|
||||
for cc_address in expected_cc:
|
||||
self.assertIn(cc_address, sent_cc_adddresses)
|
||||
|
||||
if expected_content:
|
||||
email_content = sent_emails[0]["kwargs"]["Content"]["Simple"]["Body"]["Text"]["Data"]
|
||||
self.assertIn(expected_content, email_content)
|
||||
|
@ -1074,6 +1096,36 @@ class TestDomainRequest(TestCase):
|
|||
self.assertEqual(domain_request2.generic_org_type, domain_request2.converted_generic_org_type)
|
||||
self.assertEqual(domain_request2.federal_agency, domain_request2.converted_federal_agency)
|
||||
|
||||
@less_console_noise_decorator
|
||||
def test_portfolio_domain_requests_cc_requests_viewers(self):
|
||||
"""test that portfolio domain request emails cc portfolio members who have read requests access"""
|
||||
fed_agency = FederalAgency.objects.filter(agency="Non-Federal Agency").first()
|
||||
portfolio = Portfolio.objects.create(
|
||||
organization_name="Test Portfolio",
|
||||
creator=self.dummy_user_2,
|
||||
federal_agency=fed_agency,
|
||||
organization_type=DomainRequest.OrganizationChoices.FEDERAL,
|
||||
)
|
||||
user_portfolio_permission = UserPortfolioPermission.objects.create( # noqa: F841
|
||||
user=self.dummy_user_3, portfolio=portfolio, roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN]
|
||||
)
|
||||
# Adds cc'ed email in this test's allow list
|
||||
AllowedEmail.objects.create(email="portfolioadmin@igorville.com")
|
||||
|
||||
msg = "Create a domain request and submit it and see if email cc's portfolio admin and members who can view \
|
||||
requests."
|
||||
domain_request = completed_domain_request(
|
||||
name="test.gov", user=self.dummy_user_2, portfolio=portfolio, organization_name="Test Portfolio"
|
||||
)
|
||||
self.check_email_sent(
|
||||
domain_request,
|
||||
msg,
|
||||
"submit",
|
||||
1,
|
||||
expected_email="intern@igorville.com",
|
||||
expected_cc=["portfolioadmin@igorville.com"],
|
||||
)
|
||||
|
||||
|
||||
class TestDomainRequestSuborganization(TestCase):
|
||||
"""Tests for the suborganization fields on domain requests"""
|
||||
|
|
|
@ -255,10 +255,10 @@ class ExportDataTest(MockDbForIndividualTests, MockEppLib):
|
|||
"Organization name,City,State,SO,SO email,"
|
||||
"Security contact email,Domain managers,Invited domain managers\n"
|
||||
"adomain2.gov,Dns needed,(blank),(blank),Federal - Executive,"
|
||||
"Portfolio 1 Federal Agency,,,, ,,(blank),"
|
||||
"Portfolio 1 Federal Agency,Portfolio 1 Federal Agency,,, ,,(blank),"
|
||||
"meoward@rocks.com,squeaker@rocks.com\n"
|
||||
"defaultsecurity.gov,Ready,2023-11-01,(blank),Federal - Executive,"
|
||||
"Portfolio 1 Federal Agency,,,, ,,(blank),"
|
||||
"Portfolio 1 Federal Agency,Portfolio 1 Federal Agency,,, ,,(blank),"
|
||||
'"big_lebowski@dude.co, info@example.com, meoward@rocks.com",woofwardthethird@rocks.com\n'
|
||||
"cdomain11.gov,Ready,2024-04-02,(blank),Federal - Executive,"
|
||||
"World War I Centennial Commission,,,, ,,(blank),"
|
||||
|
@ -280,6 +280,7 @@ class ExportDataTest(MockDbForIndividualTests, MockEppLib):
|
|||
# spaces and leading/trailing whitespace
|
||||
csv_content = csv_content.replace(",,", "").replace(",", "").replace(" ", "").replace("\r\n", "\n").strip()
|
||||
expected_content = expected_content.replace(",,", "").replace(",", "").replace(" ", "").strip()
|
||||
self.maxDiff = None
|
||||
self.assertEqual(csv_content, expected_content)
|
||||
|
||||
@less_console_noise_decorator
|
||||
|
@ -316,9 +317,11 @@ class ExportDataTest(MockDbForIndividualTests, MockEppLib):
|
|||
expected_content = (
|
||||
"Domain name,Status,First ready on,Expiration date,Domain type,Agency,Organization name,"
|
||||
"City,State,SO,SO email,Security contact email,Domain managers,Invited domain managers\n"
|
||||
"adomain2.gov,Dns needed,(blank),(blank),Federal - Executive,Portfolio 1 Federal Agency,,,, ,,(blank),"
|
||||
"adomain2.gov,Dns needed,(blank),(blank),Federal - Executive,Portfolio 1 Federal Agency,"
|
||||
"Portfolio 1 Federal Agency,,, ,,(blank),"
|
||||
'"info@example.com, meoward@rocks.com",squeaker@rocks.com\n'
|
||||
"defaultsecurity.gov,Ready,2023-11-01,(blank),Federal - Executive,Portfolio 1 Federal Agency,,,, ,,(blank),"
|
||||
"defaultsecurity.gov,Ready,2023-11-01,(blank),Federal - Executive,Portfolio 1 Federal Agency,"
|
||||
"Portfolio 1 Federal Agency,,, ,,(blank),"
|
||||
'"big_lebowski@dude.co, info@example.com, meoward@rocks.com",woofwardthethird@rocks.com\n'
|
||||
)
|
||||
|
||||
|
@ -326,6 +329,7 @@ class ExportDataTest(MockDbForIndividualTests, MockEppLib):
|
|||
# spaces and leading/trailing whitespace
|
||||
csv_content = csv_content.replace(",,", "").replace(",", "").replace(" ", "").replace("\r\n", "\n").strip()
|
||||
expected_content = expected_content.replace(",,", "").replace(",", "").replace(" ", "").strip()
|
||||
self.maxDiff = None
|
||||
self.assertEqual(csv_content, expected_content)
|
||||
|
||||
@less_console_noise_decorator
|
||||
|
@ -587,7 +591,7 @@ class ExportDataTest(MockDbForIndividualTests, MockEppLib):
|
|||
expected_content = (
|
||||
"Domain name,Domain type,Agency,Organization name,City,"
|
||||
"State,Status,Expiration date, Deleted\n"
|
||||
"cdomain1.gov,Federal-Executive,Portfolio1FederalAgency,Ready,(blank)\n"
|
||||
"cdomain1.gov,Federal-Executive,Portfolio1FederalAgency,Portfolio1FederalAgency,Ready,(blank)\n"
|
||||
"adomain10.gov,Federal,ArmedForcesRetirementHome,Ready,(blank)\n"
|
||||
"cdomain11.gov,Federal-Executive,WorldWarICentennialCommission,Ready,(blank)\n"
|
||||
"zdomain12.gov,Interstate,Ready,(blank)\n"
|
||||
|
@ -601,6 +605,7 @@ class ExportDataTest(MockDbForIndividualTests, MockEppLib):
|
|||
csv_content.replace(",,", "").replace(",", "").replace(" ", "").replace("\r\n", "\n").strip()
|
||||
)
|
||||
expected_content = expected_content.replace(",,", "").replace(",", "").replace(" ", "").strip()
|
||||
self.maxDiff = None
|
||||
self.assertEqual(csv_content, expected_content)
|
||||
|
||||
@less_console_noise_decorator
|
||||
|
@ -780,9 +785,11 @@ class ExportDataTest(MockDbForIndividualTests, MockEppLib):
|
|||
"city5.gov,Approved,Federal,No,Executive,,Testorg,N/A,,NY,2,requested_suborg,SanFran,CA,,,,,1,0,"
|
||||
"city1.gov,Testy,Tester,testy@town.com,Chief Tester,Purpose of the site,There is more,"
|
||||
"Testy Tester testy2@town.com,,city.com,\n"
|
||||
"city2.gov,In review,Federal,Yes,Executive,Portfolio 1 Federal Agency,,N/A,,,2,SubOrg 1,,,,,,,0,"
|
||||
"1,city1.gov,,,,,Purpose of the site,There is more,Testy Tester testy2@town.com,,city.com,\n"
|
||||
"city3.gov,Submitted,Federal,Yes,Executive,Portfolio 1 Federal Agency,,N/A,,,2,,,,,,,,0,1,"
|
||||
"city2.gov,In review,Federal,Yes,Executive,Portfolio 1 Federal Agency,Portfolio 1 Federal Agency,"
|
||||
"N/A,,,2,SubOrg 1,,,,,,,0,1,city1.gov,,,,,Purpose of the site,There is more,"
|
||||
"Testy Tester testy2@town.com,,city.com,\n"
|
||||
"city3.gov,Submitted,Federal,Yes,Executive,Portfolio 1 Federal Agency,Portfolio 1 Federal Agency,"
|
||||
"N/A,,,2,,,,,,,,0,1,"
|
||||
'"cheeseville.gov, city1.gov, igorville.gov",,,,,Purpose of the site,CISA-first-name CISA-last-name | '
|
||||
'There is more,"Meow Tester24 te2@town.com, Testy1232 Tester24 te2@town.com, '
|
||||
'Testy Tester testy2@town.com",'
|
||||
|
@ -792,9 +799,9 @@ class ExportDataTest(MockDbForIndividualTests, MockEppLib):
|
|||
"Chief Tester,Purpose of the site,CISA-first-name CISA-last-name | There is more,"
|
||||
"Testy Tester testy2@town.com,"
|
||||
"cisaRep@igorville.gov,city.com,\n"
|
||||
"city6.gov,Submitted,Federal,Yes,Executive,Portfolio 1 Federal Agency,,N/A,,,2,,,,,,,,0,1,city1.gov,"
|
||||
",,,,Purpose of the site,CISA-first-name CISA-last-name | There is more,Testy Tester testy2@town.com,"
|
||||
"cisaRep@igorville.gov,city.com,\n"
|
||||
"city6.gov,Submitted,Federal,Yes,Executive,Portfolio 1 Federal Agency,Portfolio 1 Federal Agency,N/A,"
|
||||
",,2,,,,,,,,0,1,city1.gov,,,,,Purpose of the site,CISA-first-name CISA-last-name | There is more,"
|
||||
"Testy Tester testy2@town.com,cisaRep@igorville.gov,city.com,\n"
|
||||
)
|
||||
|
||||
# Normalize line endings and remove commas,
|
||||
|
|
|
@ -36,7 +36,7 @@ def send_templated_email( # noqa
|
|||
|
||||
to_address and bcc_address currently only support single addresses.
|
||||
|
||||
cc_address is a list and can contain many addresses. Emails not in the
|
||||
cc_addresses is a list and can contain many addresses. Emails not in the
|
||||
whitelist (if applicable) will be filtered out before sending.
|
||||
|
||||
template_name and subject_template_name are relative to the same template
|
||||
|
|
|
@ -1336,6 +1336,8 @@ class DomainDeleteUserView(UserDomainRolePermissionDeleteView):
|
|||
# Is the user deleting themselves? If so, display a different message
|
||||
delete_self = self.request.user == self.object.user
|
||||
|
||||
# Email domain managers
|
||||
|
||||
# Add a success message
|
||||
messages.success(self.request, self.get_success_message(delete_self))
|
||||
return redirect(self.get_success_url())
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue