mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-05-14 16:47:02 +02:00
merge from main
This commit is contained in:
commit
a3739193d7
26 changed files with 1084 additions and 29 deletions
|
@ -204,6 +204,16 @@ from .common import less_console_noise
|
||||||
# <test code goes here>
|
# <test code goes here>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Or alternatively, if you prefer using a decorator, just use:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from .common import less_console_noise_decorator
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def some_function():
|
||||||
|
# <test code goes here>
|
||||||
|
```
|
||||||
|
|
||||||
### Accessibility Testing in the browser
|
### Accessibility Testing in the browser
|
||||||
|
|
||||||
We use the [ANDI](https://www.ssa.gov/accessibility/andi/help/install.html) browser extension
|
We use the [ANDI](https://www.ssa.gov/accessibility/andi/help/install.html) browser extension
|
||||||
|
|
|
@ -16,12 +16,14 @@ The following set of rules should be followed while an incident is in progress.
|
||||||
- If downtime occurs outside of working hours, team members who are off for the day may still be pinged and called but are not required to join if unavailable to do so.
|
- If downtime occurs outside of working hours, team members who are off for the day may still be pinged and called but are not required to join if unavailable to do so.
|
||||||
- Uncomment the [banner on get.gov](https://github.com/cisagov/get.gov/blob/0365d3d34b041cc9353497b2b5f81b6ab7fe75a9/_includes/header.html#L9), so it is transparent to users that we know about the issue on manage.get.gov.
|
- Uncomment the [banner on get.gov](https://github.com/cisagov/get.gov/blob/0365d3d34b041cc9353497b2b5f81b6ab7fe75a9/_includes/header.html#L9), so it is transparent to users that we know about the issue on manage.get.gov.
|
||||||
- Designers or Developers should be able to make this change; if designers are online and can help with this task, that will allow developers to focus on fixing the bug.
|
- Designers or Developers should be able to make this change; if designers are online and can help with this task, that will allow developers to focus on fixing the bug.
|
||||||
|
- If the issue persists for three hours or more, follow the [instructions for enabling/disabling a redirect to get.gov](https://docs.google.com/document/d/1PiWXpjBzbiKsSYqEo9Rkl72HMytMp7zTte9CI-vvwYw/edit).
|
||||||
|
|
||||||
## Post Incident
|
## Post Incident
|
||||||
|
|
||||||
The following checklist should be followed after the site is back up and running.
|
The following checklist should be followed after the site is back up and running.
|
||||||
|
|
||||||
- [ ] Message in #dotgov-announce with an @here saying the issue is resolved
|
- [ ] Message in #dotgov-announce with an @here saying the issue is resolved.
|
||||||
|
- [ ] If the redirect was used, refer to the [instructions for enabling/disabling a redirect to get.gov](https://docs.google.com/document/d/1PiWXpjBzbiKsSYqEo9Rkl72HMytMp7zTte9CI-vvwYw/edit) to turn off this redirect. Double-check in the browser that this redirect is no longer occurring (the change may take a few minutes to take full effect).
|
||||||
- [ ] Remove the [banner on get.gov](https://github.com/cisagov/get.gov/blob/0365d3d34b041cc9353497b2b5f81b6ab7fe75a9/_includes/header.html#L9) by commenting it out.
|
- [ ] Remove the [banner on get.gov](https://github.com/cisagov/get.gov/blob/0365d3d34b041cc9353497b2b5f81b6ab7fe75a9/_includes/header.html#L9) by commenting it out.
|
||||||
- [ ] Write up what happened and when; if the cause is already known, write that as well. This is a draft for internal communications and not for any public facing site and can be as simple as using bullet points.
|
- [ ] Write up what happened and when; if the cause is already known, write that as well. This is a draft for internal communications and not for any public facing site and can be as simple as using bullet points.
|
||||||
- [ ] If the cause is not known yet, developers should investigate the issue as the highest priority task.
|
- [ ] If the cause is not known yet, developers should investigate the issue as the highest priority task.
|
||||||
|
|
|
@ -910,6 +910,9 @@ class DomainInformationAdmin(ListHeaderAdmin):
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Readonly fields for analysts and superusers
|
||||||
|
readonly_fields = ("other_contacts",)
|
||||||
|
|
||||||
# Read only that we'll leverage for CISA Analysts
|
# Read only that we'll leverage for CISA Analysts
|
||||||
analyst_readonly_fields = [
|
analyst_readonly_fields = [
|
||||||
"creator",
|
"creator",
|
||||||
|
@ -938,6 +941,8 @@ class DomainInformationAdmin(ListHeaderAdmin):
|
||||||
# Table ordering
|
# Table ordering
|
||||||
ordering = ["domain__name"]
|
ordering = ["domain__name"]
|
||||||
|
|
||||||
|
change_form_template = "django/admin/domain_information_change_form.html"
|
||||||
|
|
||||||
def get_readonly_fields(self, request, obj=None):
|
def get_readonly_fields(self, request, obj=None):
|
||||||
"""Set the read-only state on form elements.
|
"""Set the read-only state on form elements.
|
||||||
We have 1 conditions that determine which fields are read-only:
|
We have 1 conditions that determine which fields are read-only:
|
||||||
|
@ -1116,6 +1121,9 @@ class DomainRequestAdmin(ListHeaderAdmin):
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Readonly fields for analysts and superusers
|
||||||
|
readonly_fields = ("other_contacts", "current_websites", "alternative_domains")
|
||||||
|
|
||||||
# Read only that we'll leverage for CISA Analysts
|
# Read only that we'll leverage for CISA Analysts
|
||||||
analyst_readonly_fields = [
|
analyst_readonly_fields = [
|
||||||
"creator",
|
"creator",
|
||||||
|
@ -1142,6 +1150,8 @@ class DomainRequestAdmin(ListHeaderAdmin):
|
||||||
# Table ordering
|
# Table ordering
|
||||||
ordering = ["requested_domain__name"]
|
ordering = ["requested_domain__name"]
|
||||||
|
|
||||||
|
change_form_template = "django/admin/domain_request_change_form.html"
|
||||||
|
|
||||||
# Trigger action when a fieldset is changed
|
# Trigger action when a fieldset is changed
|
||||||
def save_model(self, request, obj, form, change):
|
def save_model(self, request, obj, form, change):
|
||||||
"""Custom save_model definition that handles edge cases"""
|
"""Custom save_model definition that handles edge cases"""
|
||||||
|
@ -1296,7 +1306,7 @@ class DomainRequestAdmin(ListHeaderAdmin):
|
||||||
readonly_fields.extend([field.name for field in self.model._meta.fields])
|
readonly_fields.extend([field.name for field in self.model._meta.fields])
|
||||||
# Add the multi-select fields to readonly_fields:
|
# Add the multi-select fields to readonly_fields:
|
||||||
# Complex fields like ManyToManyField require special handling
|
# Complex fields like ManyToManyField require special handling
|
||||||
readonly_fields.extend(["current_websites", "other_contacts", "alternative_domains"])
|
readonly_fields.extend(["alternative_domains"])
|
||||||
|
|
||||||
if request.user.has_perm("registrar.full_access_permission"):
|
if request.user.has_perm("registrar.full_access_permission"):
|
||||||
return readonly_fields
|
return readonly_fields
|
||||||
|
@ -1407,7 +1417,6 @@ class DomainAdmin(ListHeaderAdmin):
|
||||||
)
|
)
|
||||||
|
|
||||||
def queryset(self, request, queryset):
|
def queryset(self, request, queryset):
|
||||||
logger.debug(self.value())
|
|
||||||
if self.value() == "1":
|
if self.value() == "1":
|
||||||
return queryset.filter(domain_info__is_election_board=True)
|
return queryset.filter(domain_info__is_election_board=True)
|
||||||
if self.value() == "0":
|
if self.value() == "0":
|
||||||
|
@ -1813,6 +1822,13 @@ class VerifiedByStaffAdmin(ListHeaderAdmin):
|
||||||
super().save_model(request, obj, form, change)
|
super().save_model(request, obj, form, change)
|
||||||
|
|
||||||
|
|
||||||
|
class FederalAgencyAdmin(ListHeaderAdmin):
|
||||||
|
list_display = ["agency"]
|
||||||
|
search_fields = ["agency"]
|
||||||
|
search_help_text = "Search by agency name."
|
||||||
|
ordering = ["agency"]
|
||||||
|
|
||||||
|
|
||||||
admin.site.unregister(LogEntry) # Unregister the default registration
|
admin.site.unregister(LogEntry) # Unregister the default registration
|
||||||
admin.site.register(LogEntry, CustomLogEntryAdmin)
|
admin.site.register(LogEntry, CustomLogEntryAdmin)
|
||||||
admin.site.register(models.User, MyUserAdmin)
|
admin.site.register(models.User, MyUserAdmin)
|
||||||
|
@ -1826,6 +1842,7 @@ admin.site.register(models.DomainInvitation, DomainInvitationAdmin)
|
||||||
admin.site.register(models.DomainInformation, DomainInformationAdmin)
|
admin.site.register(models.DomainInformation, DomainInformationAdmin)
|
||||||
admin.site.register(models.Domain, DomainAdmin)
|
admin.site.register(models.Domain, DomainAdmin)
|
||||||
admin.site.register(models.DraftDomain, DraftDomainAdmin)
|
admin.site.register(models.DraftDomain, DraftDomainAdmin)
|
||||||
|
admin.site.register(models.FederalAgency, FederalAgencyAdmin)
|
||||||
# Host and HostIP removed from django admin because changes in admin
|
# Host and HostIP removed from django admin because changes in admin
|
||||||
# do not propagate to registry and logic not applied
|
# do not propagate to registry and logic not applied
|
||||||
admin.site.register(models.Host, MyHostAdmin)
|
admin.site.register(models.Host, MyHostAdmin)
|
||||||
|
|
|
@ -451,7 +451,7 @@ function enableRelatedWidgetButtons(changeLink, deleteLink, viewLink, elementPk,
|
||||||
// If it is, expand it.
|
// If it is, expand it.
|
||||||
const targetElement = document.querySelector(".scroll-indicator");
|
const targetElement = document.querySelector(".scroll-indicator");
|
||||||
const options = {
|
const options = {
|
||||||
threshold: 1 // Adjust the threshold as needed (1 indicates when the target element is fully visible)
|
threshold: 1
|
||||||
};
|
};
|
||||||
// Create a new Intersection Observer
|
// Create a new Intersection Observer
|
||||||
const observer = new IntersectionObserver((entries, observer) => {
|
const observer = new IntersectionObserver((entries, observer) => {
|
||||||
|
|
|
@ -54,6 +54,7 @@
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|
||||||
/** An IIFE to initialize the analytics page
|
/** An IIFE to initialize the analytics page
|
||||||
*/
|
*/
|
||||||
(function () {
|
(function () {
|
||||||
|
|
|
@ -347,6 +347,77 @@ input.admin-confirm-button {
|
||||||
color: $dhs-blue-70;
|
color: $dhs-blue-70;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
details.dja-detail-table {
|
||||||
|
display: inline-table;
|
||||||
|
background-color: var(--body-bg);
|
||||||
|
.dja-details-summary {
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--body-quiet-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1024px){
|
||||||
|
.dja-detail-contents {
|
||||||
|
max-width: 400px !important;
|
||||||
|
overflow-x: scroll !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tr {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
td, th {
|
||||||
|
padding-left: 12px;
|
||||||
|
border: none
|
||||||
|
}
|
||||||
|
|
||||||
|
thead > tr > th {
|
||||||
|
border-radius: 4px;
|
||||||
|
border-top: none;
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
address.margin-top-neg-1__detail-list {
|
||||||
|
margin-top: -8px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dja-detail-list {
|
||||||
|
dl {
|
||||||
|
padding-left: 0px !important;
|
||||||
|
margin-top: 5px !important;
|
||||||
|
}
|
||||||
|
// Mimic the normal label size
|
||||||
|
dt {
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
color: var(--body-quiet-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
address {
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
color: var(--body-quiet-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
address.dja-address-contact-list {
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
color: var(--body-quiet-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mimic the normal label size
|
||||||
|
@media (max-width: 1024px){
|
||||||
|
.dja-detail-list dt {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--body-quiet-color);
|
||||||
|
}
|
||||||
|
.dja-detail-list address {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--body-quiet-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.errors span.select2-selection {
|
.errors span.select2-selection {
|
||||||
border: 1px solid var(--error-fg) !important;
|
border: 1px solid var(--error-fg) !important;
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,6 +93,12 @@ class UserFixture:
|
||||||
"last_name": "Chin",
|
"last_name": "Chin",
|
||||||
"email": "szu.chin@associates.cisa.dhs.gov",
|
"email": "szu.chin@associates.cisa.dhs.gov",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"username": "012f844d-8a0f-4225-9d82-cbf87bff1d3e",
|
||||||
|
"first_name": "Riley",
|
||||||
|
"last_name": "Orr",
|
||||||
|
"email": "riley+320@truss.works",
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
STAFF = [
|
STAFF = [
|
||||||
|
@ -169,6 +175,12 @@ class UserFixture:
|
||||||
"last_name": "Mcelya-Analyst",
|
"last_name": "Mcelya-Analyst",
|
||||||
"email": "ALEXANDER.MCELYA@cisa.dhs.gov",
|
"email": "ALEXANDER.MCELYA@cisa.dhs.gov",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"username": "082a066f-e0a4-45f6-8672-4343a1208a36",
|
||||||
|
"first_name": "Riley-Analyst",
|
||||||
|
"last_name": "Orr-Analyst",
|
||||||
|
"email": "riley+321@truss.works",
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
def load_users(cls, users, group_name):
|
def load_users(cls, users, group_name):
|
||||||
|
|
38
src/registrar/migrations/0079_create_federal_agencies_v01.py
Normal file
38
src/registrar/migrations/0079_create_federal_agencies_v01.py
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
# Generated by Django 4.2.10 on 2024-03-22 22:18
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
from registrar.models import FederalAgency
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
|
# For linting: RunPython expects a function reference.
|
||||||
|
def create_federal_agencies(apps, schema_editor) -> Any:
|
||||||
|
FederalAgency.create_federal_agencies(apps, schema_editor)
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("registrar", "0078_rename_organization_type_domaininformation_generic_org_type_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="FederalAgency",
|
||||||
|
fields=[
|
||||||
|
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
|
||||||
|
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||||
|
("updated_at", models.DateTimeField(auto_now=True)),
|
||||||
|
("agency", models.CharField(blank=True, help_text="Federal agency", null=True)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"verbose_name": "Federal agency",
|
||||||
|
"verbose_name_plural": "Federal agencies",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.RunPython(
|
||||||
|
create_federal_agencies,
|
||||||
|
reverse_code=migrations.RunPython.noop,
|
||||||
|
atomic=True,
|
||||||
|
),
|
||||||
|
]
|
37
src/registrar/migrations/0080_create_groups_v09.py
Normal file
37
src/registrar/migrations/0080_create_groups_v09.py
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
# This migration creates the create_full_access_group and create_cisa_analyst_group groups
|
||||||
|
# It is dependent on 0079 (which populates federal agencies)
|
||||||
|
# If permissions on the groups need changing, edit CISA_ANALYST_GROUP_PERMISSIONS
|
||||||
|
# in the user_group model then:
|
||||||
|
# [NOT RECOMMENDED]
|
||||||
|
# step 1: docker-compose exec app ./manage.py migrate --fake registrar 0035_contenttypes_permissions
|
||||||
|
# step 2: docker-compose exec app ./manage.py migrate registrar 0036_create_groups
|
||||||
|
# step 3: fake run the latest migration in the migrations list
|
||||||
|
# [RECOMMENDED]
|
||||||
|
# Alternatively:
|
||||||
|
# step 1: duplicate the migration that loads data
|
||||||
|
# step 2: docker-compose exec app ./manage.py migrate
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
from registrar.models import UserGroup
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
|
# For linting: RunPython expects a function reference,
|
||||||
|
# so let's give it one
|
||||||
|
def create_groups(apps, schema_editor) -> Any:
|
||||||
|
UserGroup.create_cisa_analyst_group(apps, schema_editor)
|
||||||
|
UserGroup.create_full_access_group(apps, schema_editor)
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("registrar", "0079_create_federal_agencies_v01"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(
|
||||||
|
create_groups,
|
||||||
|
reverse_code=migrations.RunPython.noop,
|
||||||
|
atomic=True,
|
||||||
|
),
|
||||||
|
]
|
|
@ -4,6 +4,7 @@ from .domain_request import DomainRequest
|
||||||
from .domain_information import DomainInformation
|
from .domain_information import DomainInformation
|
||||||
from .domain import Domain
|
from .domain import Domain
|
||||||
from .draft_domain import DraftDomain
|
from .draft_domain import DraftDomain
|
||||||
|
from .federal_agency import FederalAgency
|
||||||
from .host_ip import HostIP
|
from .host_ip import HostIP
|
||||||
from .host import Host
|
from .host import Host
|
||||||
from .domain_invitation import DomainInvitation
|
from .domain_invitation import DomainInvitation
|
||||||
|
@ -22,6 +23,7 @@ __all__ = [
|
||||||
"Domain",
|
"Domain",
|
||||||
"DraftDomain",
|
"DraftDomain",
|
||||||
"DomainInvitation",
|
"DomainInvitation",
|
||||||
|
"FederalAgency",
|
||||||
"HostIP",
|
"HostIP",
|
||||||
"Host",
|
"Host",
|
||||||
"UserDomainRole",
|
"UserDomainRole",
|
||||||
|
@ -39,6 +41,7 @@ auditlog.register(Domain)
|
||||||
auditlog.register(DraftDomain)
|
auditlog.register(DraftDomain)
|
||||||
auditlog.register(DomainInvitation)
|
auditlog.register(DomainInvitation)
|
||||||
auditlog.register(DomainInformation)
|
auditlog.register(DomainInformation)
|
||||||
|
auditlog.register(FederalAgency)
|
||||||
auditlog.register(HostIP)
|
auditlog.register(HostIP)
|
||||||
auditlog.register(Host)
|
auditlog.register(Host)
|
||||||
auditlog.register(UserDomainRole)
|
auditlog.register(UserDomainRole)
|
||||||
|
|
223
src/registrar/models/federal_agency.py
Normal file
223
src/registrar/models/federal_agency.py
Normal file
|
@ -0,0 +1,223 @@
|
||||||
|
from .utility.time_stamped_model import TimeStampedModel
|
||||||
|
from django.db import models
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class FederalAgency(TimeStampedModel):
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "Federal agency"
|
||||||
|
verbose_name_plural = "Federal agencies"
|
||||||
|
|
||||||
|
agency = models.CharField(
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
help_text="Federal agency",
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return f"{self.agency}"
|
||||||
|
|
||||||
|
def create_federal_agencies(apps, schema_editor):
|
||||||
|
"""This method gets run from a data migration to prepopulate data
|
||||||
|
regarding federal agencies."""
|
||||||
|
|
||||||
|
# Hard to pass self to these methods as the calls from migrations
|
||||||
|
# are only expecting apps and schema_editor, so we'll just define
|
||||||
|
# apps, schema_editor in the local scope instead
|
||||||
|
|
||||||
|
AGENCIES = [
|
||||||
|
"Administrative Conference of the United States",
|
||||||
|
"Advisory Council on Historic Preservation",
|
||||||
|
"American Battle Monuments Commission",
|
||||||
|
"AMTRAK",
|
||||||
|
"Appalachian Regional Commission",
|
||||||
|
("Appraisal Subcommittee of the Federal Financial " "Institutions Examination Council"),
|
||||||
|
"Architect of the Capitol",
|
||||||
|
"Armed Forces Retirement Home",
|
||||||
|
"Barry Goldwater Scholarship and Excellence in Education Foundation",
|
||||||
|
"Central Intelligence Agency",
|
||||||
|
"Christopher Columbus Fellowship Foundation",
|
||||||
|
"Civil Rights Cold Case Records Review Board",
|
||||||
|
"Commission for the Preservation of America's Heritage Abroad",
|
||||||
|
"Commission of Fine Arts",
|
||||||
|
"Committee for Purchase From People Who Are Blind or Severely Disabled",
|
||||||
|
"Commodity Futures Trading Commission",
|
||||||
|
"Congressional Budget Office",
|
||||||
|
"Consumer Financial Protection Bureau",
|
||||||
|
"Consumer Product Safety Commission",
|
||||||
|
"Corporation for National and Community Service",
|
||||||
|
"Council of Inspectors General on Integrity and Efficiency",
|
||||||
|
"Court Services and Offender Supervision",
|
||||||
|
"Cyberspace Solarium Commission",
|
||||||
|
"DC Court Services and Offender Supervision Agency",
|
||||||
|
"DC Pre-trial Services",
|
||||||
|
"Defense Nuclear Facilities Safety Board",
|
||||||
|
"Delta Regional Authority",
|
||||||
|
"Denali Commission",
|
||||||
|
"Department of Agriculture",
|
||||||
|
"Department of Commerce",
|
||||||
|
"Department of Defense",
|
||||||
|
"Department of Education",
|
||||||
|
"Department of Energy",
|
||||||
|
"Department of Health and Human Services",
|
||||||
|
"Department of Homeland Security",
|
||||||
|
"Department of Housing and Urban Development",
|
||||||
|
"Department of Justice",
|
||||||
|
"Department of Labor",
|
||||||
|
"Department of State",
|
||||||
|
"Department of the Interior",
|
||||||
|
"Department of the Treasury",
|
||||||
|
"Department of Transportation",
|
||||||
|
"Department of Veterans Affairs",
|
||||||
|
"Director of National Intelligence",
|
||||||
|
"Dwight D. Eisenhower Memorial Commission",
|
||||||
|
"Election Assistance Commission",
|
||||||
|
"Environmental Protection Agency",
|
||||||
|
"Equal Employment Opportunity Commission",
|
||||||
|
"Executive Office of the President",
|
||||||
|
"Export-Import Bank of the United States",
|
||||||
|
"Farm Credit Administration",
|
||||||
|
"Farm Credit System Insurance Corporation",
|
||||||
|
"Federal Communications Commission",
|
||||||
|
"Federal Deposit Insurance Corporation",
|
||||||
|
"Federal Election Commission",
|
||||||
|
"Federal Energy Regulatory Commission",
|
||||||
|
"Federal Financial Institutions Examination Council",
|
||||||
|
"Federal Housing Finance Agency",
|
||||||
|
"Federal Judiciary",
|
||||||
|
"Federal Labor Relations Authority",
|
||||||
|
"Federal Maritime Commission",
|
||||||
|
"Federal Mediation and Conciliation Service",
|
||||||
|
"Federal Mine Safety and Health Review Commission",
|
||||||
|
"Federal Permitting Improvement Steering Council",
|
||||||
|
"Federal Reserve Board of Governors",
|
||||||
|
"Federal Trade Commission",
|
||||||
|
"General Services Administration",
|
||||||
|
"gov Administration",
|
||||||
|
"Government Accountability Office",
|
||||||
|
"Government Publishing Office",
|
||||||
|
"Gulf Coast Ecosystem Restoration Council",
|
||||||
|
"Harry S. Truman Scholarship Foundation",
|
||||||
|
"Institute of Museum and Library Services",
|
||||||
|
"Institute of Peace",
|
||||||
|
"Inter-American Foundation",
|
||||||
|
"International Boundary and Water Commission: United States and Mexico",
|
||||||
|
"International Boundary Commission: United States and Canada",
|
||||||
|
"International Joint Commission: United States and Canada",
|
||||||
|
"James Madison Memorial Fellowship Foundation",
|
||||||
|
"Japan-U.S. Friendship Commission",
|
||||||
|
"John F. Kennedy Center for the Performing Arts",
|
||||||
|
"Legal Services Corporation",
|
||||||
|
"Legislative Branch",
|
||||||
|
"Library of Congress",
|
||||||
|
"Marine Mammal Commission",
|
||||||
|
"Medicaid and CHIP Payment and Access Commission",
|
||||||
|
"Medicare Payment Advisory Commission",
|
||||||
|
"Merit Systems Protection Board",
|
||||||
|
"Millennium Challenge Corporation",
|
||||||
|
"Morris K. Udall and Stewart L. Udall Foundation",
|
||||||
|
"National Aeronautics and Space Administration",
|
||||||
|
"National Archives and Records Administration",
|
||||||
|
"National Capital Planning Commission",
|
||||||
|
"National Council on Disability",
|
||||||
|
"National Credit Union Administration",
|
||||||
|
"National Endowment for the Arts",
|
||||||
|
"National Endowment for the Humanities",
|
||||||
|
"National Foundation on the Arts and the Humanities",
|
||||||
|
"National Gallery of Art",
|
||||||
|
"National Indian Gaming Commission",
|
||||||
|
"National Labor Relations Board",
|
||||||
|
"National Mediation Board",
|
||||||
|
"National Science Foundation",
|
||||||
|
"National Security Commission on Artificial Intelligence",
|
||||||
|
"National Transportation Safety Board",
|
||||||
|
"Networking Information Technology Research and Development",
|
||||||
|
"Non-Federal Agency",
|
||||||
|
"Northern Border Regional Commission",
|
||||||
|
"Nuclear Regulatory Commission",
|
||||||
|
"Nuclear Safety Oversight Committee",
|
||||||
|
"Occupational Safety and Health Review Commission",
|
||||||
|
"Office of Compliance",
|
||||||
|
"Office of Congressional Workplace Rights",
|
||||||
|
"Office of Government Ethics",
|
||||||
|
"Office of Navajo and Hopi Indian Relocation",
|
||||||
|
"Office of Personnel Management",
|
||||||
|
"Open World Leadership Center",
|
||||||
|
"Overseas Private Investment Corporation",
|
||||||
|
"Peace Corps",
|
||||||
|
"Pension Benefit Guaranty Corporation",
|
||||||
|
"Postal Regulatory Commission",
|
||||||
|
"Presidio Trust",
|
||||||
|
"Privacy and Civil Liberties Oversight Board",
|
||||||
|
"Public Buildings Reform Board",
|
||||||
|
"Public Defender Service for the District of Columbia",
|
||||||
|
"Railroad Retirement Board",
|
||||||
|
"Securities and Exchange Commission",
|
||||||
|
"Selective Service System",
|
||||||
|
"Small Business Administration",
|
||||||
|
"Smithsonian Institution",
|
||||||
|
"Social Security Administration",
|
||||||
|
"Social Security Advisory Board",
|
||||||
|
"Southeast Crescent Regional Commission",
|
||||||
|
"Southwest Border Regional Commission",
|
||||||
|
"State Justice Institute",
|
||||||
|
"Stennis Center for Public Service",
|
||||||
|
"Surface Transportation Board",
|
||||||
|
"Tennessee Valley Authority",
|
||||||
|
"The Executive Office of the President",
|
||||||
|
"The Intelligence Community",
|
||||||
|
"The Legislative Branch",
|
||||||
|
"The Supreme Court",
|
||||||
|
"The United States World War One Centennial Commission",
|
||||||
|
"U.S. Access Board",
|
||||||
|
"U.S. Agency for Global Media",
|
||||||
|
"U.S. Agency for International Development",
|
||||||
|
"U.S. Capitol Police",
|
||||||
|
"U.S. Chemical Safety Board",
|
||||||
|
"U.S. China Economic and Security Review Commission",
|
||||||
|
"U.S. Commission for the Preservation of Americas Heritage Abroad",
|
||||||
|
"U.S. Commission of Fine Arts",
|
||||||
|
"U.S. Commission on Civil Rights",
|
||||||
|
"U.S. Commission on International Religious Freedom",
|
||||||
|
"U.S. Courts",
|
||||||
|
"U.S. Department of Agriculture",
|
||||||
|
"U.S. Interagency Council on Homelessness",
|
||||||
|
"U.S. International Trade Commission",
|
||||||
|
"U.S. Nuclear Waste Technical Review Board",
|
||||||
|
"U.S. Office of Special Counsel",
|
||||||
|
"U.S. Postal Service",
|
||||||
|
"U.S. Semiquincentennial Commission",
|
||||||
|
"U.S. Trade and Development Agency",
|
||||||
|
"U.S.-China Economic and Security Review Commission",
|
||||||
|
"Udall Foundation",
|
||||||
|
"United States AbilityOne",
|
||||||
|
"United States Access Board",
|
||||||
|
"United States African Development Foundation",
|
||||||
|
"United States Agency for Global Media",
|
||||||
|
"United States Arctic Research Commission",
|
||||||
|
"United States Global Change Research Program",
|
||||||
|
"United States Holocaust Memorial Museum",
|
||||||
|
"United States Institute of Peace",
|
||||||
|
"United States Interagency Council on Homelessness",
|
||||||
|
"United States International Development Finance Corporation",
|
||||||
|
"United States International Trade Commission",
|
||||||
|
"United States Postal Service",
|
||||||
|
"United States Senate",
|
||||||
|
"United States Trade and Development Agency",
|
||||||
|
"Utah Reclamation Mitigation and Conservation Commission",
|
||||||
|
"Vietnam Education Foundation",
|
||||||
|
"Western Hemisphere Drug Policy Commission",
|
||||||
|
"Woodrow Wilson International Center for Scholars",
|
||||||
|
"World War I Centennial Commission",
|
||||||
|
]
|
||||||
|
|
||||||
|
FederalAgency = apps.get_model("registrar", "FederalAgency")
|
||||||
|
logger.info("Creating federal agency table.")
|
||||||
|
|
||||||
|
try:
|
||||||
|
agencies = [FederalAgency(agency=agency) for agency in AGENCIES]
|
||||||
|
FederalAgency.objects.bulk_create(agencies)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error creating federal agencies: {e}")
|
|
@ -71,6 +71,11 @@ class UserGroup(Group):
|
||||||
"model": "verifiedbystaff",
|
"model": "verifiedbystaff",
|
||||||
"permissions": ["add_verifiedbystaff", "change_verifiedbystaff", "delete_verifiedbystaff"],
|
"permissions": ["add_verifiedbystaff", "change_verifiedbystaff", "delete_verifiedbystaff"],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"app_label": "registrar",
|
||||||
|
"model": "federalagency",
|
||||||
|
"permissions": ["add_federalagency", "change_federalagency", "delete_federalagency"],
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
# Avoid error: You can't execute queries until the end
|
# Avoid error: You can't execute queries until the end
|
||||||
|
|
61
src/registrar/templates/admin/fieldset.html
Normal file
61
src/registrar/templates/admin/fieldset.html
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
{% load i18n static %}
|
||||||
|
|
||||||
|
{% comment %}
|
||||||
|
This is copied from Djangos implementation of this template, with added "blocks"
|
||||||
|
It is not inherently customizable on its own, so we can modify this instead.
|
||||||
|
https://github.com/django/django/blob/main/django/contrib/admin/templates/admin/includes/fieldset.html
|
||||||
|
{% endcomment %}
|
||||||
|
<fieldset class="module aligned {{ fieldset.classes }}">
|
||||||
|
{% if fieldset.name %}<h2>{{ fieldset.name }}</h2>{% endif %}
|
||||||
|
|
||||||
|
{% if fieldset.description %}
|
||||||
|
<div class="description">{{ fieldset.description|safe }}</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% for line in fieldset %}
|
||||||
|
<div class="form-row{% if line.fields|length == 1 and line.errors %} errors{% endif %}{% if not line.has_visible_field %} hidden{% endif %}{% for field in line %}{% if field.field.name %} field-{{ field.field.name }}{% endif %}{% endfor %}">
|
||||||
|
{% if line.fields|length == 1 %}{{ line.errors }}{% else %}<div class="flex-container form-multiline">{% endif %}
|
||||||
|
{% for field in line %}
|
||||||
|
<div>
|
||||||
|
{% if not line.fields|length == 1 and not field.is_readonly %}{{ field.errors }}{% endif %}
|
||||||
|
<div class="flex-container{% if not line.fields|length == 1 %} fieldBox{% if field.field.name %} field-{{ field.field.name }}{% endif %}{% if not field.is_readonly and field.errors %} errors{% endif %}{% if field.field.is_hidden %} hidden{% endif %}{% elif field.is_checkbox %} checkbox-row{% endif %}">
|
||||||
|
{% if field.is_checkbox %}
|
||||||
|
{# .gov override #}
|
||||||
|
{% block field_checkbox %}
|
||||||
|
{{ field.field }}{{ field.label_tag }}
|
||||||
|
{% endblock field_checkbox%}
|
||||||
|
{% else %}
|
||||||
|
{{ field.label_tag }}
|
||||||
|
{% if field.is_readonly %}
|
||||||
|
{# .gov override #}
|
||||||
|
{% block field_readonly %}
|
||||||
|
<div class="readonly">{{ field.contents }}</div>
|
||||||
|
{% endblock field_readonly%}
|
||||||
|
{% else %}
|
||||||
|
{# .gov override #}
|
||||||
|
{% block field_other %}
|
||||||
|
{{ field.field }}
|
||||||
|
{% endblock field_other%}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if field.field.help_text %}
|
||||||
|
{# .gov override #}
|
||||||
|
{% block help_text %}
|
||||||
|
<div class="help"{% if field.field.id_for_label %} id="{{ field.field.id_for_label }}_helptext"{% endif %}>
|
||||||
|
<div>{{ field.field.help_text|safe }}</div>
|
||||||
|
</div>
|
||||||
|
{% endblock help_text %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# .gov addition #}
|
||||||
|
{% block after_help_text %}
|
||||||
|
{# For templating purposes #}
|
||||||
|
{% endblock after_help_text %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% if not line.fields|length == 1 %}</div>{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</fieldset>
|
|
@ -0,0 +1,15 @@
|
||||||
|
{% extends 'admin/change_form.html' %}
|
||||||
|
{% load i18n static %}
|
||||||
|
|
||||||
|
{% block field_sets %}
|
||||||
|
{% for fieldset in adminform %}
|
||||||
|
{% comment %}
|
||||||
|
TODO: this will eventually need to be changed to something like this
|
||||||
|
if we ever want to customize this file:
|
||||||
|
{% include "django/admin/includes/domain_information_fieldset.html" %}
|
||||||
|
|
||||||
|
Use detail_table_fieldset as an example, or just extend it.
|
||||||
|
{% endcomment %}
|
||||||
|
{% include "django/admin/includes/detail_table_fieldset.html" %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endblock %}
|
|
@ -4,7 +4,17 @@
|
||||||
{% block field_sets %}
|
{% block field_sets %}
|
||||||
{# Create an invisible <a> tag so that we can use a click event to toggle the modal. #}
|
{# Create an invisible <a> tag so that we can use a click event to toggle the modal. #}
|
||||||
<a id="invisible-ineligible-modal-toggler" class="display-none" href="#toggle-set-ineligible" aria-controls="toggle-set-ineligible" data-open-modal></a>
|
<a id="invisible-ineligible-modal-toggler" class="display-none" href="#toggle-set-ineligible" aria-controls="toggle-set-ineligible" data-open-modal></a>
|
||||||
{{ block.super }}
|
|
||||||
|
{% for fieldset in adminform %}
|
||||||
|
{% comment %}
|
||||||
|
TODO: this will eventually need to be changed to something like this
|
||||||
|
if we ever want to customize this file:
|
||||||
|
{% include "django/admin/includes/domain_information_fieldset.html" %}
|
||||||
|
|
||||||
|
Use detail_table_fieldset as an example, or just extend it.
|
||||||
|
{% endcomment %}
|
||||||
|
{% include "django/admin/includes/detail_table_fieldset.html" %}
|
||||||
|
{% endfor %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block submit_buttons_bottom %}
|
{% block submit_buttons_bottom %}
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
{% load i18n static %}
|
||||||
|
|
||||||
|
<address class="{% if no_title_top_padding %}margin-top-neg-1__detail-list{% endif %} dja-address-contact-list">
|
||||||
|
|
||||||
|
{% if show_formatted_name %}
|
||||||
|
{% if contact.get_formatted_name %}
|
||||||
|
<a href="{% url 'admin:registrar_contact_change' user.id %}">{{ user.get_formatted_name }}</a><br />
|
||||||
|
{% else %}
|
||||||
|
None<br />
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if user.title or user.contact.title or user.email or user.contact.email or user.phone or user.contact.phone %}
|
||||||
|
{# Title #}
|
||||||
|
{% if user.title or user.contact.title %}
|
||||||
|
{% if user.contact.title %}
|
||||||
|
{{ user.contact.title }}
|
||||||
|
{% else %}
|
||||||
|
{{ user.title }}
|
||||||
|
{% endif %}
|
||||||
|
<br>
|
||||||
|
{% else %}
|
||||||
|
None<br>
|
||||||
|
{% endif %}
|
||||||
|
{# Email #}
|
||||||
|
{% if user.email or user.contact.email %}
|
||||||
|
{% if user.contact.email %}
|
||||||
|
{{ user.contact.email }}
|
||||||
|
{% else %}
|
||||||
|
{{ user.email }}
|
||||||
|
{% endif %}
|
||||||
|
<br>
|
||||||
|
{% else %}
|
||||||
|
None<br>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# Phone #}
|
||||||
|
{% if user.phone or user.contact.phone %}
|
||||||
|
{% if user.contact.phone %}
|
||||||
|
{{ user.contact.phone }}
|
||||||
|
{% else %}
|
||||||
|
{{ user.phone }}
|
||||||
|
{% endif %}
|
||||||
|
<br>
|
||||||
|
{% else %}
|
||||||
|
None<br>
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
No additional contact information found.
|
||||||
|
{% endif %}
|
||||||
|
</address>
|
|
@ -0,0 +1,106 @@
|
||||||
|
{% extends "admin/fieldset.html" %}
|
||||||
|
{% load static url_helpers %}
|
||||||
|
|
||||||
|
{% comment %}
|
||||||
|
This is using a custom implementation fieldset.html (see admin/fieldset.html)
|
||||||
|
{% endcomment %}
|
||||||
|
{% block field_readonly %}
|
||||||
|
{% with all_contacts=original.other_contacts.all %}
|
||||||
|
{% if field.field.name == "other_contacts" %}
|
||||||
|
{% if all_contacts.count > 2 %}
|
||||||
|
<div class="readonly">
|
||||||
|
{% for contact in all_contacts %}
|
||||||
|
<a href="{% url 'admin:registrar_contact_change' contact.id %}">{{ contact.get_formatted_name }}</a>{% if not forloop.last %}, {% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="flex-container dja-detail-list">
|
||||||
|
<dl class="usa-list usa-list--unstyled margin-0">
|
||||||
|
{% for contact in all_contacts %}
|
||||||
|
<dt class="{% if forloop.counter == 1%}margin-top-0{% endif %}">
|
||||||
|
Organization contact {{forloop.counter}}
|
||||||
|
</dt>
|
||||||
|
<dd>
|
||||||
|
{% include "django/admin/includes/contact_detail_list.html" with user=contact show_formatted_name=True %}
|
||||||
|
</dd>
|
||||||
|
{% endfor %}
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% elif field.field.name == "current_websites" %}
|
||||||
|
{% comment %}
|
||||||
|
The "website" model is essentially just a text field.
|
||||||
|
It is not useful to be redirected to the object definition,
|
||||||
|
rather it is more useful in this scenario to be redirected to the
|
||||||
|
actual website (as its just a plaintext string otherwise).
|
||||||
|
|
||||||
|
This ONLY applies to analysts. For superusers, its business as usual.
|
||||||
|
{% endcomment %}
|
||||||
|
<div class="readonly">
|
||||||
|
{% with total_websites=field.contents|split:", " %}
|
||||||
|
{% for website in total_websites %}
|
||||||
|
<a href="{{ website }}" class="padding-top-1 current-website__{{forloop.counter}}">{{ website }}</a>{% if not forloop.last %}, {% endif %}
|
||||||
|
{# Acts as a <br> #}
|
||||||
|
{% if total_websites|length < 5 %}
|
||||||
|
<div class="display-block margin-top-1"></div>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endwith %}
|
||||||
|
</div>
|
||||||
|
{% elif field.field.name == "alternative_domains" %}
|
||||||
|
<div class="readonly">
|
||||||
|
{% for alt_domain in original.alternative_domains.all %}
|
||||||
|
<a href="{% url 'admin:registrar_website_change' alt_domain.id %}">{{ alt_domain }}</a>{% if not forloop.last %}, {% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="readonly">{{ field.contents }}</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
{% endblock field_readonly %}
|
||||||
|
|
||||||
|
{% block after_help_text %}
|
||||||
|
{% if field.field.name == "creator" %}
|
||||||
|
<div class="flex-container">
|
||||||
|
<label aria-label="Creator contact details"></label>
|
||||||
|
{% include "django/admin/includes/contact_detail_list.html" with user=original.creator no_title_top_padding=field.is_readonly %}
|
||||||
|
</div>
|
||||||
|
{% elif field.field.name == "submitter" %}
|
||||||
|
<div class="flex-container">
|
||||||
|
<label aria-label="Submitter contact details"></label>
|
||||||
|
{% include "django/admin/includes/contact_detail_list.html" with user=original.submitter no_title_top_padding=field.is_readonly %}
|
||||||
|
</div>
|
||||||
|
{% elif field.field.name == "authorizing_official" %}
|
||||||
|
<div class="flex-container">
|
||||||
|
<label aria-label="Authorizing official contact details"></label>
|
||||||
|
{% include "django/admin/includes/contact_detail_list.html" with user=original.authorizing_official no_title_top_padding=field.is_readonly %}
|
||||||
|
</div>
|
||||||
|
{% elif field.field.name == "other_contacts" and original.other_contacts.all %}
|
||||||
|
{% with all_contacts=original.other_contacts.all %}
|
||||||
|
{% if all_contacts.count > 2 %}
|
||||||
|
<details class="margin-top-1 dja-detail-table" aria-role="button" open>
|
||||||
|
<summary class="padding-1 padding-left-0 dja-details-summary">Details</summary>
|
||||||
|
<div class="grid-container margin-left-0 padding-left-0 padding-right-0 dja-details-contents">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th colspan="4">Other contact information</th>
|
||||||
|
<tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for contact in all_contacts %}
|
||||||
|
<tr>
|
||||||
|
<th class="padding-left-1" scope="row">{{ contact.get_formatted_name }}</th>
|
||||||
|
<td class="padding-left-1">{{ contact.title }}</td>
|
||||||
|
<td class="padding-left-1">{{ contact.email }}</td>
|
||||||
|
<td class="padding-left-1">{{ contact.phone }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
{% endif %}
|
||||||
|
{% endblock after_help_text %}
|
|
@ -133,9 +133,11 @@
|
||||||
<td data-sort-value="{{ invitation.created_at|date:"U" }}" data-label="Date created">{{ invitation.created_at|date }} </td>
|
<td data-sort-value="{{ invitation.created_at|date:"U" }}" data-label="Date created">{{ invitation.created_at|date }} </td>
|
||||||
<td data-label="Status">{{ invitation.status|title }}</td>
|
<td data-label="Status">{{ invitation.status|title }}</td>
|
||||||
<td>
|
<td>
|
||||||
|
{% if invitation.status == invitation.DomainInvitationStatus.INVITED %}
|
||||||
<form method="POST" action="{% url "invitation-delete" pk=invitation.id %}">
|
<form method="POST" action="{% url "invitation-delete" pk=invitation.id %}">
|
||||||
{% csrf_token %}<input type="submit" class="usa-button--unstyled text-no-underline cursor-pointer" value="Cancel">
|
{% csrf_token %}<input type="submit" class="usa-button--unstyled text-no-underline cursor-pointer" value="Cancel">
|
||||||
</form>
|
</form>
|
||||||
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
|
@ -26,6 +26,14 @@ def endswith(text, ends):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter("split")
|
||||||
|
def split_string(value, key):
|
||||||
|
"""
|
||||||
|
Splits a given string
|
||||||
|
"""
|
||||||
|
return value.split(key)
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag
|
@register.simple_tag
|
||||||
def public_site_url(url_path):
|
def public_site_url(url_path):
|
||||||
"""Make a full URL for this path at our public site.
|
"""Make a full URL for this path at our public site.
|
||||||
|
|
|
@ -116,6 +116,31 @@ class GenericTestHelper(TestCase):
|
||||||
self.url = url
|
self.url = url
|
||||||
self.client = client
|
self.client = client
|
||||||
|
|
||||||
|
def assert_response_contains_distinct_values(self, response, expected_values):
|
||||||
|
"""
|
||||||
|
Asserts that each specified value appears exactly once in the response.
|
||||||
|
|
||||||
|
This method iterates over a list of tuples, where each tuple contains a field name
|
||||||
|
and its expected value. It then performs an assertContains check for each value,
|
||||||
|
ensuring that each value appears exactly once in the response.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- response: The HttpResponse object to inspect.
|
||||||
|
- expected_values: A list of tuples, where each tuple contains:
|
||||||
|
- field: The name of the field (used for subTest identification).
|
||||||
|
- value: The expected value to check for in the response.
|
||||||
|
|
||||||
|
Example usage:
|
||||||
|
expected_values = [
|
||||||
|
("title", "Treat inspector</td>"),
|
||||||
|
("email", "meoward.jones@igorville.gov</td>"),
|
||||||
|
]
|
||||||
|
self.assert_response_contains_distinct_values(response, expected_values)
|
||||||
|
"""
|
||||||
|
for field, value in expected_values:
|
||||||
|
with self.subTest(field=field, expected_value=value):
|
||||||
|
self.assertContains(response, value, count=1)
|
||||||
|
|
||||||
def assert_table_sorted(self, o_index, sort_fields):
|
def assert_table_sorted(self, o_index, sort_fields):
|
||||||
"""
|
"""
|
||||||
This helper function validates the sorting functionality of a Django Admin table view.
|
This helper function validates the sorting functionality of a Django Admin table view.
|
||||||
|
@ -179,7 +204,6 @@ class GenericTestHelper(TestCase):
|
||||||
{"action": "delete_selected", "select_across": selected_across, "index": index, "_selected_action": "23"},
|
{"action": "delete_selected", "select_across": selected_across, "index": index, "_selected_action": "23"},
|
||||||
follow=True,
|
follow=True,
|
||||||
)
|
)
|
||||||
print(f"what is the response? {response}")
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
@ -548,6 +572,12 @@ class MockDb(TestCase):
|
||||||
state=Domain.State.READY,
|
state=Domain.State.READY,
|
||||||
first_ready=timezone.make_aware(datetime.combine(date.today() + timedelta(days=1), datetime.min.time())),
|
first_ready=timezone.make_aware(datetime.combine(date.today() + timedelta(days=1), datetime.min.time())),
|
||||||
)
|
)
|
||||||
|
self.domain_11, _ = Domain.objects.get_or_create(
|
||||||
|
name="cdomain11.gov", state=Domain.State.READY, first_ready=timezone.now()
|
||||||
|
)
|
||||||
|
self.domain_12, _ = Domain.objects.get_or_create(
|
||||||
|
name="zdomain12.gov", state=Domain.State.READY, first_ready=timezone.now()
|
||||||
|
)
|
||||||
|
|
||||||
self.domain_information_1, _ = DomainInformation.objects.get_or_create(
|
self.domain_information_1, _ = DomainInformation.objects.get_or_create(
|
||||||
creator=self.user,
|
creator=self.user,
|
||||||
|
@ -616,6 +646,20 @@ class MockDb(TestCase):
|
||||||
federal_agency="Armed Forces Retirement Home",
|
federal_agency="Armed Forces Retirement Home",
|
||||||
is_election_board=False,
|
is_election_board=False,
|
||||||
)
|
)
|
||||||
|
self.domain_information_11, _ = DomainInformation.objects.get_or_create(
|
||||||
|
creator=self.user,
|
||||||
|
domain=self.domain_11,
|
||||||
|
generic_org_type="federal",
|
||||||
|
federal_agency="World War I Centennial Commission",
|
||||||
|
federal_type="executive",
|
||||||
|
is_election_board=True,
|
||||||
|
)
|
||||||
|
self.domain_information_12, _ = DomainInformation.objects.get_or_create(
|
||||||
|
creator=self.user,
|
||||||
|
domain=self.domain_12,
|
||||||
|
generic_org_type="interstate",
|
||||||
|
is_election_board=False,
|
||||||
|
)
|
||||||
|
|
||||||
meoward_user = get_user_model().objects.create(
|
meoward_user = get_user_model().objects.create(
|
||||||
username="meoward_username", first_name="first_meoward", last_name="last_meoward", email="meoward@rocks.com"
|
username="meoward_username", first_name="first_meoward", last_name="last_meoward", email="meoward@rocks.com"
|
||||||
|
@ -641,6 +685,14 @@ class MockDb(TestCase):
|
||||||
user=meoward_user, domain=self.domain_2, role=UserDomainRole.Roles.MANAGER
|
user=meoward_user, domain=self.domain_2, role=UserDomainRole.Roles.MANAGER
|
||||||
)
|
)
|
||||||
|
|
||||||
|
_, created = UserDomainRole.objects.get_or_create(
|
||||||
|
user=meoward_user, domain=self.domain_11, role=UserDomainRole.Roles.MANAGER
|
||||||
|
)
|
||||||
|
|
||||||
|
_, created = UserDomainRole.objects.get_or_create(
|
||||||
|
user=meoward_user, domain=self.domain_12, role=UserDomainRole.Roles.MANAGER
|
||||||
|
)
|
||||||
|
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
self.domain_request_1 = completed_domain_request(
|
self.domain_request_1 = completed_domain_request(
|
||||||
status=DomainRequest.DomainRequestStatus.STARTED, name="city1.gov"
|
status=DomainRequest.DomainRequestStatus.STARTED, name="city1.gov"
|
||||||
|
|
|
@ -285,7 +285,7 @@ class TestDomainAdmin(MockEppLib, WebTest):
|
||||||
|
|
||||||
# There are 4 template references to Federal (4) plus four references in the table
|
# There are 4 template references to Federal (4) plus four references in the table
|
||||||
# for our actual domain_request
|
# for our actual domain_request
|
||||||
self.assertContains(response, "Federal", count=8)
|
self.assertContains(response, "Federal", count=36)
|
||||||
# This may be a bit more robust
|
# This may be a bit more robust
|
||||||
self.assertContains(response, '<td class="field-generic_org_type">Federal</td>', count=1)
|
self.assertContains(response, '<td class="field-generic_org_type">Federal</td>', count=1)
|
||||||
# Now let's make sure the long description does not exist
|
# Now let's make sure the long description does not exist
|
||||||
|
@ -709,7 +709,7 @@ class TestDomainRequestAdmin(MockEppLib):
|
||||||
response = self.client.get("/admin/registrar/domainrequest/")
|
response = self.client.get("/admin/registrar/domainrequest/")
|
||||||
# There are 4 template references to Federal (4) plus two references in the table
|
# There are 4 template references to Federal (4) plus two references in the table
|
||||||
# for our actual domain request
|
# for our actual domain request
|
||||||
self.assertContains(response, "Federal", count=6)
|
self.assertContains(response, "Federal", count=34)
|
||||||
# This may be a bit more robust
|
# This may be a bit more robust
|
||||||
self.assertContains(response, '<td class="field-generic_org_type">Federal</td>', count=1)
|
self.assertContains(response, '<td class="field-generic_org_type">Federal</td>', count=1)
|
||||||
# Now let's make sure the long description does not exist
|
# Now let's make sure the long description does not exist
|
||||||
|
@ -1231,8 +1231,144 @@ class TestDomainRequestAdmin(MockEppLib):
|
||||||
# Since we're using client to mock the request, we can only test against
|
# Since we're using client to mock the request, we can only test against
|
||||||
# non-interpolated values
|
# non-interpolated values
|
||||||
expected_content = "<strong>Requested domain:</strong>"
|
expected_content = "<strong>Requested domain:</strong>"
|
||||||
|
expected_content2 = '<span class="scroll-indicator"></span>'
|
||||||
|
expected_content3 = '<div class="submit-row-wrapper">'
|
||||||
self.assertContains(request, expected_content)
|
self.assertContains(request, expected_content)
|
||||||
|
self.assertContains(request, expected_content2)
|
||||||
|
self.assertContains(request, expected_content3)
|
||||||
|
|
||||||
|
def test_other_contacts_has_readonly_link(self):
|
||||||
|
"""Tests if the readonly other_contacts field has links"""
|
||||||
|
|
||||||
|
# Create a fake domain request
|
||||||
|
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
|
||||||
|
|
||||||
|
# Get the other contact
|
||||||
|
other_contact = domain_request.other_contacts.all().first()
|
||||||
|
|
||||||
|
p = "userpass"
|
||||||
|
self.client.login(username="staffuser", password=p)
|
||||||
|
response = self.client.get(
|
||||||
|
"/admin/registrar/domainrequest/{}/change/".format(domain_request.pk),
|
||||||
|
follow=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Make sure the page loaded, and that we're on the right page
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertContains(response, domain_request.requested_domain.name)
|
||||||
|
|
||||||
|
# Check that the page contains the url we expect
|
||||||
|
expected_href = reverse("admin:registrar_contact_change", args=[other_contact.id])
|
||||||
|
self.assertContains(response, expected_href)
|
||||||
|
|
||||||
|
# Check that the page contains the link we expect.
|
||||||
|
# Since the url is dynamic (populated by JS), we can test for its existence
|
||||||
|
# by checking for the end tag.
|
||||||
|
expected_url = "Testy Tester</a>"
|
||||||
|
self.assertContains(response, expected_url)
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_other_websites_has_readonly_link(self):
|
||||||
|
"""Tests if the readonly other_websites field has links"""
|
||||||
|
|
||||||
|
# Create a fake domain request
|
||||||
|
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
|
||||||
|
|
||||||
|
p = "userpass"
|
||||||
|
self.client.login(username="staffuser", password=p)
|
||||||
|
response = self.client.get(
|
||||||
|
"/admin/registrar/domainrequest/{}/change/".format(domain_request.pk),
|
||||||
|
follow=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Make sure the page loaded, and that we're on the right page
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertContains(response, domain_request.requested_domain.name)
|
||||||
|
|
||||||
|
# Check that the page contains the link we expect.
|
||||||
|
expected_url = '<a href="city.com" class="padding-top-1 current-website__1">city.com</a>'
|
||||||
|
self.assertContains(response, expected_url)
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_contact_fields_have_detail_table(self):
|
||||||
|
"""Tests if the contact fields have the detail table which displays title, email, and phone"""
|
||||||
|
|
||||||
|
# Create fake creator
|
||||||
|
_creator = User.objects.create(
|
||||||
|
username="MrMeoward",
|
||||||
|
first_name="Meoward",
|
||||||
|
last_name="Jones",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Due to the relation between User <==> Contact,
|
||||||
|
# the underlying contact has to be modified this way.
|
||||||
|
_creator.contact.email = "meoward.jones@igorville.gov"
|
||||||
|
_creator.contact.phone = "(555) 123 12345"
|
||||||
|
_creator.contact.title = "Treat inspector"
|
||||||
|
_creator.contact.save()
|
||||||
|
|
||||||
|
# Create a fake domain request
|
||||||
|
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW, user=_creator)
|
||||||
|
|
||||||
|
p = "userpass"
|
||||||
|
self.client.login(username="staffuser", password=p)
|
||||||
|
response = self.client.get(
|
||||||
|
"/admin/registrar/domainrequest/{}/change/".format(domain_request.pk),
|
||||||
|
follow=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Make sure the page loaded, and that we're on the right page
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertContains(response, domain_request.requested_domain.name)
|
||||||
|
|
||||||
|
# Check that the modal has the right content
|
||||||
|
# Check for the header
|
||||||
|
|
||||||
|
# == Check for the creator == #
|
||||||
|
|
||||||
|
# Check for the right title, email, and phone number in the response.
|
||||||
|
expected_creator_fields = [
|
||||||
|
# Field, expected value
|
||||||
|
("title", "Treat inspector"),
|
||||||
|
("email", "meoward.jones@igorville.gov"),
|
||||||
|
("phone", "(555) 123 12345"),
|
||||||
|
]
|
||||||
|
self.test_helper.assert_response_contains_distinct_values(response, expected_creator_fields)
|
||||||
|
|
||||||
|
# Check for the field itself
|
||||||
|
self.assertContains(response, "Meoward Jones")
|
||||||
|
|
||||||
|
# == Check for the submitter == #
|
||||||
|
expected_submitter_fields = [
|
||||||
|
# Field, expected value
|
||||||
|
("title", "Admin Tester"),
|
||||||
|
("email", "mayor@igorville.gov"),
|
||||||
|
("phone", "(555) 555 5556"),
|
||||||
|
]
|
||||||
|
self.test_helper.assert_response_contains_distinct_values(response, expected_submitter_fields)
|
||||||
|
self.assertContains(response, "Testy2 Tester2")
|
||||||
|
|
||||||
|
# == Check for the authorizing_official == #
|
||||||
|
expected_ao_fields = [
|
||||||
|
# Field, expected value
|
||||||
|
("title", "Chief Tester"),
|
||||||
|
("email", "testy@town.com"),
|
||||||
|
("phone", "(555) 555 5555"),
|
||||||
|
]
|
||||||
|
self.test_helper.assert_response_contains_distinct_values(response, expected_ao_fields)
|
||||||
|
|
||||||
|
# count=5 because the underlying domain has two users with this name.
|
||||||
|
# The dropdown has 3 of these.
|
||||||
|
self.assertContains(response, "Testy Tester", count=5)
|
||||||
|
|
||||||
|
# == Test the other_employees field == #
|
||||||
|
expected_other_employees_fields = [
|
||||||
|
# Field, expected value
|
||||||
|
("title", "Another Tester"),
|
||||||
|
("email", "testy2@town.com"),
|
||||||
|
("phone", "(555) 555 5557"),
|
||||||
|
]
|
||||||
|
self.test_helper.assert_response_contains_distinct_values(response, expected_other_employees_fields)
|
||||||
|
|
||||||
def test_save_model_sets_restricted_status_on_user(self):
|
def test_save_model_sets_restricted_status_on_user(self):
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
|
@ -1317,6 +1453,7 @@ class TestDomainRequestAdmin(MockEppLib):
|
||||||
self.assertContains(response, "Yes, select ineligible status")
|
self.assertContains(response, "Yes, select ineligible status")
|
||||||
|
|
||||||
def test_readonly_when_restricted_creator(self):
|
def test_readonly_when_restricted_creator(self):
|
||||||
|
self.maxDiff = None
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
|
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
|
||||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
|
@ -1329,6 +1466,9 @@ class TestDomainRequestAdmin(MockEppLib):
|
||||||
readonly_fields = self.admin.get_readonly_fields(request, domain_request)
|
readonly_fields = self.admin.get_readonly_fields(request, domain_request)
|
||||||
|
|
||||||
expected_fields = [
|
expected_fields = [
|
||||||
|
"other_contacts",
|
||||||
|
"current_websites",
|
||||||
|
"alternative_domains",
|
||||||
"id",
|
"id",
|
||||||
"created_at",
|
"created_at",
|
||||||
"updated_at",
|
"updated_at",
|
||||||
|
@ -1361,8 +1501,6 @@ class TestDomainRequestAdmin(MockEppLib):
|
||||||
"is_policy_acknowledged",
|
"is_policy_acknowledged",
|
||||||
"submission_date",
|
"submission_date",
|
||||||
"notes",
|
"notes",
|
||||||
"current_websites",
|
|
||||||
"other_contacts",
|
|
||||||
"alternative_domains",
|
"alternative_domains",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1376,6 +1514,9 @@ class TestDomainRequestAdmin(MockEppLib):
|
||||||
readonly_fields = self.admin.get_readonly_fields(request)
|
readonly_fields = self.admin.get_readonly_fields(request)
|
||||||
|
|
||||||
expected_fields = [
|
expected_fields = [
|
||||||
|
"other_contacts",
|
||||||
|
"current_websites",
|
||||||
|
"alternative_domains",
|
||||||
"creator",
|
"creator",
|
||||||
"about_your_organization",
|
"about_your_organization",
|
||||||
"requested_domain",
|
"requested_domain",
|
||||||
|
@ -1397,7 +1538,11 @@ class TestDomainRequestAdmin(MockEppLib):
|
||||||
|
|
||||||
readonly_fields = self.admin.get_readonly_fields(request)
|
readonly_fields = self.admin.get_readonly_fields(request)
|
||||||
|
|
||||||
expected_fields = []
|
expected_fields = [
|
||||||
|
"other_contacts",
|
||||||
|
"current_websites",
|
||||||
|
"alternative_domains",
|
||||||
|
]
|
||||||
|
|
||||||
self.assertEqual(readonly_fields, expected_fields)
|
self.assertEqual(readonly_fields, expected_fields)
|
||||||
|
|
||||||
|
@ -1827,6 +1972,125 @@ class TestDomainInformationAdmin(TestCase):
|
||||||
Contact.objects.all().delete()
|
Contact.objects.all().delete()
|
||||||
User.objects.all().delete()
|
User.objects.all().delete()
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_other_contacts_has_readonly_link(self):
|
||||||
|
"""Tests if the readonly other_contacts field has links"""
|
||||||
|
|
||||||
|
# Create a fake domain request and domain
|
||||||
|
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
|
||||||
|
domain_request.approve()
|
||||||
|
domain_info = DomainInformation.objects.filter(domain=domain_request.approved_domain).get()
|
||||||
|
|
||||||
|
# Get the other contact
|
||||||
|
other_contact = domain_info.other_contacts.all().first()
|
||||||
|
|
||||||
|
p = "userpass"
|
||||||
|
self.client.login(username="staffuser", password=p)
|
||||||
|
|
||||||
|
response = self.client.get(
|
||||||
|
"/admin/registrar/domaininformation/{}/change/".format(domain_info.pk),
|
||||||
|
follow=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Make sure the page loaded, and that we're on the right page
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertContains(response, domain_info.domain.name)
|
||||||
|
|
||||||
|
# Check that the page contains the url we expect
|
||||||
|
expected_href = reverse("admin:registrar_contact_change", args=[other_contact.id])
|
||||||
|
self.assertContains(response, expected_href)
|
||||||
|
|
||||||
|
# Check that the page contains the link we expect.
|
||||||
|
# Since the url is dynamic (populated by JS), we can test for its existence
|
||||||
|
# by checking for the end tag.
|
||||||
|
expected_url = "Testy Tester</a>"
|
||||||
|
self.assertContains(response, expected_url)
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_contact_fields_have_detail_table(self):
|
||||||
|
"""Tests if the contact fields have the detail table which displays title, email, and phone"""
|
||||||
|
|
||||||
|
# Create fake creator
|
||||||
|
_creator = User.objects.create(
|
||||||
|
username="MrMeoward",
|
||||||
|
first_name="Meoward",
|
||||||
|
last_name="Jones",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Due to the relation between User <==> Contact,
|
||||||
|
# the underlying contact has to be modified this way.
|
||||||
|
_creator.contact.email = "meoward.jones@igorville.gov"
|
||||||
|
_creator.contact.phone = "(555) 123 12345"
|
||||||
|
_creator.contact.title = "Treat inspector"
|
||||||
|
_creator.contact.save()
|
||||||
|
|
||||||
|
# Create a fake domain request
|
||||||
|
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW, user=_creator)
|
||||||
|
domain_request.approve()
|
||||||
|
domain_info = DomainInformation.objects.filter(domain=domain_request.approved_domain).get()
|
||||||
|
|
||||||
|
p = "userpass"
|
||||||
|
self.client.login(username="staffuser", password=p)
|
||||||
|
response = self.client.get(
|
||||||
|
"/admin/registrar/domaininformation/{}/change/".format(domain_info.pk),
|
||||||
|
follow=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Make sure the page loaded, and that we're on the right page
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertContains(response, domain_info.domain.name)
|
||||||
|
|
||||||
|
# Check that the modal has the right content
|
||||||
|
# Check for the header
|
||||||
|
|
||||||
|
# == Check for the creator == #
|
||||||
|
|
||||||
|
# Check for the right title, email, and phone number in the response.
|
||||||
|
# We only need to check for the end tag
|
||||||
|
# (Otherwise this test will fail if we change classes, etc)
|
||||||
|
expected_creator_fields = [
|
||||||
|
# Field, expected value
|
||||||
|
("title", "Treat inspector"),
|
||||||
|
("email", "meoward.jones@igorville.gov"),
|
||||||
|
("phone", "(555) 123 12345"),
|
||||||
|
]
|
||||||
|
self.test_helper.assert_response_contains_distinct_values(response, expected_creator_fields)
|
||||||
|
|
||||||
|
# Check for the field itself
|
||||||
|
self.assertContains(response, "Meoward Jones")
|
||||||
|
|
||||||
|
# == Check for the submitter == #
|
||||||
|
expected_submitter_fields = [
|
||||||
|
# Field, expected value
|
||||||
|
("title", "Admin Tester"),
|
||||||
|
("email", "mayor@igorville.gov"),
|
||||||
|
("phone", "(555) 555 5556"),
|
||||||
|
]
|
||||||
|
self.test_helper.assert_response_contains_distinct_values(response, expected_submitter_fields)
|
||||||
|
self.assertContains(response, "Testy2 Tester2")
|
||||||
|
|
||||||
|
# == Check for the authorizing_official == #
|
||||||
|
expected_ao_fields = [
|
||||||
|
# Field, expected value
|
||||||
|
("title", "Chief Tester"),
|
||||||
|
("email", "testy@town.com"),
|
||||||
|
("phone", "(555) 555 5555"),
|
||||||
|
]
|
||||||
|
self.test_helper.assert_response_contains_distinct_values(response, expected_ao_fields)
|
||||||
|
|
||||||
|
# count=5 because the underlying domain has two users with this name.
|
||||||
|
# The dropdown has 3 of these.
|
||||||
|
self.assertContains(response, "Testy Tester", count=5)
|
||||||
|
|
||||||
|
# == Test the other_employees field == #
|
||||||
|
expected_other_employees_fields = [
|
||||||
|
# Field, expected value
|
||||||
|
("title", "Another Tester"),
|
||||||
|
("email", "testy2@town.com"),
|
||||||
|
("phone", "(555) 555 5557"),
|
||||||
|
]
|
||||||
|
self.test_helper.assert_response_contains_distinct_values(response, expected_other_employees_fields)
|
||||||
|
|
||||||
def test_readonly_fields_for_analyst(self):
|
def test_readonly_fields_for_analyst(self):
|
||||||
"""Ensures that analysts have their permissions setup correctly"""
|
"""Ensures that analysts have their permissions setup correctly"""
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
|
@ -1836,6 +2100,7 @@ class TestDomainInformationAdmin(TestCase):
|
||||||
readonly_fields = self.admin.get_readonly_fields(request)
|
readonly_fields = self.admin.get_readonly_fields(request)
|
||||||
|
|
||||||
expected_fields = [
|
expected_fields = [
|
||||||
|
"other_contacts",
|
||||||
"creator",
|
"creator",
|
||||||
"type_of_work",
|
"type_of_work",
|
||||||
"more_organization_information",
|
"more_organization_information",
|
||||||
|
|
|
@ -39,6 +39,9 @@ class TestGroups(TestCase):
|
||||||
"view_domaininvitation",
|
"view_domaininvitation",
|
||||||
"change_domainrequest",
|
"change_domainrequest",
|
||||||
"change_draftdomain",
|
"change_draftdomain",
|
||||||
|
"add_federalagency",
|
||||||
|
"change_federalagency",
|
||||||
|
"delete_federalagency",
|
||||||
"analyst_access_permission",
|
"analyst_access_permission",
|
||||||
"change_user",
|
"change_user",
|
||||||
"delete_userdomainrole",
|
"delete_userdomainrole",
|
||||||
|
|
|
@ -44,6 +44,7 @@ class CsvReportsTest(MockDb):
|
||||||
fake_open = mock_open()
|
fake_open = mock_open()
|
||||||
expected_file_content = [
|
expected_file_content = [
|
||||||
call("Domain name,Domain type,Agency,Organization name,City,State,Security contact email\r\n"),
|
call("Domain name,Domain type,Agency,Organization name,City,State,Security contact email\r\n"),
|
||||||
|
call("cdomain11.gov,Federal - Executive,World War I Centennial Commission,,,, \r\n"),
|
||||||
call("cdomain1.gov,Federal - Executive,World War I Centennial Commission,,,, \r\n"),
|
call("cdomain1.gov,Federal - Executive,World War I Centennial Commission,,,, \r\n"),
|
||||||
call("adomain10.gov,Federal,Armed Forces Retirement Home,,,, \r\n"),
|
call("adomain10.gov,Federal,Armed Forces Retirement Home,,,, \r\n"),
|
||||||
call("ddomain3.gov,Federal,Armed Forces Retirement Home,,,, \r\n"),
|
call("ddomain3.gov,Federal,Armed Forces Retirement Home,,,, \r\n"),
|
||||||
|
@ -65,6 +66,7 @@ class CsvReportsTest(MockDb):
|
||||||
fake_open = mock_open()
|
fake_open = mock_open()
|
||||||
expected_file_content = [
|
expected_file_content = [
|
||||||
call("Domain name,Domain type,Agency,Organization name,City,State,Security contact email\r\n"),
|
call("Domain name,Domain type,Agency,Organization name,City,State,Security contact email\r\n"),
|
||||||
|
call("cdomain11.gov,Federal - Executive,World War I Centennial Commission,,,, \r\n"),
|
||||||
call("cdomain1.gov,Federal - Executive,World War I Centennial Commission,,,, \r\n"),
|
call("cdomain1.gov,Federal - Executive,World War I Centennial Commission,,,, \r\n"),
|
||||||
call("adomain10.gov,Federal,Armed Forces Retirement Home,,,, \r\n"),
|
call("adomain10.gov,Federal,Armed Forces Retirement Home,,,, \r\n"),
|
||||||
call("ddomain3.gov,Federal,Armed Forces Retirement Home,,,, \r\n"),
|
call("ddomain3.gov,Federal,Armed Forces Retirement Home,,,, \r\n"),
|
||||||
|
@ -255,8 +257,10 @@ class ExportDataTest(MockDb, MockEppLib):
|
||||||
"AO email,Security contact email,Status,Expiration date\n"
|
"AO email,Security contact email,Status,Expiration date\n"
|
||||||
"adomain10.gov,Federal,Armed Forces Retirement Home,Ready\n"
|
"adomain10.gov,Federal,Armed Forces Retirement Home,Ready\n"
|
||||||
"adomain2.gov,Interstate,(blank),Dns needed\n"
|
"adomain2.gov,Interstate,(blank),Dns needed\n"
|
||||||
|
"cdomain11.govFederal-ExecutiveWorldWarICentennialCommissionReady\n"
|
||||||
"ddomain3.gov,Federal,Armed Forces Retirement Home,123@mail.gov,On hold,2023-05-25\n"
|
"ddomain3.gov,Federal,Armed Forces Retirement Home,123@mail.gov,On hold,2023-05-25\n"
|
||||||
"defaultsecurity.gov,Federal - Executive,World War I Centennial Commission,(blank),Ready"
|
"defaultsecurity.gov,Federal - Executive,World War I Centennial Commission,(blank),Ready\n"
|
||||||
|
"zdomain12.govInterstateReady\n"
|
||||||
)
|
)
|
||||||
# Normalize line endings and remove commas,
|
# Normalize line endings and remove commas,
|
||||||
# spaces and leading/trailing whitespace
|
# spaces and leading/trailing whitespace
|
||||||
|
@ -315,8 +319,10 @@ class ExportDataTest(MockDb, MockEppLib):
|
||||||
"Security contact email,Status\n"
|
"Security contact email,Status\n"
|
||||||
"adomain10.gov,Federal,Armed Forces Retirement Home,Ready\n"
|
"adomain10.gov,Federal,Armed Forces Retirement Home,Ready\n"
|
||||||
"adomain2.gov,Interstate,Dns needed\n"
|
"adomain2.gov,Interstate,Dns needed\n"
|
||||||
|
"cdomain11.govFederal-ExecutiveWorldWarICentennialCommissionReady\n"
|
||||||
"cdomain1.gov,Federal - Executive,World War I Centennial Commission,Ready\n"
|
"cdomain1.gov,Federal - Executive,World War I Centennial Commission,Ready\n"
|
||||||
"ddomain3.gov,Federal,Armed Forces Retirement Home,On hold\n"
|
"ddomain3.gov,Federal,Armed Forces Retirement Home,On hold\n"
|
||||||
|
"zdomain12.govInterstateReady\n"
|
||||||
)
|
)
|
||||||
# Normalize line endings and remove commas,
|
# Normalize line endings and remove commas,
|
||||||
# spaces and leading/trailing whitespace
|
# spaces and leading/trailing whitespace
|
||||||
|
@ -365,6 +371,7 @@ class ExportDataTest(MockDb, MockEppLib):
|
||||||
"Domain name,Domain type,Agency,Organization name,City,"
|
"Domain name,Domain type,Agency,Organization name,City,"
|
||||||
"State,Security contact email\n"
|
"State,Security contact email\n"
|
||||||
"adomain10.gov,Federal,Armed Forces Retirement Home\n"
|
"adomain10.gov,Federal,Armed Forces Retirement Home\n"
|
||||||
|
"cdomain11.govFederal-ExecutiveWorldWarICentennialCommission\n"
|
||||||
"cdomain1.gov,Federal - Executive,World War I Centennial Commission\n"
|
"cdomain1.gov,Federal - Executive,World War I Centennial Commission\n"
|
||||||
"ddomain3.gov,Federal,Armed Forces Retirement Home\n"
|
"ddomain3.gov,Federal,Armed Forces Retirement Home\n"
|
||||||
)
|
)
|
||||||
|
@ -455,6 +462,8 @@ class ExportDataTest(MockDb, MockEppLib):
|
||||||
"State,Status,Expiration date\n"
|
"State,Status,Expiration date\n"
|
||||||
"cdomain1.gov,Federal-Executive,World War I Centennial Commission,,,,Ready,\n"
|
"cdomain1.gov,Federal-Executive,World War I Centennial Commission,,,,Ready,\n"
|
||||||
"adomain10.gov,Federal,Armed Forces Retirement Home,,,,Ready,\n"
|
"adomain10.gov,Federal,Armed Forces Retirement Home,,,,Ready,\n"
|
||||||
|
"cdomain11.govFederal-ExecutiveWorldWarICentennialCommissionReady\n"
|
||||||
|
"zdomain12.govInterstateReady\n"
|
||||||
"zdomain9.gov,Federal,Armed Forces Retirement Home,,,,Deleted,\n"
|
"zdomain9.gov,Federal,Armed Forces Retirement Home,,,,Deleted,\n"
|
||||||
"sdomain8.gov,Federal,Armed Forces Retirement Home,,,,Deleted,\n"
|
"sdomain8.gov,Federal,Armed Forces Retirement Home,,,,Deleted,\n"
|
||||||
"xdomain7.gov,Federal,Armed Forces Retirement Home,,,,Deleted,\n"
|
"xdomain7.gov,Federal,Armed Forces Retirement Home,,,,Deleted,\n"
|
||||||
|
@ -515,9 +524,11 @@ class ExportDataTest(MockDb, MockEppLib):
|
||||||
"Security contact email,Domain manager email 1,Domain manager email 2,Domain manager email 3\n"
|
"Security contact email,Domain manager email 1,Domain manager email 2,Domain manager email 3\n"
|
||||||
"adomain10.gov,Ready,,Federal,Armed Forces Retirement Home,,,, , ,\n"
|
"adomain10.gov,Ready,,Federal,Armed Forces Retirement Home,,,, , ,\n"
|
||||||
"adomain2.gov,Dns needed,,Interstate,,,,, , , ,meoward@rocks.com\n"
|
"adomain2.gov,Dns needed,,Interstate,,,,, , , ,meoward@rocks.com\n"
|
||||||
|
"cdomain11.govReadyFederal-ExecutiveWorldWarICentennialCommissionmeoward@rocks.com\n"
|
||||||
"cdomain1.gov,Ready,,Federal - Executive,World War I Centennial Commission,,,"
|
"cdomain1.gov,Ready,,Federal - Executive,World War I Centennial Commission,,,"
|
||||||
", , , ,meoward@rocks.com,info@example.com,big_lebowski@dude.co\n"
|
", , , ,meoward@rocks.com,info@example.com,big_lebowski@dude.co\n"
|
||||||
"ddomain3.gov,On hold,,Federal,Armed Forces Retirement Home,,,, , , ,,\n"
|
"ddomain3.gov,On hold,,Federal,Armed Forces Retirement Home,,,, , , ,,\n"
|
||||||
|
"zdomain12.govReadyInterstatemeoward@rocks.com\n"
|
||||||
)
|
)
|
||||||
# Normalize line endings and remove commas,
|
# Normalize line endings and remove commas,
|
||||||
# spaces and leading/trailing whitespace
|
# spaces and leading/trailing whitespace
|
||||||
|
@ -551,10 +562,12 @@ class ExportDataTest(MockDb, MockEppLib):
|
||||||
"MANAGED DOMAINS COUNTS AT END DATE\n"
|
"MANAGED DOMAINS COUNTS AT END DATE\n"
|
||||||
"Total,Federal,Interstate,State or territory,Tribal,County,City,"
|
"Total,Federal,Interstate,State or territory,Tribal,County,City,"
|
||||||
"Special district,School district,Election office\n"
|
"Special district,School district,Election office\n"
|
||||||
"1,1,0,0,0,0,0,0,0,1\n"
|
"3,2,1,0,0,0,0,0,0,2\n"
|
||||||
"\n"
|
"\n"
|
||||||
"Domain name,Domain type,Domain manager email 1,Domain manager email 2,Domain manager email 3\n"
|
"Domain name,Domain type,Domain manager email 1,Domain manager email 2,Domain manager email 3\n"
|
||||||
|
"cdomain11.govFederal-Executivemeoward@rocks.com\n"
|
||||||
"cdomain1.gov,Federal - Executive,meoward@rocks.com,info@example.com,big_lebowski@dude.co\n"
|
"cdomain1.gov,Federal - Executive,meoward@rocks.com,info@example.com,big_lebowski@dude.co\n"
|
||||||
|
"zdomain12.govInterstatemeoward@rocks.com\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Normalize line endings and remove commas,
|
# Normalize line endings and remove commas,
|
||||||
|
@ -674,12 +687,12 @@ class HelperFunctions(MockDb):
|
||||||
}
|
}
|
||||||
# Test with distinct
|
# Test with distinct
|
||||||
managed_domains_sliced_at_end_date = get_sliced_domains(filter_condition, True)
|
managed_domains_sliced_at_end_date = get_sliced_domains(filter_condition, True)
|
||||||
expected_content = [1, 1, 0, 0, 0, 0, 0, 0, 0, 1]
|
expected_content = [3, 2, 1, 0, 0, 0, 0, 0, 0, 2]
|
||||||
self.assertEqual(managed_domains_sliced_at_end_date, expected_content)
|
self.assertEqual(managed_domains_sliced_at_end_date, expected_content)
|
||||||
|
|
||||||
# Test without distinct
|
# Test without distinct
|
||||||
managed_domains_sliced_at_end_date = get_sliced_domains(filter_condition)
|
managed_domains_sliced_at_end_date = get_sliced_domains(filter_condition)
|
||||||
expected_content = [1, 3, 0, 0, 0, 0, 0, 0, 0, 1]
|
expected_content = [3, 4, 1, 0, 0, 0, 0, 0, 0, 2]
|
||||||
self.assertEqual(managed_domains_sliced_at_end_date, expected_content)
|
self.assertEqual(managed_domains_sliced_at_end_date, expected_content)
|
||||||
|
|
||||||
def test_get_sliced_requests(self):
|
def test_get_sliced_requests(self):
|
||||||
|
|
|
@ -665,6 +665,22 @@ class TestDomainManagers(TestDomainOverview):
|
||||||
with self.assertRaises(DomainInvitation.DoesNotExist):
|
with self.assertRaises(DomainInvitation.DoesNotExist):
|
||||||
DomainInvitation.objects.get(id=invitation.id)
|
DomainInvitation.objects.get(id=invitation.id)
|
||||||
|
|
||||||
|
def test_domain_invitation_cancel_retrieved_invitation(self):
|
||||||
|
"""Posting to the delete view when invitation retrieved returns an error message"""
|
||||||
|
email_address = "mayor@igorville.gov"
|
||||||
|
invitation, _ = DomainInvitation.objects.get_or_create(
|
||||||
|
domain=self.domain, email=email_address, status=DomainInvitation.DomainInvitationStatus.RETRIEVED
|
||||||
|
)
|
||||||
|
with less_console_noise():
|
||||||
|
response = self.client.post(reverse("invitation-delete", kwargs={"pk": invitation.id}), follow=True)
|
||||||
|
# Assert that an error message is displayed to the user
|
||||||
|
self.assertContains(response, f"Invitation to {email_address} has already been retrieved.")
|
||||||
|
# Assert that the Cancel link is not displayed
|
||||||
|
self.assertNotContains(response, "Cancel")
|
||||||
|
# Assert that the DomainInvitation is not deleted
|
||||||
|
self.assertTrue(DomainInvitation.objects.filter(id=invitation.id).exists())
|
||||||
|
DomainInvitation.objects.filter(email=email_address).delete()
|
||||||
|
|
||||||
def test_domain_invitation_cancel_no_permissions(self):
|
def test_domain_invitation_cancel_no_permissions(self):
|
||||||
"""Posting to the delete view as a different user should fail."""
|
"""Posting to the delete view as a different user should fail."""
|
||||||
email_address = "mayor@igorville.gov"
|
email_address = "mayor@igorville.gov"
|
||||||
|
|
|
@ -80,7 +80,7 @@ def parse_domain_row(columns, domain_info: DomainInformation, security_emails_di
|
||||||
if security_email.lower() in invalid_emails:
|
if security_email.lower() in invalid_emails:
|
||||||
security_email = "(blank)"
|
security_email = "(blank)"
|
||||||
|
|
||||||
if domain_info.federal_type:
|
if domain_info.federal_type and domain_info.generic_org_type == DomainRequest.OrganizationChoices.FEDERAL:
|
||||||
domain_type = f"{domain_info.get_generic_org_type_display()} - {domain_info.get_federal_type_display()}"
|
domain_type = f"{domain_info.get_generic_org_type_display()} - {domain_info.get_federal_type_display()}"
|
||||||
else:
|
else:
|
||||||
domain_type = domain_info.get_generic_org_type_display()
|
domain_type = domain_info.get_generic_org_type_display()
|
||||||
|
@ -469,16 +469,43 @@ def get_sliced_domains(filter_condition, distinct=False):
|
||||||
domains_count = DomainInformation.objects.filter(**filter_condition).distinct().count()
|
domains_count = DomainInformation.objects.filter(**filter_condition).distinct().count()
|
||||||
|
|
||||||
# Round trip 2: Get counts for other slices
|
# Round trip 2: Get counts for other slices
|
||||||
|
# This will require either 8 filterd and distinct DB round trips,
|
||||||
|
# or 2 DB round trips plus iteration on domain_permissions for each domain
|
||||||
if distinct:
|
if distinct:
|
||||||
generic_org_types_query = (
|
generic_org_types_query = DomainInformation.objects.filter(**filter_condition).values_list(
|
||||||
DomainInformation.objects.filter(**filter_condition).values_list("generic_org_type", flat=True).distinct()
|
"domain_id", "generic_org_type"
|
||||||
)
|
)
|
||||||
|
# Initialize Counter to store counts for each generic_org_type
|
||||||
|
generic_org_type_counts = Counter()
|
||||||
|
|
||||||
|
# Keep track of domains already counted
|
||||||
|
domains_counted = set()
|
||||||
|
|
||||||
|
# Iterate over distinct domains
|
||||||
|
for domain_id, generic_org_type in generic_org_types_query:
|
||||||
|
# Check if the domain has already been counted
|
||||||
|
if domain_id in domains_counted:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Get all permissions for the current domain
|
||||||
|
domain_permissions = DomainInformation.objects.filter(domain_id=domain_id, **filter_condition).values_list(
|
||||||
|
"domain__permissions", flat=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check if the domain has multiple permissions
|
||||||
|
if len(domain_permissions) > 0:
|
||||||
|
# Mark the domain as counted
|
||||||
|
domains_counted.add(domain_id)
|
||||||
|
|
||||||
|
# Increment the count for the corresponding generic_org_type
|
||||||
|
generic_org_type_counts[generic_org_type] += 1
|
||||||
else:
|
else:
|
||||||
generic_org_types_query = DomainInformation.objects.filter(**filter_condition).values_list(
|
generic_org_types_query = DomainInformation.objects.filter(**filter_condition).values_list(
|
||||||
"generic_org_type", flat=True
|
"generic_org_type", flat=True
|
||||||
)
|
)
|
||||||
generic_org_type_counts = Counter(generic_org_types_query)
|
generic_org_type_counts = Counter(generic_org_types_query)
|
||||||
|
|
||||||
|
# Extract counts for each generic_org_type
|
||||||
federal = generic_org_type_counts.get(DomainRequest.OrganizationChoices.FEDERAL, 0)
|
federal = generic_org_type_counts.get(DomainRequest.OrganizationChoices.FEDERAL, 0)
|
||||||
interstate = generic_org_type_counts.get(DomainRequest.OrganizationChoices.INTERSTATE, 0)
|
interstate = generic_org_type_counts.get(DomainRequest.OrganizationChoices.INTERSTATE, 0)
|
||||||
state_or_territory = generic_org_type_counts.get(DomainRequest.OrganizationChoices.STATE_OR_TERRITORY, 0)
|
state_or_territory = generic_org_type_counts.get(DomainRequest.OrganizationChoices.STATE_OR_TERRITORY, 0)
|
||||||
|
@ -505,21 +532,16 @@ def get_sliced_domains(filter_condition, distinct=False):
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def get_sliced_requests(filter_condition, distinct=False):
|
def get_sliced_requests(filter_condition):
|
||||||
"""Get filtered requests counts sliced by org type and election office."""
|
"""Get filtered requests counts sliced by org type and election office."""
|
||||||
|
|
||||||
# Round trip 1: Get distinct requests based on filter condition
|
# Round trip 1: Get distinct requests based on filter condition
|
||||||
requests_count = DomainRequest.objects.filter(**filter_condition).distinct().count()
|
requests_count = DomainRequest.objects.filter(**filter_condition).distinct().count()
|
||||||
|
|
||||||
# Round trip 2: Get counts for other slices
|
# Round trip 2: Get counts for other slices
|
||||||
if distinct:
|
generic_org_types_query = DomainRequest.objects.filter(**filter_condition).values_list(
|
||||||
generic_org_types_query = (
|
"generic_org_type", flat=True
|
||||||
DomainRequest.objects.filter(**filter_condition).values_list("generic_org_type", flat=True).distinct()
|
)
|
||||||
)
|
|
||||||
else:
|
|
||||||
generic_org_types_query = DomainRequest.objects.filter(**filter_condition).values_list(
|
|
||||||
"generic_org_type", flat=True
|
|
||||||
)
|
|
||||||
generic_org_type_counts = Counter(generic_org_types_query)
|
generic_org_type_counts = Counter(generic_org_types_query)
|
||||||
|
|
||||||
federal = generic_org_type_counts.get(DomainRequest.OrganizationChoices.FEDERAL, 0)
|
federal = generic_org_type_counts.get(DomainRequest.OrganizationChoices.FEDERAL, 0)
|
||||||
|
|
|
@ -822,6 +822,18 @@ class DomainAddUserView(DomainFormBaseView):
|
||||||
class DomainInvitationDeleteView(SuccessMessageMixin, DomainInvitationPermissionDeleteView):
|
class DomainInvitationDeleteView(SuccessMessageMixin, DomainInvitationPermissionDeleteView):
|
||||||
object: DomainInvitation # workaround for type mismatch in DeleteView
|
object: DomainInvitation # workaround for type mismatch in DeleteView
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
"""Override post method in order to error in the case when the
|
||||||
|
domain invitation status is RETRIEVED"""
|
||||||
|
self.object = self.get_object()
|
||||||
|
form = self.get_form()
|
||||||
|
if form.is_valid() and self.object.status == self.object.DomainInvitationStatus.INVITED:
|
||||||
|
return self.form_valid(form)
|
||||||
|
else:
|
||||||
|
# Produce an error message if the domain invatation status is RETRIEVED
|
||||||
|
messages.error(request, f"Invitation to {self.object.email} has already been retrieved.")
|
||||||
|
return HttpResponseRedirect(self.get_success_url())
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
return reverse("domain-users", kwargs={"pk": self.object.domain.id})
|
return reverse("domain-users", kwargs={"pk": self.object.domain.id})
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue