mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-05-17 18:09:25 +02:00
Merge branch 'main' into za/1988-investigate-indexes
This commit is contained in:
commit
6a393aad51
21 changed files with 734 additions and 6142 deletions
13
.github/workflows/deploy-development.yaml
vendored
13
.github/workflows/deploy-development.yaml
vendored
|
@ -22,16 +22,9 @@ jobs:
|
||||||
- name: Compile USWDS assets
|
- name: Compile USWDS assets
|
||||||
working-directory: ./src
|
working-directory: ./src
|
||||||
run: |
|
run: |
|
||||||
docker compose run node bash -c "\
|
docker compose run node npm install &&
|
||||||
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash && \
|
docker compose run node npx gulp copyAssets &&
|
||||||
export NVM_DIR=\"\$HOME/.nvm\" && \
|
docker compose run node npx gulp compile
|
||||||
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\" && \
|
|
||||||
[ -s \"\$NVM_DIR/bash_completion\" ] && \. \"\$NVM_DIR/bash_completion\" && \
|
|
||||||
nvm install 21.7.3 && \
|
|
||||||
nvm use 21.7.3 && \
|
|
||||||
npm install && \
|
|
||||||
npx gulp copyAssets && \
|
|
||||||
npx gulp compile"
|
|
||||||
- name: Collect static assets
|
- name: Collect static assets
|
||||||
working-directory: ./src
|
working-directory: ./src
|
||||||
run: docker compose run app python manage.py collectstatic --no-input
|
run: docker compose run app python manage.py collectstatic --no-input
|
||||||
|
|
13
.github/workflows/deploy-sandbox.yaml
vendored
13
.github/workflows/deploy-sandbox.yaml
vendored
|
@ -43,16 +43,9 @@ jobs:
|
||||||
- name: Compile USWDS assets
|
- name: Compile USWDS assets
|
||||||
working-directory: ./src
|
working-directory: ./src
|
||||||
run: |
|
run: |
|
||||||
docker compose run node bash -c "\
|
docker compose run node npm install &&
|
||||||
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash && \
|
docker compose run node npx gulp copyAssets &&
|
||||||
export NVM_DIR=\"\$HOME/.nvm\" && \
|
docker compose run node npx gulp compile
|
||||||
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\" && \
|
|
||||||
[ -s \"\$NVM_DIR/bash_completion\" ] && \. \"\$NVM_DIR/bash_completion\" && \
|
|
||||||
nvm install 21.7.3 && \
|
|
||||||
nvm use 21.7.3 && \
|
|
||||||
npm install && \
|
|
||||||
npx gulp copyAssets && \
|
|
||||||
npx gulp compile"
|
|
||||||
- name: Collect static assets
|
- name: Collect static assets
|
||||||
working-directory: ./src
|
working-directory: ./src
|
||||||
run: docker compose run app python manage.py collectstatic --no-input
|
run: docker compose run app python manage.py collectstatic --no-input
|
||||||
|
|
13
.github/workflows/deploy-stable.yaml
vendored
13
.github/workflows/deploy-stable.yaml
vendored
|
@ -23,16 +23,9 @@ jobs:
|
||||||
- name: Compile USWDS assets
|
- name: Compile USWDS assets
|
||||||
working-directory: ./src
|
working-directory: ./src
|
||||||
run: |
|
run: |
|
||||||
docker compose run node bash -c "\
|
docker compose run node npm install &&
|
||||||
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash && \
|
docker compose run node npx gulp copyAssets &&
|
||||||
export NVM_DIR=\"\$HOME/.nvm\" && \
|
docker compose run node npx gulp compile
|
||||||
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\" && \
|
|
||||||
[ -s \"\$NVM_DIR/bash_completion\" ] && \. \"\$NVM_DIR/bash_completion\" && \
|
|
||||||
nvm install 21.7.3 && \
|
|
||||||
nvm use 21.7.3 && \
|
|
||||||
npm install && \
|
|
||||||
npx gulp copyAssets && \
|
|
||||||
npx gulp compile"
|
|
||||||
- name: Collect static assets
|
- name: Collect static assets
|
||||||
working-directory: ./src
|
working-directory: ./src
|
||||||
run: docker compose run app python manage.py collectstatic --no-input
|
run: docker compose run app python manage.py collectstatic --no-input
|
||||||
|
|
13
.github/workflows/deploy-staging.yaml
vendored
13
.github/workflows/deploy-staging.yaml
vendored
|
@ -23,16 +23,9 @@ jobs:
|
||||||
- name: Compile USWDS assets
|
- name: Compile USWDS assets
|
||||||
working-directory: ./src
|
working-directory: ./src
|
||||||
run: |
|
run: |
|
||||||
docker compose run node bash -c "\
|
docker compose run node npm install &&
|
||||||
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash && \
|
docker compose run node npx gulp copyAssets &&
|
||||||
export NVM_DIR=\"\$HOME/.nvm\" && \
|
docker compose run node npx gulp compile
|
||||||
[ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\" && \
|
|
||||||
[ -s \"\$NVM_DIR/bash_completion\" ] && \. \"\$NVM_DIR/bash_completion\" && \
|
|
||||||
nvm install 21.7.3 && \
|
|
||||||
nvm use 21.7.3 && \
|
|
||||||
npm install && \
|
|
||||||
npx gulp copyAssets && \
|
|
||||||
npx gulp compile"
|
|
||||||
- name: Collect static assets
|
- name: Collect static assets
|
||||||
working-directory: ./src
|
working-directory: ./src
|
||||||
run: docker compose run app python manage.py collectstatic --no-input
|
run: docker compose run app python manage.py collectstatic --no-input
|
||||||
|
|
|
@ -33,4 +33,5 @@ exports.init = uswds.init;
|
||||||
exports.compile = uswds.compile;
|
exports.compile = uswds.compile;
|
||||||
exports.watch = uswds.watch;
|
exports.watch = uswds.watch;
|
||||||
exports.copyAssets = uswds.copyAssets
|
exports.copyAssets = uswds.copyAssets
|
||||||
|
exports.updateUswds = uswds.updateUswds
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
FROM docker.io/cimg/node:current-browsers
|
FROM docker.io/cimg/node:current-browsers
|
||||||
FROM node:21.7.3
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Install app dependencies
|
# Install app dependencies
|
||||||
|
@ -7,6 +6,4 @@ WORKDIR /app
|
||||||
# where available (npm@5+)
|
# where available (npm@5+)
|
||||||
COPY --chown=circleci:circleci package*.json ./
|
COPY --chown=circleci:circleci package*.json ./
|
||||||
|
|
||||||
|
|
||||||
RUN npm install -g npm@10.5.0
|
|
||||||
RUN npm install
|
RUN npm install
|
6616
src/package-lock.json
generated
6616
src/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -3,11 +3,6 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "========================",
|
"description": "========================",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"engines": {
|
|
||||||
"node": "21.7.3",
|
|
||||||
"npm": "10.5.0"
|
|
||||||
},
|
|
||||||
"engineStrict": true,
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"pa11y-ci": "pa11y-ci",
|
"pa11y-ci": "pa11y-ci",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
@ -15,7 +10,7 @@
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@uswds/uswds": "^3.3.0",
|
"@uswds/uswds": "^3.8.0",
|
||||||
"pa11y-ci": "^3.0.1",
|
"pa11y-ci": "^3.0.1",
|
||||||
"sass": "^1.54.8"
|
"sass": "^1.54.8"
|
||||||
},
|
},
|
||||||
|
|
|
@ -594,7 +594,7 @@ class MyUserAdmin(BaseUserAdmin, ImportExportModelAdmin):
|
||||||
None,
|
None,
|
||||||
{"fields": ("username", "password", "status", "verification_type")},
|
{"fields": ("username", "password", "status", "verification_type")},
|
||||||
),
|
),
|
||||||
("Personal Info", {"fields": ("first_name", "last_name", "email")}),
|
("Personal Info", {"fields": ("first_name", "middle_name", "last_name", "email", "title")}),
|
||||||
(
|
(
|
||||||
"Permissions",
|
"Permissions",
|
||||||
{
|
{
|
||||||
|
@ -625,7 +625,7 @@ class MyUserAdmin(BaseUserAdmin, ImportExportModelAdmin):
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
("Personal Info", {"fields": ("first_name", "last_name", "email")}),
|
("Personal Info", {"fields": ("first_name", "middle_name", "last_name", "email", "title")}),
|
||||||
(
|
(
|
||||||
"Permissions",
|
"Permissions",
|
||||||
{
|
{
|
||||||
|
@ -651,7 +651,9 @@ class MyUserAdmin(BaseUserAdmin, ImportExportModelAdmin):
|
||||||
analyst_readonly_fields = [
|
analyst_readonly_fields = [
|
||||||
"Personal Info",
|
"Personal Info",
|
||||||
"first_name",
|
"first_name",
|
||||||
|
"middle_name",
|
||||||
"last_name",
|
"last_name",
|
||||||
|
"title",
|
||||||
"email",
|
"email",
|
||||||
"Permissions",
|
"Permissions",
|
||||||
"is_active",
|
"is_active",
|
||||||
|
@ -1124,7 +1126,6 @@ class DomainInformationAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
||||||
"Type of organization",
|
"Type of organization",
|
||||||
{
|
{
|
||||||
"fields": [
|
"fields": [
|
||||||
"generic_org_type",
|
|
||||||
"is_election_board",
|
"is_election_board",
|
||||||
"organization_type",
|
"organization_type",
|
||||||
]
|
]
|
||||||
|
@ -1171,7 +1172,7 @@ class DomainInformationAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
||||||
]
|
]
|
||||||
|
|
||||||
# Readonly fields for analysts and superusers
|
# Readonly fields for analysts and superusers
|
||||||
readonly_fields = ("other_contacts", "generic_org_type", "is_election_board")
|
readonly_fields = ("other_contacts", "is_election_board", "federal_agency")
|
||||||
|
|
||||||
# Read only that we'll leverage for CISA Analysts
|
# Read only that we'll leverage for CISA Analysts
|
||||||
analyst_readonly_fields = [
|
analyst_readonly_fields = [
|
||||||
|
@ -1385,7 +1386,6 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
||||||
"Type of organization",
|
"Type of organization",
|
||||||
{
|
{
|
||||||
"fields": [
|
"fields": [
|
||||||
"generic_org_type",
|
|
||||||
"is_election_board",
|
"is_election_board",
|
||||||
"organization_type",
|
"organization_type",
|
||||||
]
|
]
|
||||||
|
@ -1436,8 +1436,8 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
||||||
"other_contacts",
|
"other_contacts",
|
||||||
"current_websites",
|
"current_websites",
|
||||||
"alternative_domains",
|
"alternative_domains",
|
||||||
"generic_org_type",
|
|
||||||
"is_election_board",
|
"is_election_board",
|
||||||
|
"federal_agency",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Read only that we'll leverage for CISA Analysts
|
# Read only that we'll leverage for CISA Analysts
|
||||||
|
@ -1879,7 +1879,7 @@ class DomainAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
||||||
search_fields = ["name"]
|
search_fields = ["name"]
|
||||||
search_help_text = "Search by domain name."
|
search_help_text = "Search by domain name."
|
||||||
change_form_template = "django/admin/domain_change_form.html"
|
change_form_template = "django/admin/domain_change_form.html"
|
||||||
readonly_fields = ["state", "expiration_date", "first_ready", "deleted"]
|
readonly_fields = ("state", "expiration_date", "first_ready", "deleted", "federal_agency")
|
||||||
|
|
||||||
# Table ordering
|
# Table ordering
|
||||||
ordering = ["name"]
|
ordering = ["name"]
|
||||||
|
|
|
@ -385,7 +385,6 @@ class DomainOrgNameAddressForm(forms.ModelForm):
|
||||||
# because for this fields we are creating an individual
|
# because for this fields we are creating an individual
|
||||||
# instance of the Select. For the other fields we use the for loop to set
|
# instance of the Select. For the other fields we use the for loop to set
|
||||||
# the class's required attribute to true.
|
# the class's required attribute to true.
|
||||||
"federal_agency": forms.TextInput,
|
|
||||||
"organization_name": forms.TextInput,
|
"organization_name": forms.TextInput,
|
||||||
"address_line1": forms.TextInput,
|
"address_line1": forms.TextInput,
|
||||||
"address_line2": forms.TextInput,
|
"address_line2": forms.TextInput,
|
||||||
|
|
37
src/registrar/migrations/0094_create_groups_v12.py
Normal file
37
src/registrar/migrations/0094_create_groups_v12.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", "0093_alter_publiccontact_unique_together"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(
|
||||||
|
create_groups,
|
||||||
|
reverse_code=migrations.RunPython.noop,
|
||||||
|
atomic=True,
|
||||||
|
),
|
||||||
|
]
|
23
src/registrar/migrations/0095_user_middle_name_user_title.py
Normal file
23
src/registrar/migrations/0095_user_middle_name_user_title.py
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# Generated by Django 4.2.10 on 2024-05-22 14:54
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("registrar", "0094_create_groups_v12"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="user",
|
||||||
|
name="middle_name",
|
||||||
|
field=models.CharField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="user",
|
||||||
|
name="title",
|
||||||
|
field=models.CharField(blank=True, null=True, verbose_name="title / role"),
|
||||||
|
),
|
||||||
|
]
|
|
@ -9,6 +9,7 @@ from django.db import models
|
||||||
from django_fsm import FSMField, transition # type: ignore
|
from django_fsm import FSMField, transition # type: ignore
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from registrar.models.domain import Domain
|
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.generic_helper import CreateOrUpdateOrganizationTypeHelper
|
||||||
from registrar.utility.errors import FSMDomainRequestError, FSMErrorCodes
|
from registrar.utility.errors import FSMDomainRequestError, FSMErrorCodes
|
||||||
|
|
||||||
|
@ -758,6 +759,10 @@ class DomainRequest(TimeStampedModel):
|
||||||
domain request into an admin on that domain. It also triggers an email
|
domain request into an admin on that domain. It also triggers an email
|
||||||
notification."""
|
notification."""
|
||||||
|
|
||||||
|
if self.federal_agency is None:
|
||||||
|
self.federal_agency = FederalAgency.objects.filter(agency="Non-Federal Agency").first()
|
||||||
|
self.save()
|
||||||
|
|
||||||
# create the domain
|
# create the domain
|
||||||
Domain = apps.get_model("registrar.Domain")
|
Domain = apps.get_model("registrar.Domain")
|
||||||
|
|
||||||
|
|
|
@ -90,6 +90,17 @@ class User(AbstractUser):
|
||||||
help_text="Phone",
|
help_text="Phone",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
middle_name = models.CharField(
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
title = models.CharField(
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
verbose_name="title / role",
|
||||||
|
)
|
||||||
|
|
||||||
verification_type = models.CharField(
|
verification_type = models.CharField(
|
||||||
choices=VerificationTypeChoices.choices,
|
choices=VerificationTypeChoices.choices,
|
||||||
null=True,
|
null=True,
|
||||||
|
|
|
@ -81,7 +81,7 @@
|
||||||
<div class="grid-col-auto">
|
<div class="grid-col-auto">
|
||||||
<img class="usa-banner__header-flag" src="{% static 'img/us_flag_small.png' %}" alt="U.S. flag" />
|
<img class="usa-banner__header-flag" src="{% static 'img/us_flag_small.png' %}" alt="U.S. flag" />
|
||||||
</div>
|
</div>
|
||||||
<div class="grid-col-fill tablet:grid-col-auto">
|
<div class="grid-col-fill tablet:grid-col-auto" aria-hidden="true">
|
||||||
<p class="usa-banner__header-text">
|
<p class="usa-banner__header-text">
|
||||||
An official website of the United States government
|
An official website of the United States government
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -93,7 +93,7 @@
|
||||||
</li>
|
</li>
|
||||||
<li class="usa-identifier__required-links-item">
|
<li class="usa-identifier__required-links-item">
|
||||||
<a rel="noopener noreferrer" target="_blank" href="https://www.dhs.gov/accessibility" class="usa-identifier__required-link usa-link usa-link--external"
|
<a rel="noopener noreferrer" target="_blank" href="https://www.dhs.gov/accessibility" class="usa-identifier__required-link usa-link usa-link--external"
|
||||||
>Accessibility</a
|
>Accessibility statement</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li class="usa-identifier__required-links-item">
|
<li class="usa-identifier__required-links-item">
|
||||||
|
|
|
@ -646,7 +646,7 @@ class TestDomainAdmin(MockEppLib, WebTest):
|
||||||
response = self.client.get("/admin/registrar/domain/")
|
response = self.client.get("/admin/registrar/domain/")
|
||||||
# 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=54)
|
self.assertContains(response, "Federal", count=56)
|
||||||
# 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
|
||||||
|
@ -2230,8 +2230,8 @@ class TestDomainRequestAdmin(MockEppLib):
|
||||||
"other_contacts",
|
"other_contacts",
|
||||||
"current_websites",
|
"current_websites",
|
||||||
"alternative_domains",
|
"alternative_domains",
|
||||||
"generic_org_type",
|
|
||||||
"is_election_board",
|
"is_election_board",
|
||||||
|
"federal_agency",
|
||||||
"id",
|
"id",
|
||||||
"created_at",
|
"created_at",
|
||||||
"updated_at",
|
"updated_at",
|
||||||
|
@ -2284,8 +2284,8 @@ class TestDomainRequestAdmin(MockEppLib):
|
||||||
"other_contacts",
|
"other_contacts",
|
||||||
"current_websites",
|
"current_websites",
|
||||||
"alternative_domains",
|
"alternative_domains",
|
||||||
"generic_org_type",
|
|
||||||
"is_election_board",
|
"is_election_board",
|
||||||
|
"federal_agency",
|
||||||
"creator",
|
"creator",
|
||||||
"about_your_organization",
|
"about_your_organization",
|
||||||
"requested_domain",
|
"requested_domain",
|
||||||
|
@ -2312,8 +2312,8 @@ class TestDomainRequestAdmin(MockEppLib):
|
||||||
"other_contacts",
|
"other_contacts",
|
||||||
"current_websites",
|
"current_websites",
|
||||||
"alternative_domains",
|
"alternative_domains",
|
||||||
"generic_org_type",
|
|
||||||
"is_election_board",
|
"is_election_board",
|
||||||
|
"federal_agency",
|
||||||
]
|
]
|
||||||
|
|
||||||
self.assertEqual(readonly_fields, expected_fields)
|
self.assertEqual(readonly_fields, expected_fields)
|
||||||
|
@ -3170,8 +3170,8 @@ class TestDomainInformationAdmin(TestCase):
|
||||||
|
|
||||||
expected_fields = [
|
expected_fields = [
|
||||||
"other_contacts",
|
"other_contacts",
|
||||||
"generic_org_type",
|
|
||||||
"is_election_board",
|
"is_election_board",
|
||||||
|
"federal_agency",
|
||||||
"creator",
|
"creator",
|
||||||
"type_of_work",
|
"type_of_work",
|
||||||
"more_organization_information",
|
"more_organization_information",
|
||||||
|
@ -3534,7 +3534,7 @@ class TestMyUserAdmin(TestCase):
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
("Personal Info", {"fields": ("first_name", "last_name", "email")}),
|
("Personal Info", {"fields": ("first_name", "middle_name", "last_name", "email", "title")}),
|
||||||
("Permissions", {"fields": ("is_active", "groups")}),
|
("Permissions", {"fields": ("is_active", "groups")}),
|
||||||
("Important dates", {"fields": ("last_login", "date_joined")}),
|
("Important dates", {"fields": ("last_login", "date_joined")}),
|
||||||
)
|
)
|
||||||
|
|
|
@ -12,6 +12,7 @@ from registrar.models import (
|
||||||
DraftDomain,
|
DraftDomain,
|
||||||
DomainInvitation,
|
DomainInvitation,
|
||||||
UserDomainRole,
|
UserDomainRole,
|
||||||
|
FederalAgency,
|
||||||
)
|
)
|
||||||
|
|
||||||
import boto3_mocking
|
import boto3_mocking
|
||||||
|
@ -75,6 +76,26 @@ class TestDomainRequest(TestCase):
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
return self.assertRaises(Exception, None, exception_type)
|
return self.assertRaises(Exception, None, exception_type)
|
||||||
|
|
||||||
|
def test_federal_agency_set_to_non_federal_on_approve(self):
|
||||||
|
"""Ensures that when the federal_agency field is 'none' when .approve() is called,
|
||||||
|
the field is set to the 'Non-Federal Agency' record"""
|
||||||
|
domain_request = completed_domain_request(
|
||||||
|
status=DomainRequest.DomainRequestStatus.IN_REVIEW,
|
||||||
|
name="city2.gov",
|
||||||
|
federal_agency=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Ensure that the federal agency is None
|
||||||
|
self.assertEqual(domain_request.federal_agency, None)
|
||||||
|
|
||||||
|
# Approve the request
|
||||||
|
domain_request.approve()
|
||||||
|
self.assertEqual(domain_request.status, DomainRequest.DomainRequestStatus.APPROVED)
|
||||||
|
|
||||||
|
# After approval, it should be "Non-Federal agency"
|
||||||
|
expected_federal_agency = FederalAgency.objects.filter(agency="Non-Federal Agency").first()
|
||||||
|
self.assertEqual(domain_request.federal_agency, expected_federal_agency)
|
||||||
|
|
||||||
def test_empty_create_fails(self):
|
def test_empty_create_fails(self):
|
||||||
"""Can't create a completely empty domain request.
|
"""Can't create a completely empty domain request.
|
||||||
NOTE: something about theexception this test raises messes up with the
|
NOTE: something about theexception this test raises messes up with the
|
||||||
|
@ -942,6 +963,7 @@ class TestDomainInformation(TestCase):
|
||||||
domain=domain,
|
domain=domain,
|
||||||
notes="test notes",
|
notes="test notes",
|
||||||
domain_request=domain_request,
|
domain_request=domain_request,
|
||||||
|
federal_agency=FederalAgency.objects.get(agency="Non-Federal Agency"),
|
||||||
).__dict__
|
).__dict__
|
||||||
|
|
||||||
# Test the two records for consistency
|
# Test the two records for consistency
|
||||||
|
|
|
@ -1447,7 +1447,7 @@ class TestDomainOrganization(TestDomainOverview):
|
||||||
|
|
||||||
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
||||||
|
|
||||||
org_name_page.form["federal_agency"] = "Department of State"
|
org_name_page.form["federal_agency"] = FederalAgency.objects.filter(agency="Department of State").get().id
|
||||||
org_name_page.form["city"] = "Faketown"
|
org_name_page.form["city"] = "Faketown"
|
||||||
|
|
||||||
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
|
@ -1456,9 +1456,8 @@ class TestDomainOrganization(TestDomainOverview):
|
||||||
success_result_page = org_name_page.form.submit()
|
success_result_page = org_name_page.form.submit()
|
||||||
self.assertEqual(success_result_page.status_code, 200)
|
self.assertEqual(success_result_page.status_code, 200)
|
||||||
|
|
||||||
# Check for the old and new value
|
# Check that the agency has not changed
|
||||||
self.assertContains(success_result_page, federal_agency.id)
|
self.assertEqual(self.domain_information.federal_agency.agency, "AMTRAK")
|
||||||
self.assertNotContains(success_result_page, "Department of State")
|
|
||||||
|
|
||||||
# Do another check on the form itself
|
# Do another check on the form itself
|
||||||
form = success_result_page.forms[0]
|
form = success_result_page.forms[0]
|
||||||
|
|
|
@ -32,8 +32,9 @@ def send_templated_email(
|
||||||
template_name and subject_template_name are relative to the same template
|
template_name and subject_template_name are relative to the same template
|
||||||
context as Django's HTML templates. context gives additional information
|
context as Django's HTML templates. context gives additional information
|
||||||
that the template may use.
|
that the template may use.
|
||||||
|
|
||||||
|
Raises EmailSendingError if SES client could not be accessed
|
||||||
"""
|
"""
|
||||||
logger.info(f"An email was sent! Template name: {template_name} to {to_address}")
|
|
||||||
template = get_template(template_name)
|
template = get_template(template_name)
|
||||||
email_body = template.render(context=context)
|
email_body = template.render(context=context)
|
||||||
|
|
||||||
|
@ -48,7 +49,9 @@ def send_templated_email(
|
||||||
aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY,
|
aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY,
|
||||||
config=settings.BOTO_CONFIG,
|
config=settings.BOTO_CONFIG,
|
||||||
)
|
)
|
||||||
|
logger.info(f"An email was sent! Template name: {template_name} to {to_address}")
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
|
logger.debug("E-mail unable to send! Could not access the SES client.")
|
||||||
raise EmailSendingError("Could not access the SES client.") from exc
|
raise EmailSendingError("Could not access the SES client.") from exc
|
||||||
|
|
||||||
destination = {"ToAddresses": [to_address]}
|
destination = {"ToAddresses": [to_address]}
|
||||||
|
|
|
@ -734,7 +734,10 @@ class DomainAddUserView(DomainFormBaseView):
|
||||||
does not make a domain information object
|
does not make a domain information object
|
||||||
email: string- email to send to
|
email: string- email to send to
|
||||||
add_success: bool- default True indicates:
|
add_success: bool- default True indicates:
|
||||||
adding a success message to the view if the email sending succeeds"""
|
adding a success message to the view if the email sending succeeds
|
||||||
|
|
||||||
|
raises EmailSendingError
|
||||||
|
"""
|
||||||
|
|
||||||
# Set a default email address to send to for staff
|
# Set a default email address to send to for staff
|
||||||
requestor_email = settings.DEFAULT_FROM_EMAIL
|
requestor_email = settings.DEFAULT_FROM_EMAIL
|
||||||
|
@ -762,33 +765,43 @@ class DomainAddUserView(DomainFormBaseView):
|
||||||
"requestor_email": requestor_email,
|
"requestor_email": requestor_email,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
except EmailSendingError:
|
except EmailSendingError as exc:
|
||||||
messages.warning(self.request, "Could not send email invitation.")
|
|
||||||
logger.warn(
|
logger.warn(
|
||||||
"Could not sent email invitation to %s for domain %s",
|
"Could not sent email invitation to %s for domain %s",
|
||||||
email,
|
email,
|
||||||
self.object,
|
self.object,
|
||||||
exc_info=True,
|
exc_info=True,
|
||||||
)
|
)
|
||||||
|
raise EmailSendingError("Could not send email invitation.") from exc
|
||||||
else:
|
else:
|
||||||
if add_success:
|
if add_success:
|
||||||
messages.success(self.request, f"{email} has been invited to this domain.")
|
messages.success(self.request, f"{email} has been invited to this domain.")
|
||||||
|
|
||||||
def _make_invitation(self, email_address: str, requestor: User):
|
def _make_invitation(self, email_address: str, requestor: User):
|
||||||
"""Make a Domain invitation for this email and redirect with a message."""
|
"""Make a Domain invitation for this email and redirect with a message."""
|
||||||
invitation, created = DomainInvitation.objects.get_or_create(email=email_address, domain=self.object)
|
# Check to see if an invite has already been sent (NOTE: we do not want to create an invite just yet.)
|
||||||
if not created:
|
try:
|
||||||
|
invite = DomainInvitation.objects.get(email=email_address, domain=self.object)
|
||||||
# that invitation already existed
|
# that invitation already existed
|
||||||
messages.warning(
|
if invite is not None:
|
||||||
self.request,
|
messages.warning(
|
||||||
f"{email_address} has already been invited to this domain.",
|
self.request,
|
||||||
)
|
f"{email_address} has already been invited to this domain.",
|
||||||
else:
|
)
|
||||||
self._send_domain_invitation_email(email=email_address, requestor=requestor)
|
except DomainInvitation.DoesNotExist:
|
||||||
|
# Try to send the invitation. If it succeeds, add it to the DomainInvitation table.
|
||||||
|
try:
|
||||||
|
self._send_domain_invitation_email(email=email_address, requestor=requestor)
|
||||||
|
except EmailSendingError:
|
||||||
|
messages.warning(self.request, "Could not send email invitation.")
|
||||||
|
else:
|
||||||
|
# (NOTE: only create a domainInvitation if the e-mail sends correctly)
|
||||||
|
DomainInvitation.objects.get_or_create(email=email_address, domain=self.object)
|
||||||
return redirect(self.get_success_url())
|
return redirect(self.get_success_url())
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
"""Add the specified user on this domain."""
|
"""Add the specified user on this domain.
|
||||||
|
Throws EmailSendingError."""
|
||||||
requested_email = form.cleaned_data["email"]
|
requested_email = form.cleaned_data["email"]
|
||||||
requestor = self.request.user
|
requestor = self.request.user
|
||||||
# look up a user with that email
|
# look up a user with that email
|
||||||
|
@ -799,7 +812,22 @@ class DomainAddUserView(DomainFormBaseView):
|
||||||
return self._make_invitation(requested_email, requestor)
|
return self._make_invitation(requested_email, requestor)
|
||||||
else:
|
else:
|
||||||
# if user already exists then just send an email
|
# if user already exists then just send an email
|
||||||
self._send_domain_invitation_email(requested_email, requestor, add_success=False)
|
try:
|
||||||
|
self._send_domain_invitation_email(requested_email, requestor, add_success=False)
|
||||||
|
except EmailSendingError:
|
||||||
|
logger.warn(
|
||||||
|
"Could not send email invitation (EmailSendingError)",
|
||||||
|
self.object,
|
||||||
|
exc_info=True,
|
||||||
|
)
|
||||||
|
messages.warning(self.request, "Could not send email invitation.")
|
||||||
|
except Exception:
|
||||||
|
logger.warn(
|
||||||
|
"Could not send email invitation (Other Exception)",
|
||||||
|
self.object,
|
||||||
|
exc_info=True,
|
||||||
|
)
|
||||||
|
messages.warning(self.request, "Could not send email invitation.")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
UserDomainRole.objects.create(
|
UserDomainRole.objects.create(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue