Merge remote-tracking branch 'origin/main' into nl/2854-org-member-invitation

This commit is contained in:
CocoByte 2024-11-14 10:12:02 -07:00
commit 00c7b9007a
No known key found for this signature in database
GPG key ID: BBFAA2526384C97F
41 changed files with 660 additions and 248 deletions

View file

@ -1,11 +1,9 @@
name: Clone Staging Database
on:
# these will be uncommented after testing
# ----
# schedule:
# # Run daily at 2:00 PM EST
# - cron: '0 * * * *'
schedule:
# Run daily at 2:00 PM EST
- cron: '0 * * * *'
# Allow manual triggering
workflow_dispatch:
@ -21,6 +19,7 @@ jobs:
CF_USERNAME: ${{ secrets.CF_MS_USERNAME }}
CF_PASSWORD: ${{ secrets.CF_MS_PASSWORD }}
steps:
- uses: GitHubSecurityLab/actions-permissions/monitor@v1
- name: Clone Database
run: |
# install cf cli and other tools
@ -43,7 +42,7 @@ jobs:
# clone from source to destination
cf target -s $SOURCE_ENVIRONMENT
cg-manage-rds clone getgov-$DESTINATION_ENVIRONMENT-database getgov-$SOURCE_ENVIRONMENT-database
# unshare the service
cf unshare-service getgov-$DESTINATION_ENVIRONMENT-database -s $SOURCE_ENVIRONMENT
cg-manage-rds clone getgov-$SOURCE_ENVIRONMENT-database getgov-$DESTINATION_ENVIRONMENT-database
- name: Cleanup
if: always()
run: cf unshare-service getgov-$DESTINATION_ENVIRONMENT-database -s $SOURCE_ENVIRONMENT -f

View file

@ -37,6 +37,7 @@ jobs:
CF_USERNAME: CF_${{ github.event.inputs.environment }}_USERNAME
CF_PASSWORD: CF_${{ github.event.inputs.environment }}_PASSWORD
steps:
- uses: GitHubSecurityLab/actions-permissions/monitor@v1
- name: Create cache table for ${{ github.event.inputs.environment }}
uses: cloud-gov/cg-cli-tools@main
with:

View file

@ -13,6 +13,7 @@ jobs:
CF_USERNAME: CF_${{ secrets.CF_REPORT_ENV }}_USERNAME
CF_PASSWORD: CF_${{ secrets.CF_REPORT_ENV }}_PASSWORD
steps:
- uses: GitHubSecurityLab/actions-permissions/monitor@v1
- name: Generate current-federal.csv
uses: cloud-gov/cg-cli-tools@main
with:

View file

@ -17,6 +17,8 @@ jobs:
deploy-development:
runs-on: ubuntu-latest
steps:
- uses: GitHubSecurityLab/actions-permissions/monitor@v1
- uses: actions/checkout@v3
- name: Compile USWDS assets

View file

@ -44,6 +44,7 @@ jobs:
variables:
runs-on: ubuntu-latest
steps:
- uses: GitHubSecurityLab/actions-permissions/monitor@v1
- name: Setting global variables
uses: actions/github-script@v6
id: var
@ -53,6 +54,7 @@ jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: GitHubSecurityLab/actions-permissions/monitor@v1
- uses: actions/checkout@v3
- name: Compile USWDS assets
working-directory: ./src

View file

@ -35,6 +35,7 @@ jobs:
environment: ${{ steps.var.outputs.environment}}
runs-on: "ubuntu-latest"
steps:
- uses: GitHubSecurityLab/actions-permissions/monitor@v1
- name: Setting global variables
uses: actions/github-script@v6
id: var
@ -45,6 +46,7 @@ jobs:
runs-on: ubuntu-latest
needs: [variables]
steps:
- uses: GitHubSecurityLab/actions-permissions/monitor@v1
- uses: actions/checkout@v3
- name: Compile USWDS assets
working-directory: ./src

View file

@ -18,6 +18,7 @@ jobs:
if: ${{ github.ref_type == 'tag' }}
runs-on: ubuntu-latest
steps:
- uses: GitHubSecurityLab/actions-permissions/monitor@v1
- uses: actions/checkout@v3
- name: Compile USWDS assets

View file

@ -18,6 +18,7 @@ jobs:
if: ${{ github.ref_type == 'tag' }}
runs-on: ubuntu-latest
steps:
- uses: GitHubSecurityLab/actions-permissions/monitor@v1
- uses: actions/checkout@v3
- name: Compile USWDS assets

View file

@ -10,6 +10,7 @@ jobs:
notify:
runs-on: ubuntu-latest
steps:
- uses: GitHubSecurityLab/actions-permissions/monitor@v1
- uses: jenschelkopf/issue-label-notification-action@1.3
with:
recipients: |

View file

@ -45,6 +45,7 @@ jobs:
CF_USERNAME: CF_${{ github.event.inputs.environment }}_USERNAME
CF_PASSWORD: CF_${{ github.event.inputs.environment }}_PASSWORD
steps:
- uses: GitHubSecurityLab/actions-permissions/monitor@v1
- name: Run Django migrations for ${{ github.event.inputs.environment }}
uses: cloud-gov/cg-cli-tools@main
with:

View file

@ -45,6 +45,7 @@ jobs:
CF_USERNAME: CF_${{ github.event.inputs.environment }}_USERNAME
CF_PASSWORD: CF_${{ github.event.inputs.environment }}_PASSWORD
steps:
- uses: GitHubSecurityLab/actions-permissions/monitor@v1
- name: Delete existing data for ${{ github.event.inputs.environment }}
uses: cloud-gov/cg-cli-tools@main
with:

View file

@ -54,6 +54,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: GitHubSecurityLab/actions-permissions/monitor@v1
- name: Check out
uses: actions/checkout@v3
- name: MockUserLogin should not be in settings.MIDDLEWARE

View file

@ -21,6 +21,7 @@ jobs:
python-linting:
runs-on: ubuntu-latest
steps:
- uses: GitHubSecurityLab/actions-permissions/monitor@v1
- uses: actions/checkout@v3
- name: Linting
@ -32,6 +33,7 @@ jobs:
python-test:
runs-on: ubuntu-latest
steps:
- uses: GitHubSecurityLab/actions-permissions/monitor@v1
- uses: actions/checkout@v3
- name: Unit tests
@ -41,6 +43,7 @@ jobs:
django-migrations-complete:
runs-on: ubuntu-latest
steps:
- uses: GitHubSecurityLab/actions-permissions/monitor@v1
- uses: actions/checkout@v3
- name: Check for complete migrations

View file

@ -3,7 +3,7 @@
Secrets are read from the running environment.
Secrets were originally created with:
Secrets are originally created with:
```sh
cf cups getgov-credentials -p credentials-<ENVIRONMENT>.json
@ -38,6 +38,49 @@ cf restage getgov-stable --strategy rolling
Non-secret environment variables can be declared in `manifest-<ENVIRONMENT>.json` directly.
## Rotating login.gov credentials
The DJANGO_SECRET_KEY and DJANGO_SECRET_LOGIN_KEY are reset once a year for each sandbox, see their sections below for more information on them and how to manually generate these keys. To save time, complete the following steps to rotate these credentials using a script in non-production environments:
### Step 1 login
To run the script make sure you are logged on the cf cli and make sure you have access to the [Login Partner Dashboard](https://dashboard.int.identitysandbox.gov/service_providers/2640).
### Step 2 Run the script
Run the following where "ENV" refers to whichever sandbox you want to reset credentials on. Note, the below assumes you are in the root directory of our app.
```bash
ops/scripts/rotate_login_certs.sh ENV
```
### Step 3 Respond to the terminal prompts
Respond to the prompts from the script and, when it asks for the cert information, the below is an example of what you should enter. Note for "Common Name" you should put the name of the sandbox and for "Email Address" it should be the address of who owns that sandbox (such as the developer's email, if it's a develop sandbox, or whoever ran this action otherwise)
```bash
Country Name (2 letter code) [AU]:US
State or Province Name (full name) [Some-State]:DC
Locality Name (eg, city) []:DC
Organization Name (eg, company) [Internet Widgits Pty Ltd]:DHS
Organizational Unit Name (eg, section) []:CISA
Common Name (e.g. server FQDN or YOUR name) []:ENV
Email Address []: example@something.com
```
Note when this script is done it will have generated a .pem and a .crt file, as well as updated the cert info on the sandbox
### Step 4 Delete the old cert
Navigate to to the Login Partner Dashboard linked above and delete the old cert
### Step 5 add the new cert
In whichever directory you ran the script there should now be a .crt file named "public-ENV.crt", where ENV is the space name you used on Step 2. Upload this cert in the Login Partner Dashboard in the same section where you deleted the old one.
### Production only
This script should not be run in production. Instead, you will need to manually create the keys and then refrain from updating the sandbox. Once the cert is created you will upload it to the Login Partner Dashboard for our production system, and then open a ticket with them to update our existing Login.gov integration. Once they respond back saying it has been applied, you can then update the sandbox.
## DJANGO_SECRET_KEY
This is a standard Django secret key. See Django documentation for tips on generating a new one.
@ -46,6 +89,7 @@ This is a standard Django secret key. See Django documentation for tips on gener
This is the base64 encoded private key used in the OpenID Connect authentication flow with Login.gov. It is used to sign a token during user login; the signature is examined by Login.gov before their API grants access to user data.
### Manually creating creating the Login Key
Generate a new key using this command (or whatever is most recently [recommended by Login.gov](https://developers.login.gov/testing/#creating-a-public-certificate)):
```bash
@ -60,6 +104,8 @@ base64 private.pem
You also need to upload the `public.crt` key if recently created to the login.gov identity sandbox: https://dashboard.int.identitysandbox.gov/
## AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY
To access the AWS Simple Email Service, we need credentials from the CISA AWS
@ -76,6 +122,8 @@ These are the client certificate and its private key used to identify the regist
The private key is protected by a passphrase for safer transport and storage.
Note this must be reset once a year.
These were generated with the following steps:
### Step 1: Generate an unencrypted private key with a named curve
@ -90,7 +138,7 @@ openssl ecparam -name prime256v1 -genkey -out client_unencrypted.key
openssl pkcs8 -topk8 -v2 aes-256-cbc -in client_unencrypted.key -out client.key
```
### Generate the certificate
### Step 3: Generate the certificate
```bash
openssl req -new -x509 -days 365 -key client.key -out client.crt -subj "/C=US/ST=DC/L=Washington/O=GSA/OU=18F/CN=GOV Prototype Registrar"
@ -112,7 +160,7 @@ base64 -i client.key
base64 -i client.crt
```
You'll need to give the new certificate to the registry vendor _before_ rotating it in production. Once it has been accepted by the vendor, make sure to update the kdbx file on Google Drive.
You'll need to give the new certificate to the registry vendor _before_ rotating it in production. Once it has been accepted by the vendor, make sure to update [the KBDX](https://docs.google.com/document/d/1_BbJmjYZNYLNh4jJPPnUEG9tFCzJrOc0nMrZrnSKKyw) file on Google Drive.
## REGISTRY_HOSTNAME

View file

@ -0,0 +1,51 @@
# This script rotates the login.gov credentials, DJANGO_SECRET_KEY and DJANGO_SECRET_LOGIN_KEY that allow for identity sandbox to work on sandboxes and local.
# The echo prints in this script should serve for documentation for running manually.
# Run this script once a year for each environment
# NOTE: This script was written for MacOS and to be run at the root directory.
if [ -z "$1" ]; then
echo 'Please specify a space to update (i.e. lmm)' >&2
exit 1
fi
echo "You need access to the Login partner dashboard, otherwise you will not be able to complete the steps in this script (https://dashboard.int.identitysandbox.gov/service_providers/2640)"
read -p " Do you have access to the partner dashboard mentioned above? (y/n) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
exit 1
fi
if [ ! $(command -v jq) ] || [ ! $(command -v cf) ]; then
echo "jq, and cf packages must be installed. Please install via your preferred manager."
exit 1
fi
cf target -o cisa-dotgov
read -p "Are you logged in to the cisa-dotgov CF org above? (y/n) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]
then
cf login -a https://api.fr.cloud.gov --sso
fi
echo "Targeting space"
cf target -o cisa-dotgov -s $1
echo "Creating new login.gov credentials for $1..."
django_key=$(python3 -c 'from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())')
openssl req -noenc -x509 -days 365 -newkey rsa:2048 -keyout private-$1.pem -out public-$1.crt
login_key=$(base64 -i private-$1.pem)
echo "Creating the final json"
cf env getgov-$1 | awk '/VCAP_SERVICES: /,/^$/' | sed s/VCAP_SERVICES:// | jq '."user-provided"[0].credentials' | jq --arg django_key "$django_key" --arg login_key "$login_key" '. + {"DJANGO_SECRET_KEY":$django_key, "DJANGO_SECRET_LOGIN_KEY":$login_key}' > credentials-$1.json
echo "Updating creds on the sandbox"
cf uups getgov-credentials -p credentials-$1.json
cf restage getgov-$1 --strategy rolling
echo "\n\n\nNow you will need to update some things for Login. Please sign-in to https://dashboard.int.identitysandbox.gov/."
echo "Navigate to our application config: https://dashboard.int.identitysandbox.gov/service_providers/2640/edit?"
echo "There are two things to update."
echo "1. Remove the old cert associated with the user's email (under Public Certificates)"
echo "2. You need to upload the public-$1.crt file generated as part of the previous command. See the "choose cert file" button under Public Certificates."
echo "Then, tell the developer to update their local .env file by retrieving their credentials from the sandbox"

View file

@ -7,6 +7,7 @@
"http://localhost:8080/",
"http://localhost:8080/health/",
"http://localhost:8080/request/",
"http://localhost:8080/request/start",
"http://localhost:8080/request/organization/",
"http://localhost:8080/request/org_federal/",
"http://localhost:8080/request/org_election/",

View file

@ -1640,6 +1640,70 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
def lookups(self, request, model_admin):
return DomainRequest.DomainRequestStatus.choices
class GenericOrgFilter(admin.SimpleListFilter):
"""Custom Generic Organization filter that accomodates portfolio feature.
If we have a portfolio, use the portfolio's organization. If not, use the
organization in the Domain Request object."""
title = "generic organization"
parameter_name = "converted_generic_orgs"
def lookups(self, request, model_admin):
converted_generic_orgs = set()
for domain_request in DomainRequest.objects.all():
converted_generic_org = domain_request.converted_generic_org_type
if converted_generic_org:
converted_generic_orgs.add(converted_generic_org)
return sorted((org, org) for org in converted_generic_orgs)
# Filter queryset
def queryset(self, request, queryset):
if self.value(): # Check if a generic org is selected in the filter
return queryset.filter(
# Filter based on the generic org value returned by converted_generic_org_type
id__in=[
domain_request.id
for domain_request in queryset
if domain_request.converted_generic_org_type
and domain_request.converted_generic_org_type == self.value()
]
)
return queryset
class FederalTypeFilter(admin.SimpleListFilter):
"""Custom Federal Type filter that accomodates portfolio feature.
If we have a portfolio, use the portfolio's federal type. If not, use the
organization in the Domain Request object."""
title = "federal Type"
parameter_name = "converted_federal_types"
def lookups(self, request, model_admin):
converted_federal_types = set()
for domain_request in DomainRequest.objects.all():
converted_federal_type = domain_request.converted_federal_type
if converted_federal_type:
converted_federal_types.add(converted_federal_type)
return sorted((type, type) for type in converted_federal_types)
# Filter queryset
def queryset(self, request, queryset):
if self.value(): # Check if federal Type is selected in the filter
return queryset.filter(
# Filter based on the federal type returned by converted_federal_type
id__in=[
domain_request.id
for domain_request in queryset
if domain_request.converted_federal_type
and domain_request.converted_federal_type == self.value()
]
)
return queryset
class InvestigatorFilter(admin.SimpleListFilter):
"""Custom investigator filter that only displays users with the manager role"""
@ -1700,6 +1764,30 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
if self.value() == "0":
return queryset.filter(Q(is_election_board=False) | Q(is_election_board=None))
@admin.display(description=_("Generic Org Type"))
def converted_generic_org_type(self, obj):
return obj.converted_generic_org_type
@admin.display(description=_("Organization Name"))
def converted_organization_name(self, obj):
return obj.converted_organization_name
@admin.display(description=_("Federal Agency"))
def converted_federal_agency(self, obj):
return obj.converted_federal_agency
@admin.display(description=_("Federal Type"))
def converted_federal_type(self, obj):
return obj.converted_federal_type
@admin.display(description=_("City"))
def converted_city(self, obj):
return obj.converted_city
@admin.display(description=_("State/Territory"))
def converted_state_territory(self, obj):
return obj.converted_state_territory
# Columns
list_display = [
"requested_domain",
@ -1707,13 +1795,13 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
"last_submitted_date",
"last_status_update",
"status",
"generic_org_type",
"federal_type",
"federal_agency",
"organization_name",
"custom_election_board",
"city",
"state_territory",
"converted_generic_org_type",
"converted_organization_name",
"converted_federal_agency",
"converted_federal_type",
"converted_city",
"converted_state_territory",
"investigator",
]
@ -1738,8 +1826,8 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
# Filters
list_filter = (
StatusListFilter,
"generic_org_type",
"federal_type",
GenericOrgFilter,
FederalTypeFilter,
ElectionOfficeFilter,
"rejection_reason",
InvestigatorFilter,
@ -1869,15 +1957,16 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
"suborganization_city",
"suborganization_state_territory",
]
autocomplete_fields = [
"approved_domain",
"requested_domain",
"creator",
"senior_official",
"investigator",
"portfolio",
"sub_organization",
]
filter_horizontal = ("current_websites", "alternative_domains", "other_contacts")
# Table ordering

View file

@ -39,12 +39,13 @@ from registrar.views.utility import always_404
from api.views import available, rdap, get_current_federal, get_current_full
DOMAIN_REQUEST_NAMESPACE = views.DomainRequestWizard.URL_NAMESPACE
domain_request_urls = [
path("", views.DomainRequestWizard.as_view(), name=""),
path("finished/", views.Finished.as_view(), name="finished"),
]
# dynamically generate the other domain_request_urls
domain_request_urls = [
path("", RedirectView.as_view(pattern_name="domain-request:start"), name="redirect-to-start"),
path("start/", views.DomainRequestWizard.as_view(), name="start"),
path("finished/", views.Finished.as_view(), name="finished"),
]
for step, view in [
# add/remove steps here
(Step.ORGANIZATION_TYPE, views.OrganizationType),
@ -65,7 +66,7 @@ for step, view in [
(PortfolioDomainRequestStep.REQUESTING_ENTITY, views.RequestingEntity),
(PortfolioDomainRequestStep.ADDITIONAL_DETAILS, views.PortfolioAdditionalDetails),
]:
domain_request_urls.append(path(f"{step}/", view.as_view(), name=step))
domain_request_urls.append(path(f"<int:id>/{step}/", view.as_view(), name=step))
urlpatterns = [

View file

@ -0,0 +1,90 @@
# Generated by Django 4.2.10 on 2024-11-12 22:02
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("registrar", "0136_domainrequest_requested_suborganization_and_more"),
]
operations = [
migrations.AddField(
model_name="suborganization",
name="city",
field=models.CharField(blank=True, null=True),
),
migrations.AddField(
model_name="suborganization",
name="state_territory",
field=models.CharField(
blank=True,
choices=[
("AL", "Alabama (AL)"),
("AK", "Alaska (AK)"),
("AS", "American Samoa (AS)"),
("AZ", "Arizona (AZ)"),
("AR", "Arkansas (AR)"),
("CA", "California (CA)"),
("CO", "Colorado (CO)"),
("CT", "Connecticut (CT)"),
("DE", "Delaware (DE)"),
("DC", "District of Columbia (DC)"),
("FL", "Florida (FL)"),
("GA", "Georgia (GA)"),
("GU", "Guam (GU)"),
("HI", "Hawaii (HI)"),
("ID", "Idaho (ID)"),
("IL", "Illinois (IL)"),
("IN", "Indiana (IN)"),
("IA", "Iowa (IA)"),
("KS", "Kansas (KS)"),
("KY", "Kentucky (KY)"),
("LA", "Louisiana (LA)"),
("ME", "Maine (ME)"),
("MD", "Maryland (MD)"),
("MA", "Massachusetts (MA)"),
("MI", "Michigan (MI)"),
("MN", "Minnesota (MN)"),
("MS", "Mississippi (MS)"),
("MO", "Missouri (MO)"),
("MT", "Montana (MT)"),
("NE", "Nebraska (NE)"),
("NV", "Nevada (NV)"),
("NH", "New Hampshire (NH)"),
("NJ", "New Jersey (NJ)"),
("NM", "New Mexico (NM)"),
("NY", "New York (NY)"),
("NC", "North Carolina (NC)"),
("ND", "North Dakota (ND)"),
("MP", "Northern Mariana Islands (MP)"),
("OH", "Ohio (OH)"),
("OK", "Oklahoma (OK)"),
("OR", "Oregon (OR)"),
("PA", "Pennsylvania (PA)"),
("PR", "Puerto Rico (PR)"),
("RI", "Rhode Island (RI)"),
("SC", "South Carolina (SC)"),
("SD", "South Dakota (SD)"),
("TN", "Tennessee (TN)"),
("TX", "Texas (TX)"),
("UM", "United States Minor Outlying Islands (UM)"),
("UT", "Utah (UT)"),
("VT", "Vermont (VT)"),
("VI", "Virgin Islands (VI)"),
("VA", "Virginia (VA)"),
("WA", "Washington (WA)"),
("WV", "West Virginia (WV)"),
("WI", "Wisconsin (WI)"),
("WY", "Wyoming (WY)"),
("AA", "Armed Forces Americas (AA)"),
("AE", "Armed Forces Africa, Canada, Europe, Middle East (AE)"),
("AP", "Armed Forces Pacific (AP)"),
],
max_length=2,
null=True,
verbose_name="state, territory, or military post",
),
),
]

View file

@ -1409,3 +1409,48 @@ class DomainRequest(TimeStampedModel):
if not is_complete or not self._is_general_form_complete(request):
return False
return True
"""The following converted_ property methods get field data from this domain request's portfolio,
if there is an associated portfolio. If not, they return data from the domain request model."""
@property
def converted_organization_name(self):
if self.portfolio:
return self.portfolio.organization_name
return self.organization_name
@property
def converted_generic_org_type(self):
if self.portfolio:
return self.portfolio.organization_type
return self.generic_org_type
@property
def converted_federal_agency(self):
if self.portfolio:
return self.portfolio.federal_agency
return self.federal_agency
@property
def converted_federal_type(self):
if self.portfolio:
return self.portfolio.federal_type
return self.federal_type
@property
def converted_city(self):
if self.portfolio:
return self.portfolio.city
return self.city
@property
def converted_state_territory(self):
if self.portfolio:
return self.portfolio.state_territory
return self.state_territory
@property
def converted_senior_official(self):
if self.portfolio:
return self.portfolio.senior_official
return self.senior_official

View file

@ -1,4 +1,6 @@
from django.db import models
from registrar.models.domain_request import DomainRequest
from .utility.time_stamped_model import TimeStampedModel
@ -19,5 +21,18 @@ class Suborganization(TimeStampedModel):
related_name="portfolio_suborganizations",
)
city = models.CharField(
null=True,
blank=True,
)
state_territory = models.CharField(
max_length=2,
choices=DomainRequest.StateTerritoryChoices.choices,
null=True,
blank=True,
verbose_name="state, territory, or military post",
)
def __str__(self) -> str:
return f"{self.name}"

View file

@ -92,7 +92,7 @@ class CheckUserProfileMiddleware:
We set the "redirect" query param equal to where the user wants to go.
If the user wants to go to '/request/', then we set that
If the user wants to go to '/request/start/' or '/request/', then we set that
information in the query param.
Otherwise, we assume they want to go to the home page.
@ -100,7 +100,8 @@ class CheckUserProfileMiddleware:
# In some cases, we don't want to redirect to home. This handles that.
# Can easily be generalized if need be, but for now lets keep this easy to read.
custom_redirect = "domain-request:" if request.path == "/request/" else None
start_paths = ["/request/", "/request/start/"]
custom_redirect = "domain-request:start" if request.path in start_paths else None
# Don't redirect on excluded pages (such as the setup page itself)
if not any(request.path.startswith(page) for page in self._get_excluded_pages(profile_page)):

View file

@ -10,32 +10,37 @@
</div>
<div class="tablet:grid-col-9">
<main id="main-content" class="grid-container register-form-step">
{% if steps.prev %}
<a href="{% namespaced_url 'domain-request' steps.prev %}" class="breadcrumb__back">
<input type="hidden" class="display-none" id="wizard-domain-request-id" value="{{domain_request_id}}"/>
{% if steps.current == steps.first %}
{% if portfolio %}
{% url 'domain-requests' as url_2 %}
{% else %}
{% url 'home' as url_2 %}
{% endif %}
<nav class="usa-breadcrumb padding-top-0" aria-label="Domain request breadcrumb">
<ol class="usa-breadcrumb__list">
<li class="usa-breadcrumb__list-item">
<a href="{{ url_2 }}" class="usa-breadcrumb__link">
<span>
{% if portfolio%}Domain requests{%else%}Manage your domains{% endif%}
</span>
</a>
</li>
<li class="usa-breadcrumb__list-item usa-current" aria-current="page">
{% if requested_domain__name %}
<span>{{ requested_domain__name }}</span>
{% else %}
<span>New domain request</span>
{% endif %}
</li>
</ol>
</nav>
{% elif steps.prev %}
<a href="{% namespaced_url 'domain-request' steps.prev id=domain_request_id %}" class="breadcrumb__back">
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24" height="24">
<use xlink:href="{%static 'img/sprite.svg'%}#arrow_back"></use>
</svg><span class="margin-left-05">Previous step</span>
</a>
{% comment %}
TODO: uncomment in #2596
{% else %}
{% if portfolio %}
{% url 'domain-requests' as url_2 %}
<nav class="usa-breadcrumb padding-top-0" aria-label="Domain request breadcrumb">
<ol class="usa-breadcrumb__list">
<li class="usa-breadcrumb__list-item">
<a href="{{ url_2 }}" class="usa-breadcrumb__link"><span>Domain requests</span></a>
</li>
<li class="usa-breadcrumb__list-item usa-current" aria-current="page">
{% if requested_domain__name %}
<span>{{ requested_domain__name }}</span>
{% else %}
<span>Start a new domain request</span>
{% endif %}
</li>
</ol>
</nav>
{% endif %} {% endcomment %}
{% endif %}
{% block form_messages %}

View file

@ -21,7 +21,7 @@
<p>If you have <a href="{% public_site_url 'domains/before/#information-you%E2%80%99ll-need-to-complete-the-domain-request-form' %}" target="_blank" class="usa-link">all the information you need</a>,
completing your domain request might take around 15 minutes.</p>
<h2>How well reach you</h2>
<p>While reviewing your domain request, we may need to reach out with questions. Well also email you when we complete our review. If the contact information below is not correct, visit <a href="{% url 'user-profile' %}?redirect=domain-request:" class="usa-link">your profile</a> to make updates.</p>
<p>While reviewing your domain request, we may need to reach out with questions. Well also email you when we complete our review. If the contact information below is not correct, visit <a href="{% url 'user-profile' %}?redirect=domain-request:start" class="usa-link">your profile</a> to make updates.</p>
{% include "includes/profile_information.html" with user=user%}

View file

@ -15,7 +15,7 @@
</svg>
{% endif %}
{% endif %}
<a href="{% namespaced_url 'domain-request' this_step %}"
<a href="{% namespaced_url 'domain-request' this_step id=domain_request_id %}"
{% if this_step == steps.current %}
class="usa-current"
{% else %}

View file

@ -17,13 +17,8 @@
<h1>Manage your domains</h1>
{% comment %}
IMPORTANT:
If this button is added on any other page, make sure to update the
relevant view to reset request.session["new_request"] = True
{% endcomment %}
<p class="margin-top-4">
<a href="{% url 'domain-request:' %}" class="usa-button"
<a href="{% url 'domain-request:start' %}" class="usa-button"
>
Start a new domain request
</a>

View file

@ -72,7 +72,7 @@
>
</li>
<li class="usa-nav__submenu-item">
<a href="{% url 'domain-request:' %}"
<a href="{% url 'domain-request:start' %}"
><span>Start a new domain request</span></a
>
</li>

View file

@ -4,7 +4,7 @@
{% for step in steps %}
<section class="summary-item margin-top-3">
{% if is_editable %}
{% namespaced_url 'domain-request' step as domain_request_url %}
{% namespaced_url 'domain-request' step id=domain_request_id as domain_request_url %}
{% endif %}
{% if step == Step.REQUESTING_ENTITY %}

View file

@ -4,7 +4,7 @@
{% for step in steps %}
<section class="summary-item margin-top-3">
{% if is_editable %}
{% namespaced_url 'domain-request' step as domain_request_url %}
{% namespaced_url 'domain-request' step id=domain_request_id as domain_request_url %}
{% endif %}
{% if step == Step.ORGANIZATION_TYPE %}

View file

@ -22,13 +22,9 @@
<p class="margin-y-0">Domain requests can only be modified by the person who created the request.</p>
</div>
<div class="mobile:grid-col-12 tablet:grid-col-6">
{% comment %}
IMPORTANT:
If this button is added on any other page, make sure to update the
relevant view to reset request.session["new_request"] = True
{% endcomment %}
<p class="float-right-tablet tablet:margin-y-0">
<a href="{% url 'domain-request:' %}" class="usa-button"
<a href="{% url 'domain-request:start' %}" class="usa-button"
>
Start a new domain request
</a>

View file

@ -576,9 +576,9 @@ class TestDomainRequestAdmin(MockEppLib):
response = self.client.get("/admin/registrar/domainrequest/?generic_org_type__exact=federal")
# There are 2 template references to Federal (4) and two in the results data
# of the request
self.assertContains(response, "Federal", count=52)
self.assertContains(response, "Federal", count=51)
# 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-converted_generic_org_type">federal</td>', count=1)
# Now let's make sure the long description does not exist
self.assertNotContains(response, "Federal: an agency of the U.S. government")
@ -1935,8 +1935,8 @@ class TestDomainRequestAdmin(MockEppLib):
readonly_fields = self.admin.get_list_filter(request)
expected_fields = (
DomainRequestAdmin.StatusListFilter,
"generic_org_type",
"federal_type",
DomainRequestAdmin.GenericOrgFilter,
DomainRequestAdmin.FederalTypeFilter,
DomainRequestAdmin.ElectionOfficeFilter,
"rejection_reason",
DomainRequestAdmin.InvestigatorFilter,

View file

@ -14,6 +14,7 @@ from registrar.models import (
DraftDomain,
FederalAgency,
AllowedEmail,
Portfolio,
)
import boto3_mocking
@ -95,6 +96,7 @@ class TestDomainRequest(TestCase):
DomainRequest.objects.all().delete()
DraftDomain.objects.all().delete()
Domain.objects.all().delete()
Portfolio.objects.all().delete()
User.objects.all().delete()
self.mock_client.EMAILS_SENT.clear()
@ -1045,3 +1047,26 @@ class TestDomainRequest(TestCase):
status=DomainRequest.DomainRequestStatus.STARTED, name="no-others.gov", has_other_contacts=False
)
self.assertEquals(domain_request.has_other_contacts(), False)
@less_console_noise_decorator
def test_converted_type(self):
"""test that new property fields works as expected to pull domain req info such as fed agency,
generic org type, and others from portfolio"""
fed_agency = FederalAgency.objects.filter(agency="Non-Federal Agency").first()
portfolio = Portfolio.objects.create(
organization_name="Test Portfolio",
creator=self.dummy_user_2,
federal_agency=fed_agency,
organization_type=DomainRequest.OrganizationChoices.FEDERAL,
)
domain_request = completed_domain_request(name="domainre1.gov", portfolio=portfolio)
self.assertEqual(portfolio.organization_type, domain_request.converted_generic_org_type)
self.assertEqual(portfolio.federal_agency, domain_request.converted_federal_agency)
domain_request2 = completed_domain_request(
name="domainreq2.gov", federal_agency=fed_agency, generic_org_type=DomainRequest.OrganizationChoices.TRIBAL
)
self.assertEqual(domain_request2.generic_org_type, domain_request2.converted_generic_org_type)
self.assertEqual(domain_request2.federal_agency, domain_request2.converted_federal_agency)

View file

@ -701,6 +701,7 @@ class ExportDataTest(MockDbForIndividualTests, MockEppLib):
csv_file.seek(0)
# Read the content into a variable
csv_content = csv_file.read()
expected_content = (
"Domain request,Domain type,Federal type\n"
"city3.gov,Federal,Executive\n"

View file

@ -49,9 +49,9 @@ class TestViews(TestCase):
@less_console_noise_decorator
def test_domain_request_form_not_logged_in(self):
"""Domain request form not accessible without a logged-in user."""
response = self.client.get("/request/")
response = self.client.get(reverse("domain-request:start"))
self.assertEqual(response.status_code, 302)
self.assertIn("/login?next=/request/", response.headers["Location"])
self.assertIn("/login?next=/request/start/", response.headers["Location"])
class TestWithUser(MockEppLib):
@ -476,7 +476,7 @@ class HomeTests(TestWithUser):
@less_console_noise_decorator
def test_domain_request_form_view(self):
response = self.client.get("/request/", follow=True)
response = self.client.get(reverse("domain-request:start"), follow=True)
self.assertContains(
response,
"Youre about to start your .gov domain request.",
@ -503,7 +503,7 @@ class HomeTests(TestWithUser):
title="title",
)
self.client.force_login(restricted_user)
response = self.client.get("/request/", follow=True)
response = self.client.get(reverse("domain-request:start"), follow=True)
self.assertEqual(response.status_code, 403)
restricted_user.delete()
@ -718,7 +718,7 @@ class FinishUserProfileTests(TestWithUser, WebTest):
self.app.set_user(incomplete_regular_user.username)
with override_flag("", active=True):
# This will redirect the user to the setup page
finish_setup_page = self.app.get(reverse("domain-request:")).follow()
finish_setup_page = self.app.get(reverse("domain-request:start")).follow()
self._set_session_cookie()
# Assert that we're on the right page
@ -914,7 +914,7 @@ class UserProfileTests(TestWithUser, WebTest):
@less_console_noise_decorator
def test_new_request_main_nav(self):
"""test that Your profile is in main nav of new request"""
response = self.client.get("/request/", follow=True)
response = self.client.get(reverse("domain-request:start"), follow=True)
self.assertContains(response, "Your profile")
@less_console_noise_decorator
@ -927,7 +927,7 @@ class UserProfileTests(TestWithUser, WebTest):
def test_user_profile_back_button_when_coming_from_domain_request(self):
"""tests user profile,
and when they are redirected from the domain request page"""
response = self.client.get("/user-profile?redirect=domain-request:")
response = self.client.get("/user-profile?redirect=domain-request:start")
self.assertContains(response, "Your profile")
self.assertContains(response, "Go back to your domain request")
self.assertNotContains(response, "Back to manage your domains")

View file

@ -1645,7 +1645,7 @@ class TestRequestingEntity(WebTest):
def test_requesting_entity_page_new_request(self):
"""Tests that the requesting entity page loads correctly when a new request is started"""
response = self.app.get(reverse("domain-request:"))
response = self.app.get(reverse("domain-request:start"))
# Navigate past the intro page
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
@ -1672,7 +1672,7 @@ class TestRequestingEntity(WebTest):
@less_console_noise_decorator
def test_requesting_entity_page_existing_suborg_submission(self):
"""Tests that you can submit a form on this page and set a suborg"""
response = self.app.get(reverse("domain-request:"))
response = self.app.get(reverse("domain-request:start"))
# Navigate past the intro page
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
@ -1705,7 +1705,7 @@ class TestRequestingEntity(WebTest):
@less_console_noise_decorator
def test_requesting_entity_page_new_suborg_submission(self):
"""Tests that you can submit a form on this page and set a new suborg"""
response = self.app.get(reverse("domain-request:"))
response = self.app.get(reverse("domain-request:start"))
# Navigate past the intro page
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
@ -1745,7 +1745,7 @@ class TestRequestingEntity(WebTest):
@less_console_noise_decorator
def test_requesting_entity_page_organization_submission(self):
"""Tests submitting an organization on the requesting org form"""
response = self.app.get(reverse("domain-request:"))
response = self.app.get(reverse("domain-request:start"))
# Navigate past the intro page
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]

View file

@ -49,12 +49,13 @@ class DomainRequestTests(TestWithUser, WebTest):
super().tearDown()
DomainRequest.objects.all().delete()
DomainInformation.objects.all().delete()
User.objects.all().delete()
self.federal_agency.delete()
@less_console_noise_decorator
def test_domain_request_form_intro_acknowledgement(self):
"""Tests that user is presented with intro acknowledgement page"""
intro_page = self.app.get(reverse("domain-request:"))
intro_page = self.app.get(reverse("domain-request:start"))
self.assertContains(intro_page, "Youre about to start your .gov domain request")
@less_console_noise_decorator
@ -105,12 +106,12 @@ class DomainRequestTests(TestWithUser, WebTest):
self.assertEqual(detail_page.status_code, 302)
# You can access the 'Location' header to get the redirect URL
redirect_url = detail_page.url
self.assertEqual(redirect_url, "/request/generic_org_type/")
self.assertEqual(redirect_url, f"/request/{domain_request.id}/generic_org_type/")
@less_console_noise_decorator
def test_domain_request_form_empty_submit(self):
"""Tests empty submit on the first page after the acknowledgement page"""
intro_page = self.app.get(reverse("domain-request:"))
intro_page = self.app.get(reverse("domain-request:start"))
# django-webtest does not handle cookie-based sessions well because it keeps
# resetting the session key on each new request, thus destroying the concept
# of a "session". We are going to do it manually, saving the session ID here
@ -141,7 +142,7 @@ class DomainRequestTests(TestWithUser, WebTest):
domain_request.save()
# now, attempt to create another one
intro_page = self.app.get(reverse("domain-request:"))
intro_page = self.app.get(reverse("domain-request:start"))
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
intro_form = intro_page.forms[0]
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
@ -154,59 +155,6 @@ class DomainRequestTests(TestWithUser, WebTest):
self.assertContains(type_page, "You cannot submit this request yet")
@less_console_noise_decorator
def test_domain_request_into_acknowledgement_creates_new_request(self):
"""
We had to solve a bug where the wizard was creating 2 requests on first intro acknowledgement ('continue')
The wizard was also creating multiiple requests on 'continue' -> back button -> 'continue' etc.
This tests that the domain requests get created only when they should.
"""
# Get the intro page
self.app.get(reverse("home"))
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
intro_page = self.app.get(reverse("domain-request:"))
# Select the form
intro_form = intro_page.forms[0]
# Submit the form, this creates 1 Request
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
response = intro_form.submit(name="submit_button", value="intro_acknowledge")
# Landing on the next page used to create another 1 request
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
response.follow()
# Check if a new DomainRequest object has been created
domain_request_count = DomainRequest.objects.count()
self.assertEqual(domain_request_count, 1)
# Let's go back to intro and submit again, this should not create a new request
# This is the equivalent of a back button nav from step 1 to intro -> continue
intro_form = intro_page.forms[0]
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
type_form = intro_form.submit(name="submit_button", value="intro_acknowledge")
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
type_form.follow()
domain_request_count = DomainRequest.objects.count()
self.assertEqual(domain_request_count, 1)
# Go home, which will reset the session flag for new request
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
self.app.get(reverse("home"))
# This time, clicking continue will create a new request
intro_form = intro_page.forms[0]
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
intro_result = intro_form.submit(name="submit_button", value="intro_acknowledge")
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
intro_result.follow()
domain_request_count = DomainRequest.objects.count()
self.assertEqual(domain_request_count, 2)
@boto3_mocking.patching
@less_console_noise_decorator
def test_domain_request_form_submission(self):
@ -225,7 +173,7 @@ class DomainRequestTests(TestWithUser, WebTest):
SKIPPED_PAGES = 3
num_pages = len(self.TITLES) - SKIPPED_PAGES
intro_page = self.app.get(reverse("domain-request:"))
intro_page = self.app.get(reverse("domain-request:start"))
# django-webtest does not handle cookie-based sessions well because it keeps
# resetting the session key on each new request, thus destroying the concept
# of a "session". We are going to do it manually, saving the session ID here
@ -253,7 +201,7 @@ class DomainRequestTests(TestWithUser, WebTest):
# the post request should return a redirect to the next form in
# the domain request page
self.assertEqual(type_result.status_code, 302)
self.assertEqual(type_result["Location"], "/request/organization_federal/")
self.assertEqual(type_result["Location"], f"/request/{domain_request.id}/organization_federal/")
num_pages_tested += 1
# ---- FEDERAL BRANCH PAGE ----
@ -273,7 +221,7 @@ class DomainRequestTests(TestWithUser, WebTest):
# the post request should return a redirect to the next form in
# the domain request page
self.assertEqual(federal_result.status_code, 302)
self.assertEqual(federal_result["Location"], "/request/organization_contact/")
self.assertEqual(federal_result["Location"], f"/request/{domain_request.id}/organization_contact/")
num_pages_tested += 1
# ---- ORG CONTACT PAGE ----
@ -305,7 +253,7 @@ class DomainRequestTests(TestWithUser, WebTest):
# the post request should return a redirect to the next form in
# the domain request page
self.assertEqual(org_contact_result.status_code, 302)
self.assertEqual(org_contact_result["Location"], "/request/senior_official/")
self.assertEqual(org_contact_result["Location"], f"/request/{domain_request.id}/senior_official/")
num_pages_tested += 1
# ---- SENIOR OFFICIAL PAGE ----
@ -330,7 +278,7 @@ class DomainRequestTests(TestWithUser, WebTest):
# the post request should return a redirect to the next form in
# the domain request page
self.assertEqual(so_result.status_code, 302)
self.assertEqual(so_result["Location"], "/request/current_sites/")
self.assertEqual(so_result["Location"], f"/request/{domain_request.id}/current_sites/")
num_pages_tested += 1
# ---- CURRENT SITES PAGE ----
@ -352,7 +300,7 @@ class DomainRequestTests(TestWithUser, WebTest):
# the post request should return a redirect to the next form in
# the domain request page
self.assertEqual(current_sites_result.status_code, 302)
self.assertEqual(current_sites_result["Location"], "/request/dotgov_domain/")
self.assertEqual(current_sites_result["Location"], f"/request/{domain_request.id}/dotgov_domain/")
num_pages_tested += 1
# ---- DOTGOV DOMAIN PAGE ----
@ -372,7 +320,7 @@ class DomainRequestTests(TestWithUser, WebTest):
# the post request should return a redirect to the next form in
# the domain request page
self.assertEqual(dotgov_result.status_code, 302)
self.assertEqual(dotgov_result["Location"], "/request/purpose/")
self.assertEqual(dotgov_result["Location"], f"/request/{domain_request.id}/purpose/")
num_pages_tested += 1
# ---- PURPOSE PAGE ----
@ -391,7 +339,7 @@ class DomainRequestTests(TestWithUser, WebTest):
# the post request should return a redirect to the next form in
# the domain request page
self.assertEqual(purpose_result.status_code, 302)
self.assertEqual(purpose_result["Location"], "/request/other_contacts/")
self.assertEqual(purpose_result["Location"], f"/request/{domain_request.id}/other_contacts/")
num_pages_tested += 1
# ---- OTHER CONTACTS PAGE ----
@ -429,7 +377,7 @@ class DomainRequestTests(TestWithUser, WebTest):
# the post request should return a redirect to the next form in
# the domain request page
self.assertEqual(other_contacts_result.status_code, 302)
self.assertEqual(other_contacts_result["Location"], "/request/additional_details/")
self.assertEqual(other_contacts_result["Location"], f"/request/{domain_request.id}/additional_details/")
num_pages_tested += 1
# ---- ADDITIONAL DETAILS PAGE ----
@ -459,7 +407,7 @@ class DomainRequestTests(TestWithUser, WebTest):
# the post request should return a redirect to the next form in
# the domain request page
self.assertEqual(additional_details_result.status_code, 302)
self.assertEqual(additional_details_result["Location"], "/request/requirements/")
self.assertEqual(additional_details_result["Location"], f"/request/{domain_request.id}/requirements/")
num_pages_tested += 1
# ---- REQUIREMENTS PAGE ----
@ -479,7 +427,7 @@ class DomainRequestTests(TestWithUser, WebTest):
# the post request should return a redirect to the next form in
# the domain request page
self.assertEqual(requirements_result.status_code, 302)
self.assertEqual(requirements_result["Location"], "/request/review/")
self.assertEqual(requirements_result["Location"], f"/request/{domain_request.id}/review/")
num_pages_tested += 1
# ---- REVIEW AND FINSIHED PAGES ----
@ -549,7 +497,7 @@ class DomainRequestTests(TestWithUser, WebTest):
num_pages_tested = 0
# skipping elections, type_of_work, tribal_government
intro_page = self.app.get(reverse("domain-request:"))
intro_page = self.app.get(reverse("domain-request:start"))
# django-webtest does not handle cookie-based sessions well because it keeps
# resetting the session key on each new request, thus destroying the concept
# of a "session". We are going to do it manually, saving the session ID here
@ -577,7 +525,7 @@ class DomainRequestTests(TestWithUser, WebTest):
# the post request should return a redirect to the next form in
# the domain request page
self.assertEqual(type_result.status_code, 302)
self.assertEqual(type_result["Location"], "/request/organization_federal/")
self.assertEqual(type_result["Location"], f"/request/{domain_request.id}/organization_federal/")
num_pages_tested += 1
# ---- FEDERAL BRANCH PAGE ----
@ -597,7 +545,7 @@ class DomainRequestTests(TestWithUser, WebTest):
# the post request should return a redirect to the next form in
# the domain request page
self.assertEqual(federal_result.status_code, 302)
self.assertEqual(federal_result["Location"], "/request/organization_contact/")
self.assertEqual(federal_result["Location"], f"/request/{domain_request.id}/organization_contact/")
num_pages_tested += 1
# ---- ORG CONTACT PAGE ----
@ -629,7 +577,7 @@ class DomainRequestTests(TestWithUser, WebTest):
# the post request should return a redirect to the next form in
# the domain request page
self.assertEqual(org_contact_result.status_code, 302)
self.assertEqual(org_contact_result["Location"], "/request/senior_official/")
self.assertEqual(org_contact_result["Location"], f"/request/{domain_request.id}/senior_official/")
num_pages_tested += 1
# ---- SENIOR OFFICIAL PAGE ----
@ -654,7 +602,7 @@ class DomainRequestTests(TestWithUser, WebTest):
# the post request should return a redirect to the next form in
# the domain request page
self.assertEqual(so_result.status_code, 302)
self.assertEqual(so_result["Location"], "/request/current_sites/")
self.assertEqual(so_result["Location"], f"/request/{domain_request.id}/current_sites/")
num_pages_tested += 1
# ---- CURRENT SITES PAGE ----
@ -676,7 +624,7 @@ class DomainRequestTests(TestWithUser, WebTest):
# the post request should return a redirect to the next form in
# the domain request page
self.assertEqual(current_sites_result.status_code, 302)
self.assertEqual(current_sites_result["Location"], "/request/dotgov_domain/")
self.assertEqual(current_sites_result["Location"], f"/request/{domain_request.id}/dotgov_domain/")
num_pages_tested += 1
# ---- DOTGOV DOMAIN PAGE ----
@ -696,7 +644,7 @@ class DomainRequestTests(TestWithUser, WebTest):
# the post request should return a redirect to the next form in
# the domain request page
self.assertEqual(dotgov_result.status_code, 302)
self.assertEqual(dotgov_result["Location"], "/request/purpose/")
self.assertEqual(dotgov_result["Location"], f"/request/{domain_request.id}/purpose/")
num_pages_tested += 1
# ---- PURPOSE PAGE ----
@ -715,7 +663,7 @@ class DomainRequestTests(TestWithUser, WebTest):
# the post request should return a redirect to the next form in
# the domain request page
self.assertEqual(purpose_result.status_code, 302)
self.assertEqual(purpose_result["Location"], "/request/other_contacts/")
self.assertEqual(purpose_result["Location"], f"/request/{domain_request.id}/other_contacts/")
num_pages_tested += 1
# ---- OTHER CONTACTS PAGE ----
@ -753,7 +701,7 @@ class DomainRequestTests(TestWithUser, WebTest):
# the post request should return a redirect to the next form in
# the domain request page
self.assertEqual(other_contacts_result.status_code, 302)
self.assertEqual(other_contacts_result["Location"], "/request/additional_details/")
self.assertEqual(other_contacts_result["Location"], f"/request/{domain_request.id}/additional_details/")
num_pages_tested += 1
# ---- ADDITIONAL DETAILS PAGE ----
@ -783,7 +731,7 @@ class DomainRequestTests(TestWithUser, WebTest):
# the post request should return a redirect to the next form in
# the domain request page
self.assertEqual(additional_details_result.status_code, 302)
self.assertEqual(additional_details_result["Location"], "/request/requirements/")
self.assertEqual(additional_details_result["Location"], f"/request/{domain_request.id}/requirements/")
num_pages_tested += 1
# ---- REQUIREMENTS PAGE ----
@ -811,7 +759,7 @@ class DomainRequestTests(TestWithUser, WebTest):
# the post request should return a redirect to the next form in
# the domain request page
self.assertEqual(requirements_result.status_code, 302)
self.assertEqual(requirements_result["Location"], "/request/review/")
self.assertEqual(requirements_result["Location"], f"/request/{domain_request.id}/review/")
num_pages_tested += 1
# ---- REVIEW AND FINSIHED PAGES ----
@ -873,7 +821,7 @@ class DomainRequestTests(TestWithUser, WebTest):
@less_console_noise_decorator
def test_domain_request_form_conditional_federal(self):
"""Federal branch question is shown for federal organizations."""
intro_page = self.app.get(reverse("domain-request:"))
intro_page = self.app.get(reverse("domain-request:start"))
# django-webtest does not handle cookie-based sessions well because it keeps
# resetting the session key on each new request, thus destroying the concept
# of a "session". We are going to do it manually, saving the session ID here
@ -904,7 +852,7 @@ class DomainRequestTests(TestWithUser, WebTest):
# the post request should return a redirect to the federal branch
# question
self.assertEqual(type_result.status_code, 302)
self.assertEqual(type_result["Location"], "/request/organization_federal/")
self.assertIn("organization_federal", type_result["Location"])
# and the step label should appear in the sidebar of the resulting page
# but the step label for the elections page should not appear
@ -921,7 +869,7 @@ class DomainRequestTests(TestWithUser, WebTest):
# the post request should return a redirect to the contact
# question
self.assertEqual(federal_result.status_code, 302)
self.assertEqual(federal_result["Location"], "/request/organization_contact/")
self.assertIn("organization_federal", type_result["Location"])
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
contact_page = federal_result.follow()
self.assertContains(contact_page, "Federal agency")
@ -929,7 +877,7 @@ class DomainRequestTests(TestWithUser, WebTest):
@less_console_noise_decorator
def test_domain_request_form_conditional_elections(self):
"""Election question is shown for other organizations."""
intro_page = self.app.get(reverse("domain-request:"))
intro_page = self.app.get(reverse("domain-request:start"))
# django-webtest does not handle cookie-based sessions well because it keeps
# resetting the session key on each new request, thus destroying the concept
# of a "session". We are going to do it manually, saving the session ID here
@ -959,7 +907,7 @@ class DomainRequestTests(TestWithUser, WebTest):
# the post request should return a redirect to the elections question
self.assertEqual(type_result.status_code, 302)
self.assertEqual(type_result["Location"], "/request/organization_election/")
self.assertIn("organization_election", type_result["Location"])
# and the step label should appear in the sidebar of the resulting page
# but the step label for the elections page should not appear
@ -976,7 +924,7 @@ class DomainRequestTests(TestWithUser, WebTest):
# the post request should return a redirect to the contact
# question
self.assertEqual(election_result.status_code, 302)
self.assertEqual(election_result["Location"], "/request/organization_contact/")
self.assertIn("organization_contact", election_result["Location"])
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
contact_page = election_result.follow()
self.assertNotContains(contact_page, "Federal agency")
@ -984,7 +932,8 @@ class DomainRequestTests(TestWithUser, WebTest):
@less_console_noise_decorator
def test_domain_request_form_section_skipping(self):
"""Can skip forward and back in sections"""
intro_page = self.app.get(reverse("domain-request:"))
DomainRequest.objects.all().delete()
intro_page = self.app.get(reverse("domain-request:start"))
# django-webtest does not handle cookie-based sessions well because it keeps
# resetting the session key on each new request, thus destroying the concept
# of a "session". We are going to do it manually, saving the session ID here
@ -1019,17 +968,20 @@ class DomainRequestTests(TestWithUser, WebTest):
# Now click back to the organization type
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
new_page = federal_page.click(str(self.TITLES["generic_org_type"]), index=0)
# Should be a link to the organization_federal page since it is now unlocked
all_domain_requests = DomainRequest.objects.all()
self.assertEqual(all_domain_requests.count(), 1)
new_request_id = all_domain_requests.first().id
self.assertGreater(
len(new_page.html.find_all("a", href="/request/organization_federal/")),
len(new_page.html.find_all("a", href=f"/request/{new_request_id}/organization_federal/")),
0,
)
@less_console_noise_decorator
def test_domain_request_form_nonfederal(self):
"""Non-federal organizations don't have to provide their federal agency."""
intro_page = self.app.get(reverse("domain-request:"))
intro_page = self.app.get(reverse("domain-request:start"))
# django-webtest does not handle cookie-based sessions well because it keeps
# resetting the session key on each new request, thus destroying the concept
# of a "session". We are going to do it manually, saving the session ID here
@ -1069,12 +1021,12 @@ class DomainRequestTests(TestWithUser, WebTest):
# the post request should return a redirect to the
# about your organization page if it was successful.
self.assertEqual(contact_result.status_code, 302)
self.assertEqual(contact_result["Location"], "/request/about_your_organization/")
self.assertIn("about_your_organization", contact_result["Location"])
@less_console_noise_decorator
def test_domain_request_about_your_organization_special(self):
"""Special districts have to answer an additional question."""
intro_page = self.app.get(reverse("domain-request:"))
intro_page = self.app.get(reverse("domain-request:start"))
# django-webtest does not handle cookie-based sessions well because it keeps
# resetting the session key on each new request, thus destroying the concept
# of a "session". We are going to do it manually, saving the session ID here
@ -1104,7 +1056,7 @@ class DomainRequestTests(TestWithUser, WebTest):
def test_federal_agency_dropdown_excludes_expected_values(self):
"""The Federal Agency dropdown on a domain request form should not
include options for gov Administration and Non-Federal Agency"""
intro_page = self.app.get(reverse("domain-request:"))
intro_page = self.app.get(reverse("domain-request:start"))
# django-webtest does not handle cookie-based sessions well because it keeps
# resetting the session key on each new request, thus destroying the concept
# of a "session". We are going to do it manually, saving the session ID here
@ -1152,7 +1104,7 @@ class DomainRequestTests(TestWithUser, WebTest):
def test_yes_no_contact_form_inits_blank_for_new_domain_request(self):
"""On the Other Contacts page, the yes/no form gets initialized with nothing selected for
new domain requests"""
other_contacts_page = self.app.get(reverse("domain-request:other_contacts"))
other_contacts_page = self.app.get(reverse("domain-request:other_contacts", kwargs={"id": 0}))
other_contacts_form = other_contacts_page.forms[0]
self.assertEquals(other_contacts_form["other_contacts-has_other_contacts"].value, None)
@ -1160,7 +1112,7 @@ class DomainRequestTests(TestWithUser, WebTest):
def test_yes_no_additional_form_inits_blank_for_new_domain_request(self):
"""On the Additional Details page, the yes/no form gets initialized with nothing selected for
new domain requests"""
additional_details_page = self.app.get(reverse("domain-request:additional_details"))
additional_details_page = self.app.get(reverse("domain-request:additional_details", kwargs={"id": 0}))
additional_form = additional_details_page.forms[0]
# Check the cisa representative yes/no field
@ -1184,7 +1136,7 @@ class DomainRequestTests(TestWithUser, WebTest):
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
other_contacts_page = self.app.get(reverse("domain-request:other_contacts"))
other_contacts_page = self.app.get(reverse("domain-request:other_contacts", kwargs={"id": domain_request.pk}))
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
other_contacts_form = other_contacts_page.forms[0]
@ -1209,7 +1161,9 @@ class DomainRequestTests(TestWithUser, WebTest):
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
additional_details_page = self.app.get(reverse("domain-request:additional_details"))
additional_details_page = self.app.get(
reverse("domain-request:additional_details", kwargs={"id": domain_request.pk})
)
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
additional_details_form = additional_details_page.forms[0]
@ -1239,7 +1193,7 @@ class DomainRequestTests(TestWithUser, WebTest):
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
other_contacts_page = self.app.get(reverse("domain-request:other_contacts"))
other_contacts_page = self.app.get(reverse("domain-request:other_contacts", kwargs={"id": domain_request.pk}))
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
other_contacts_form = other_contacts_page.forms[0]
@ -1268,7 +1222,9 @@ class DomainRequestTests(TestWithUser, WebTest):
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
additional_details_page = self.app.get(reverse("domain-request:additional_details"))
additional_details_page = self.app.get(
reverse("domain-request:additional_details", kwargs={"id": domain_request.pk})
)
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
additional_details_form = additional_details_page.forms[0]
@ -1306,7 +1262,9 @@ class DomainRequestTests(TestWithUser, WebTest):
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
additional_details_page = self.app.get(reverse("domain-request:additional_details"))
additional_details_page = self.app.get(
reverse("domain-request:additional_details", kwargs={"id": domain_request.pk})
)
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
additional_details_form = additional_details_page.forms[0]
@ -1368,7 +1326,9 @@ class DomainRequestTests(TestWithUser, WebTest):
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
additional_details_page = self.app.get(reverse("domain-request:additional_details"))
additional_details_page = self.app.get(
reverse("domain-request:additional_details", kwargs={"id": domain_request.pk})
)
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
additional_details_form = additional_details_page.forms[0]
@ -1413,7 +1373,9 @@ class DomainRequestTests(TestWithUser, WebTest):
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
additional_details_page = self.app.get(reverse("domain-request:additional_details"))
additional_details_page = self.app.get(
reverse("domain-request:additional_details", kwargs={"id": domain_request.pk})
)
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
additional_details_form = additional_details_page.forms[0]
@ -1444,7 +1406,9 @@ class DomainRequestTests(TestWithUser, WebTest):
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
additional_details_page = self.app.get(reverse("domain-request:additional_details"))
additional_details_page = self.app.get(
reverse("domain-request:additional_details", kwargs={"id": domain_request.pk})
)
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
additional_details_form = additional_details_page.forms[0]
@ -1481,7 +1445,9 @@ class DomainRequestTests(TestWithUser, WebTest):
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
additional_details_page = self.app.get(reverse("domain-request:additional_details"))
additional_details_page = self.app.get(
reverse("domain-request:additional_details", kwargs={"id": domain_request.id})
)
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
additional_details_form = additional_details_page.forms[0]
@ -1512,7 +1478,7 @@ class DomainRequestTests(TestWithUser, WebTest):
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
other_contacts_page = self.app.get(reverse("domain-request:other_contacts"))
other_contacts_page = self.app.get(reverse("domain-request:other_contacts", kwargs={"id": domain_request.pk}))
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
other_contacts_form = other_contacts_page.forms[0]
@ -1560,7 +1526,7 @@ class DomainRequestTests(TestWithUser, WebTest):
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
other_contacts_page = self.app.get(reverse("domain-request:other_contacts"))
other_contacts_page = self.app.get(reverse("domain-request:other_contacts", kwargs={"id": domain_request.pk}))
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
other_contacts_form = other_contacts_page.forms[0]
@ -1644,7 +1610,7 @@ class DomainRequestTests(TestWithUser, WebTest):
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
other_contacts_page = self.app.get(reverse("domain-request:other_contacts"))
other_contacts_page = self.app.get(reverse("domain-request:other_contacts", kwargs={"id": domain_request.pk}))
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
other_contacts_form = other_contacts_page.forms[0]
@ -1685,7 +1651,7 @@ class DomainRequestTests(TestWithUser, WebTest):
@less_console_noise_decorator
def test_if_yes_no_form_is_no_then_no_other_contacts_required(self):
"""Applicants with no other contacts have to give a reason."""
other_contacts_page = self.app.get(reverse("domain-request:other_contacts"))
other_contacts_page = self.app.get(reverse("domain-request:other_contacts", kwargs={"id": 0}))
other_contacts_form = other_contacts_page.forms[0]
other_contacts_form["other_contacts-has_other_contacts"] = "False"
response = other_contacts_page.forms[0].submit()
@ -1701,7 +1667,7 @@ class DomainRequestTests(TestWithUser, WebTest):
@less_console_noise_decorator
def test_if_yes_no_form_is_yes_then_other_contacts_required(self):
"""Applicants with other contacts do not have to give a reason."""
other_contacts_page = self.app.get(reverse("domain-request:other_contacts"))
other_contacts_page = self.app.get(reverse("domain-request:other_contacts", kwargs={"id": 0}))
other_contacts_form = other_contacts_page.forms[0]
other_contacts_form["other_contacts-has_other_contacts"] = "True"
response = other_contacts_page.forms[0].submit()
@ -1777,7 +1743,7 @@ class DomainRequestTests(TestWithUser, WebTest):
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
other_contacts_page = self.app.get(reverse("domain-request:other_contacts"))
other_contacts_page = self.app.get(reverse("domain-request:other_contacts", kwargs={"id": domain_request.id}))
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
other_contacts_form = other_contacts_page.forms[0]
@ -1850,7 +1816,7 @@ class DomainRequestTests(TestWithUser, WebTest):
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
other_contacts_page = self.app.get(reverse("domain-request:other_contacts"))
other_contacts_page = self.app.get(reverse("domain-request:other_contacts", kwargs={"id": domain_request.id}))
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
other_contacts_form = other_contacts_page.forms[0]
@ -1927,7 +1893,7 @@ class DomainRequestTests(TestWithUser, WebTest):
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
other_contacts_page = self.app.get(reverse("domain-request:other_contacts"))
other_contacts_page = self.app.get(reverse("domain-request:other_contacts", kwargs={"id": domain_request.id}))
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
other_contacts_form = other_contacts_page.forms[0]
@ -2007,7 +1973,7 @@ class DomainRequestTests(TestWithUser, WebTest):
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
other_contacts_page = self.app.get(reverse("domain-request:other_contacts"))
other_contacts_page = self.app.get(reverse("domain-request:other_contacts", kwargs={"id": domain_request.pk}))
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
other_contacts_form = other_contacts_page.forms[0]
@ -2083,7 +2049,7 @@ class DomainRequestTests(TestWithUser, WebTest):
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
other_contacts_page = self.app.get(reverse("domain-request:other_contacts"))
other_contacts_page = self.app.get(reverse("domain-request:other_contacts", kwargs={"id": domain_request.pk}))
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
other_contacts_form = other_contacts_page.forms[0]
@ -2153,7 +2119,7 @@ class DomainRequestTests(TestWithUser, WebTest):
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
so_page = self.app.get(reverse("domain-request:senior_official"))
so_page = self.app.get(reverse("domain-request:senior_official", kwargs={"id": domain_request.pk}))
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
so_form = so_page.forms[0]
@ -2222,7 +2188,7 @@ class DomainRequestTests(TestWithUser, WebTest):
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
so_page = self.app.get(reverse("domain-request:senior_official"))
so_page = self.app.get(reverse("domain-request:senior_official", kwargs={"id": domain_request.pk}))
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
so_form = so_page.forms[0]
@ -2303,7 +2269,7 @@ class DomainRequestTests(TestWithUser, WebTest):
@less_console_noise_decorator
def test_domain_request_about_your_organiztion_interstate(self):
"""Special districts have to answer an additional question."""
intro_page = self.app.get(reverse("domain-request:"))
intro_page = self.app.get(reverse("domain-request:start"))
# django-webtest does not handle cookie-based sessions well because it keeps
# resetting the session key on each new request, thus destroying the concept
# of a "session". We are going to do it manually, saving the session ID here
@ -2332,7 +2298,7 @@ class DomainRequestTests(TestWithUser, WebTest):
@less_console_noise_decorator
def test_domain_request_tribal_government(self):
"""Tribal organizations have to answer an additional question."""
intro_page = self.app.get(reverse("domain-request:"))
intro_page = self.app.get(reverse("domain-request:start"))
# django-webtest does not handle cookie-based sessions well because it keeps
# resetting the session key on each new request, thus destroying the concept
# of a "session". We are going to do it manually, saving the session ID here
@ -2363,7 +2329,7 @@ class DomainRequestTests(TestWithUser, WebTest):
@less_console_noise_decorator
def test_domain_request_so_dynamic_text(self):
intro_page = self.app.get(reverse("domain-request:"))
intro_page = self.app.get(reverse("domain-request:start"))
# django-webtest does not handle cookie-based sessions well because it keeps
# resetting the session key on each new request, thus destroying the concept
# of a "session". We are going to do it manually, saving the session ID here
@ -2447,7 +2413,7 @@ class DomainRequestTests(TestWithUser, WebTest):
@less_console_noise_decorator
def test_domain_request_dotgov_domain_dynamic_text(self):
intro_page = self.app.get(reverse("domain-request:"))
intro_page = self.app.get(reverse("domain-request:start"))
# django-webtest does not handle cookie-based sessions well because it keeps
# resetting the session key on each new request, thus destroying the concept
# of a "session". We are going to do it manually, saving the session ID here
@ -2555,8 +2521,23 @@ class DomainRequestTests(TestWithUser, WebTest):
@less_console_noise_decorator
def test_domain_request_formsets(self):
"""Users are able to add more than one of some fields."""
current_sites_page = self.app.get(reverse("domain-request:current_sites"))
DomainRequest.objects.all().delete()
# Create a new domain request
intro_page = self.app.get(reverse("domain-request:start"))
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
intro_form = intro_page.forms[0]
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
intro_form.submit()
all_domain_requests = DomainRequest.objects.all()
self.assertEqual(all_domain_requests.count(), 1)
new_domain_request_id = all_domain_requests.first().id
# Skip to the current sites page
current_sites_page = self.app.get(reverse("domain-request:current_sites", kwargs={"id": new_domain_request_id}))
# fill in the form field
current_sites_form = current_sites_page.forms[0]
self.assertIn("current_sites-0-website", current_sites_form.fields)
@ -2573,8 +2554,11 @@ class DomainRequestTests(TestWithUser, WebTest):
value = current_sites_form["current_sites-0-website"].value
self.assertEqual(value, "https://example.com")
self.assertIn("current_sites-1-website", current_sites_form.fields)
all_domain_requests = DomainRequest.objects.all()
self.assertEqual(all_domain_requests.count(), 1, msg="Expected one domain request but got multiple")
# and it is correctly referenced in the ManyToOne relationship
domain_request = DomainRequest.objects.get() # there's only one
domain_request = all_domain_requests.first() # there's only one
self.assertEqual(
domain_request.current_websites.filter(website="https://example.com").count(),
1,
@ -2712,7 +2696,7 @@ class DomainRequestTests(TestWithUser, WebTest):
Make sure the long name is displaying in the domain request form,
org step
"""
intro_page = self.app.get(reverse("domain-request:"))
intro_page = self.app.get(reverse("domain-request:start"))
# django-webtest does not handle cookie-based sessions well because it keeps
# resetting the session key on each new request, thus destroying the concept
# of a "session". We are going to do it manually, saving the session ID here
@ -2738,7 +2722,7 @@ class DomainRequestTests(TestWithUser, WebTest):
NOTE: This may be a moot point if we implement a more solid pattern in the
future, like not a submit action at all on the review page."""
review_page = self.app.get(reverse("domain-request:review"))
review_page = self.app.get(reverse("domain-request:review", kwargs={"id": 0}))
self.assertContains(review_page, "toggle-submit-domain-request")
self.assertContains(review_page, "Your request form is incomplete")
@ -2751,7 +2735,7 @@ class DomainRequestTests(TestWithUser, WebTest):
user=self.user, portfolio=portfolio, roles=[UserPortfolioRoleChoices.ORGANIZATION_MEMBER]
)
# This user should be forbidden from creating new domain requests
intro_page = self.app.get(reverse("domain-request:"), expect_errors=True)
intro_page = self.app.get(reverse("domain-request:start"), expect_errors=True)
self.assertEqual(intro_page.status_code, 403)
# This user should also be forbidden from editing existing ones
@ -2773,7 +2757,7 @@ class DomainRequestTests(TestWithUser, WebTest):
)
# This user should be allowed to create new domain requests
intro_page = self.app.get(reverse("domain-request:"))
intro_page = self.app.get(reverse("domain-request:start"))
self.assertEqual(intro_page.status_code, 200)
# This user should also be allowed to edit existing ones
@ -2975,6 +2959,69 @@ class TestDomainRequestWizard(TestWithUser, WebTest):
DomainRequest.objects.all().delete()
DomainInformation.objects.all().delete()
@less_console_noise_decorator
def test_breadcrumb_navigation(self):
"""
Tests the breadcrumb navigation behavior in domain request wizard.
Ensures that:
- Breadcrumb shows correct text based on portfolio flag
- Links point to correct destinations
- Back button appears on appropriate steps
- Back button is not present on first step
"""
# Create initial domain request
domain_request = completed_domain_request(
status=DomainRequest.DomainRequestStatus.STARTED,
user=self.user,
)
# Test without portfolio flag
start_page = self.app.get(f"/domain-request/{domain_request.id}/edit/").follow()
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
# Check initial breadcrumb state.
# Ensure that the request name is shown if it exists, otherwise just show new domain request.
self.assertContains(start_page, '<ol class="usa-breadcrumb__list">')
self.assertContains(start_page, "city.gov")
self.assertContains(start_page, 'href="/"')
self.assertContains(start_page, "Manage your domains")
self.assertNotContains(start_page, "Previous step")
# Move to next step
form = start_page.forms[0]
next_page = form.submit().follow()
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
# Verify that the back button appears
self.assertContains(next_page, "Previous step")
self.assertContains(next_page, "#arrow_back")
# Test with portfolio flag
with override_flag("organization_feature", active=True), override_flag("organization_requests", active=True):
portfolio = Portfolio.objects.create(
creator=self.user,
organization_name="test portfolio",
)
permission = UserPortfolioPermission.objects.create(
user=self.user,
portfolio=portfolio,
roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN],
)
# Check portfolio-specific breadcrumb
portfolio_page = self.app.get(f"/domain-request/{domain_request.id}/edit/").follow()
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
self.assertContains(portfolio_page, "Domain requests")
# Clean up portfolio
permission.delete()
portfolio.delete()
# Clean up
domain_request.delete()
@less_console_noise_decorator
def test_unlocked_steps_empty_domain_request(self):
"""Test when all fields in the domain request are empty."""
@ -3016,7 +3063,7 @@ class TestDomainRequestWizard(TestWithUser, WebTest):
# 10 unlocked steps, one active step, the review step will have link_usa but not check_circle
self.assertContains(detail_page, "#check_circle", count=9)
# Type of organization
self.assertContains(detail_page, "usa-current", count=1)
self.assertContains(detail_page, "usa-current", count=2)
self.assertContains(detail_page, "link_usa-checked", count=10)
else:
@ -3078,7 +3125,7 @@ class TestDomainRequestWizard(TestWithUser, WebTest):
# which unlocks if domain exists), one active step, the review step is locked
self.assertContains(detail_page, "#check_circle", count=4)
# Type of organization
self.assertContains(detail_page, "usa-current", count=1)
self.assertContains(detail_page, "usa-current", count=2)
self.assertContains(detail_page, "link_usa-checked", count=4)
else:
@ -3152,17 +3199,17 @@ class TestDomainRequestWizard(TestWithUser, WebTest):
self.assertContains(detail_page, "#lock", 1)
# The current option should be selected
self.assertContains(detail_page, "usa-current", count=1)
self.assertContains(detail_page, "usa-current", count=2)
# We default to the requesting entity page
expected_url = reverse("domain-request:portfolio_requesting_entity")
expected_url = reverse("domain-request:portfolio_requesting_entity", kwargs={"id": domain_request.id})
# This returns the entire url, thus "in"
self.assertIn(expected_url, detail_page.request.url)
# We shouldn't show the "domains" and "domain requests" buttons
# on this page.
self.assertNotContains(detail_page, "Domains")
self.assertNotContains(detail_page, "Domain requests")
self.assertNotContains(detail_page, "<span>Domain requests")
else:
self.fail(f"Expected a redirect, but got a different response: {response}")

View file

@ -660,17 +660,17 @@ class DomainRequestsDataType:
cls.safe_get(getattr(request, "all_alternative_domains", None)),
cls.safe_get(getattr(request, "all_other_contacts", None)),
cls.safe_get(getattr(request, "all_current_websites", None)),
cls.safe_get(getattr(request, "federal_agency", None)),
cls.safe_get(getattr(request.senior_official, "first_name", None)),
cls.safe_get(getattr(request.senior_official, "last_name", None)),
cls.safe_get(getattr(request.senior_official, "email", None)),
cls.safe_get(getattr(request.senior_official, "title", None)),
cls.safe_get(getattr(request, "converted_federal_agency", None)),
cls.safe_get(getattr(request.converted_senior_official, "first_name", None)),
cls.safe_get(getattr(request.converted_senior_official, "last_name", None)),
cls.safe_get(getattr(request.converted_senior_official, "email", None)),
cls.safe_get(getattr(request.converted_senior_official, "title", None)),
cls.safe_get(getattr(request.creator, "first_name", None)),
cls.safe_get(getattr(request.creator, "last_name", None)),
cls.safe_get(getattr(request.creator, "email", None)),
cls.safe_get(getattr(request, "organization_name", None)),
cls.safe_get(getattr(request, "city", None)),
cls.safe_get(getattr(request, "state_territory", None)),
cls.safe_get(getattr(request, "converted_organization_name", None)),
cls.safe_get(getattr(request, "converted_city", None)),
cls.safe_get(getattr(request, "converted_state_territory", None)),
cls.safe_get(getattr(request, "purpose", None)),
cls.safe_get(getattr(request, "cisa_representative_email", None)),
cls.safe_get(getattr(request, "last_submitted_date", None)),

View file

@ -53,7 +53,7 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView):
URL_NAMESPACE = "domain-request"
# name for accessing /domain-request/<id>/edit
EDIT_URL_NAME = "edit-domain-request"
NEW_URL_NAME = "/request/"
NEW_URL_NAME = "/request/start/"
# region: Titles
# We need to pass our human-readable step titles as context to the templates.
@ -158,6 +158,7 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView):
# Configure titles, wizard_conditions, unlocking_steps, and steps
self.configure_step_options()
self._domain_request = None # for caching
self.kwargs = {}
def configure_step_options(self):
"""Changes which steps are available to the user based on self.is_portfolio.
@ -182,7 +183,7 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView):
def has_pk(self):
"""Does this wizard know about a DomainRequest database record?"""
return "domain_request_id" in self.storage
return bool(self.kwargs.get("id") is not None)
def get_step_enum(self):
"""Determines which step enum we should use for the wizard"""
@ -214,11 +215,10 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView):
raise ValueError("Invalid value for User")
if self.has_pk():
id = self.storage["domain_request_id"]
try:
self._domain_request = DomainRequest.objects.get(
creator=creator,
pk=id,
pk=self.kwargs.get("id"),
)
return self._domain_request
except DomainRequest.DoesNotExist:
@ -238,8 +238,6 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView):
self._domain_request.save()
else:
self._domain_request = DomainRequest.objects.create(creator=self.request.user)
self.storage["domain_request_id"] = self._domain_request.id
return self._domain_request
@property
@ -295,6 +293,7 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView):
def get(self, request, *args, **kwargs):
"""This method handles GET requests."""
self.kwargs = kwargs
if not self.is_portfolio and self.request.user.is_org_user(request):
self.is_portfolio = True
# Configure titles, wizard_conditions, unlocking_steps, and steps
@ -307,7 +306,6 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView):
# and remove any prior wizard data from their session
if current_url == self.EDIT_URL_NAME and "id" in kwargs:
del self.storage
self.storage["domain_request_id"] = kwargs["id"]
# if accessing this class directly, redirect to either to an acknowledgement
# page or to the first step in the processes (if an edit rather than a new request);
@ -450,7 +448,7 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView):
if self.domain_request.requested_domain is not None:
requested_domain_name = self.domain_request.requested_domain.name
context_stuff = {}
context = {}
# Note: we will want to consolidate the non_org_steps_complete check into the same check that
# org_steps_complete is using at some point.
@ -458,7 +456,7 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView):
org_steps_complete = len(self.db_check_for_unlocking_steps()) == len(self.steps)
if (not self.is_portfolio and non_org_steps_complete) or (self.is_portfolio and org_steps_complete):
modal_button = '<button type="submit" ' 'class="usa-button" ' ">Submit request</button>"
context_stuff = {
context = {
"not_form": False,
"form_titles": self.titles,
"steps": self.steps,
@ -475,7 +473,7 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView):
}
else: # form is not complete
modal_button = '<button type="button" class="usa-button" data-close-modal>Return to request</button>'
context_stuff = {
context = {
"not_form": True,
"form_titles": self.titles,
"steps": self.steps,
@ -491,22 +489,19 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView):
}
# Hides the requests and domains buttons in the navbar
context_stuff["hide_requests"] = self.is_portfolio
context_stuff["hide_domains"] = self.is_portfolio
context["hide_requests"] = self.is_portfolio
context["hide_domains"] = self.is_portfolio
context["domain_request_id"] = self.domain_request.id
return context_stuff
return context
def get_step_list(self) -> list:
"""Dynamically generated list of steps in the form wizard."""
return request_step_list(self, self.get_step_enum())
def goto(self, step):
if step == "generic_org_type" or step == "portfolio_requesting_entity":
# We need to avoid creating a new domain request if the user
# clicks the back button
self.request.session["new_request"] = False
self.steps.current = step
return redirect(reverse(f"{self.URL_NAMESPACE}:{step}"))
return redirect(reverse(f"{self.URL_NAMESPACE}:{step}", kwargs={"id": self.domain_request.id}))
def goto_next_step(self):
"""Redirects to the next step."""
@ -532,9 +527,6 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView):
# which button did the user press?
button: str = request.POST.get("submit_button", "")
if "new_request" not in request.session:
request.session["new_request"] = True
# if user has acknowledged the intro message
if button == "intro_acknowledge":
# Split into a function: C901 'DomainRequestWizard.post' is too complex (11)
@ -572,9 +564,6 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView):
def handle_intro_acknowledge(self, request):
"""If we are starting a new request, clear storage
and redirect to the first step"""
if request.path_info == self.NEW_URL_NAME:
if self.request.session["new_request"] is True:
del self.storage
return self.goto(self.steps.first)
def save(self, forms: list):

View file

@ -7,7 +7,6 @@ def index(request):
if request and request.user and request.user.is_authenticated:
# This controls the creation of a new domain request in the wizard
request.session["new_request"] = True
context["user_domain_count"] = request.user.get_user_domain_ids(request).count()
return render(request, "home.html", context)

View file

@ -40,8 +40,6 @@ class PortfolioDomainRequestsView(PortfolioDomainRequestsPermissionView, View):
template_name = "portfolio_requests.html"
def get(self, request):
if self.request.user.is_authenticated:
request.session["new_request"] = True
return render(request, "portfolio_requests.html")

View file

@ -53,7 +53,7 @@ class UserProfileView(UserProfilePermissionView, FormMixin):
context = super().get_context_data(**kwargs)
# Set the profile_back_button_text based on the redirect parameter
if kwargs.get("redirect") == "domain-request:":
if kwargs.get("redirect") == "domain-request:start":
context["profile_back_button_text"] = "Go back to your domain request"
else:
context["profile_back_button_text"] = "Go to manage your domains"