diff --git a/.github/ISSUE_TEMPLATE/developer-onboarding.md b/.github/ISSUE_TEMPLATE/developer-onboarding.md
index 92ae9e3a1..94b2a367d 100644
--- a/.github/ISSUE_TEMPLATE/developer-onboarding.md
+++ b/.github/ISSUE_TEMPLATE/developer-onboarding.md
@@ -3,7 +3,7 @@ name: Developer Onboarding
about: Onboarding steps for developers.
title: 'Developer Onboarding: GH_HANDLE'
labels: dev, onboarding
-assignees: loganmeetsworld
+assignees: abroddrick
---
@@ -16,7 +16,7 @@ assignees: loganmeetsworld
There are several tools we use locally that you will need to have.
- [ ] [Install the cf CLI v7](https://docs.cloudfoundry.org/cf-cli/install-go-cli.html#pkg-mac) for the ability to deploy
-- [ ] Make sure you have `gpg` >2.1.7. Run `gpg --version` to check.
+- [ ] Make sure you have `gpg` >2.1.7. Run `gpg --version` to check. If not, [install gnupg](https://formulae.brew.sh/formula/gnupg)
- [ ] Install the [Github CLI](https://cli.github.com/)
## Access
@@ -24,27 +24,27 @@ There are several tools we use locally that you will need to have.
### Steps for the onboardee
- [ ] Setup [commit signing in Github](#setting-up-commit-signing) and with git locally.
- [ ] [Create a cloud.gov account](https://cloud.gov/docs/getting-started/accounts/)
-- [ ] Have an admin add you to the CISA Github organization and Dotgov Team.
+- [ ] Email github@cisa.dhs.gov (cc: Cameron) to add you to the [CISA Github organization](https://github.com/getgov) and [.gov Team](https://github.com/orgs/cisagov/teams/gov).
- [ ] Ensure you can login to your cloud.gov account via the CLI
```bash
cf login -a api.fr.cloud.gov --sso
```
- [ ] Have an admin add you to cloud.gov org and set up your [sandbox developer space](#setting-up-developer-sandbox). Ensure you can deploy to your sandbox space.
-- [ ] Have an admin add you to our login.gov sandbox team (`.gov registrar poc`) via the [dashboard](https://dashboard.int.identitysandbox.gov/).
+- [ ] Have an admin add you to our login.gov sandbox team (`.gov Registrar`) via the [dashboard](https://dashboard.int.identitysandbox.gov/).
**Note:** As mentioned in the [Login documentation](https://developers.login.gov/testing/), the sandbox Login account is different account from your regular, production Login account. If you have not created a Login account for the sandbox before, you will need to create a new account first.
- [ ] Optional- add yourself as a codeowner if desired. See the [Developer readme](https://github.com/cisagov/getgov/blob/main/docs/developer/README.md) for how to do this and what it does.
### Steps for the onboarder
-- [ ] Add the onboardee to cloud.gov org (cisa-getgov-prototyping)
+- [ ] Add the onboardee to cloud.gov org (cisa-dotgov)
- [ ] Setup a [developer specific space for the new developer](#setting-up-developer-sandbox)
-- [ ] Add the onboardee to our login.gov sandbox team (`.gov registrar poc`) via the [dashboard](https://dashboard.int.identitysandbox.gov/)
+- [ ] Add the onboardee to our login.gov sandbox team (`.gov Registrar`) via the [dashboard](https://dashboard.int.identitysandbox.gov/)
## Documents to Review
-- [ ] [Team Charter](https://docs.google.com/document/d/1xhMKlW8bMcxyF7ipsOYxw1SQYVi-lWPkcDHSUS6miNg/edit), in particular our Github Policy
+- [ ] [Team Onboarding](https://docs.google.com/document/d/1ukbpW4LSqkb_CCt8LWfpehP03qqfyYfvK3Fl21NaEq8/edit?usp=sharing)
- [ ] [Architecture Decision Records](https://github.com/cisagov/dotgov/tree/main/docs/architecture/decisions)
- [ ] [Contributing Policy](https://github.com/cisagov/dotgov/tree/main/CONTRIBUTING.md)
@@ -80,6 +80,15 @@ You may need to add these two lines to your shell's rc file (e.g. `.bashrc` or `
GPG_TTY=$(tty)
export GPG_TTY
```
+and then
+
+```bash
+source ~/.bashrc
+```
+or
+```bash
+source ~/.zshrc
+```
## Setting up developer sandbox
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index 1eeef397b..f65007b2b 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -1,13 +1,134 @@
-# #
+## Ticket
-## đź—Ł Description ##
+Resolves #00
+
-
+All other changes require just a single approving review.-->
-## đź’ Motivation and context ##
+## Changes
-
-
-
-
\ No newline at end of file
+
+- Change 1
+- Change 2
+
+
+
+## Context for reviewers
+
+
+
+## Setup
+
+
+
+## Code Review Verification Steps
+
+### As the original developer, I have
+
+#### Satisfied acceptance criteria and met development standards
+
+- [ ] Met the acceptance criteria, or will meet them in a subsequent PR
+- [ ] Created/modified automated tests
+- [ ] Added at least 2 developers as PR reviewers (only 1 will need to approve)
+- [ ] Messaged on Slack or in standup to notify the team that a PR is ready for review
+- [ ] Changes to “how we do things” are documented in READMEs and or onboarding guide
+- [ ] If any model was updated to modify/add/delete columns, makemigrations was ran and the assoicated migrations file has been commited.
+
+#### Ensured code standards are met (Original Developer)
+
+- [ ] All new functions and methods are commented using plain language
+- [ ] Did dependency updates in Pipfile also get changed in requirements.txt?
+- [ ] Interactions with external systems are wrapped in try/except
+- [ ] Error handling exists for unusual or missing values
+
+#### Validated user-facing changes (if applicable)
+
+- [ ] New pages have been added to .pa11yci file so that they will be tested with our automated accessibility testing
+- [ ] Checked keyboard navigability
+- [ ] Tested general usability, landmarks, page header structure, and links with a screen reader (such as Voiceover or ANDI)
+- [ ] Add at least 1 designer as PR reviewer
+
+### As a code reviewer, I have
+
+#### Reviewed, tested, and left feedback about the changes
+
+- [ ] Pulled this branch locally and tested it
+- [ ] Reviewed this code and left comments
+- [ ] Checked that all code is adequately covered by tests
+- [ ] Made it clear which comments need to be addressed before this work is merged
+- [ ] If any model was updated to modify/add/delete columns, makemigrations was ran and the assoicated migrations file has been commited.
+
+#### Ensured code standards are met (Code reviewer)
+
+- [ ] All new functions and methods are commented using plain language
+- [ ] Interactions with external systems are wrapped in try/except
+- [ ] Error handling exists for unusual or missing values
+- [ ] (Rarely needed) Did dependency updates in Pipfile also get changed in requirements.txt?
+
+#### Validated user-facing changes as a developer
+
+- [ ] New pages have been added to .pa11yci file so that they will be tested with our automated accessibility testing
+- [ ] Checked keyboard navigability
+- [ ] Meets all designs and user flows provided by design/product
+- [ ] Tested general usability, landmarks, page header structure, and links with a screen reader (such as Voiceover or ANDI)
+
+- [ ] Tested with multiple browsers, the suggestion is to use ones that the developer didn't (check off which ones were used)
+ - [ ] Chrome
+ - [ ] Microsoft Edge
+ - [ ] FireFox
+ - [ ] Safari
+
+- [ ] (Rarely needed) Tested as both an analyst and applicant user
+
+**Note:** Multiple code reviewers can share the checklists above, a second reviewers should not make a duplicate checklist
+
+### As a designer reviewer, I have
+
+#### Verified that the changes match the design intention
+
+- [ ] Checked that the design translated visually
+- [ ] Checked behavior
+- [ ] Checked different states (empty, one, some, error)
+- [ ] Checked for landmarks, page heading structure, and links
+- [ ] Tried to break the intended flow
+
+#### Validated user-facing changes as a designer
+
+- [ ] Checked keyboard navigability
+- [ ] Tested general usability, landmarks, page header structure, and links with a screen reader (such as Voiceover or ANDI)
+
+- [ ] Tested with multiple browsers (check off which ones were used)
+ - [ ] Chrome
+ - [ ] Microsoft Edge
+ - [ ] FireFox
+ - [ ] Safari
+
+- [ ] (Rarely needed) Tested as both an analyst and applicant user
+
+## Screenshots
+
+
diff --git a/.github/workflows/deploy-sandbox.yaml b/.github/workflows/deploy-sandbox.yaml
index 614484a2a..5d9000401 100644
--- a/.github/workflows/deploy-sandbox.yaml
+++ b/.github/workflows/deploy-sandbox.yaml
@@ -15,6 +15,10 @@ jobs:
|| startsWith(github.head_ref, 'rb/')
|| startsWith(github.head_ref, 'ko/')
|| startsWith(github.head_ref, 'gd/')
+ || startsWith(github.head_ref, 'za/')
+ || startsWith(github.head_ref, 'rh/')
+ || startsWith(github.head_ref, 'nl/')
+ || startsWith(github.head_ref, 'dk/')
outputs:
environment: ${{ steps.var.outputs.environment}}
runs-on: "ubuntu-latest"
@@ -49,7 +53,7 @@ jobs:
with:
cf_username: ${{ secrets[env.CF_USERNAME] }}
cf_password: ${{ secrets[env.CF_PASSWORD] }}
- cf_org: cisa-getgov-prototyping
+ cf_org: cisa-dotgov
cf_space: ${{ env.ENVIRONMENT }}
push_arguments: "-f ops/manifests/manifest-${{ env.ENVIRONMENT }}.yaml"
comment:
diff --git a/.github/workflows/deploy-stable.yaml b/.github/workflows/deploy-stable.yaml
index 2f1a2a6b4..0a40ac097 100644
--- a/.github/workflows/deploy-stable.yaml
+++ b/.github/workflows/deploy-stable.yaml
@@ -36,6 +36,6 @@ jobs:
with:
cf_username: ${{ secrets.CF_STABLE_USERNAME }}
cf_password: ${{ secrets.CF_STABLE_PASSWORD }}
- cf_org: cisa-getgov-prototyping
+ cf_org: cisa-dotgov
cf_space: stable
push_arguments: "-f ops/manifests/manifest-stable.yaml"
diff --git a/.github/workflows/deploy-staging.yaml b/.github/workflows/deploy-staging.yaml
index 068751c30..1db63e2a2 100644
--- a/.github/workflows/deploy-staging.yaml
+++ b/.github/workflows/deploy-staging.yaml
@@ -36,6 +36,6 @@ jobs:
with:
cf_username: ${{ secrets.CF_STAGING_USERNAME }}
cf_password: ${{ secrets.CF_STAGING_PASSWORD }}
- cf_org: cisa-getgov-prototyping
+ cf_org: cisa-dotgov
cf_space: staging
push_arguments: "-f ops/manifests/manifest-staging.yaml"
diff --git a/.github/workflows/migrate.yaml b/.github/workflows/migrate.yaml
index 28447a605..705014af1 100644
--- a/.github/workflows/migrate.yaml
+++ b/.github/workflows/migrate.yaml
@@ -15,12 +15,16 @@ on:
options:
- stable
- staging
+ - nl
+ - rh
+ - za
- gd
- rb
- ko
- ab
- bl
- rjm
+ - dk
jobs:
migrate:
@@ -34,6 +38,6 @@ jobs:
with:
cf_username: ${{ secrets[env.CF_USERNAME] }}
cf_password: ${{ secrets[env.CF_PASSWORD] }}
- cf_org: cisa-getgov-prototyping
+ cf_org: cisa-dotgov
cf_space: ${{ github.event.inputs.environment }}
full_command: "cf run-task getgov-${{ github.event.inputs.environment }} --command 'python manage.py migrate' --name migrate"
diff --git a/.github/workflows/reset-db.yaml b/.github/workflows/reset-db.yaml
index 0d3ed4934..0bf1af2d9 100644
--- a/.github/workflows/reset-db.yaml
+++ b/.github/workflows/reset-db.yaml
@@ -16,12 +16,16 @@ on:
options:
- stable
- staging
+ - nl
+ - rh
+ - za
- gd
- rb
- ko
- ab
- bl
- rjm
+ - dk
jobs:
reset-db:
@@ -35,7 +39,7 @@ jobs:
with:
cf_username: ${{ secrets[env.CF_USERNAME] }}
cf_password: ${{ secrets[env.CF_PASSWORD] }}
- cf_org: cisa-getgov-prototyping
+ cf_org: cisa-dotgov
cf_space: ${{ github.event.inputs.environment }}
full_command: "cf run-task getgov-${{ github.event.inputs.environment }} --command 'python manage.py flush --no-input' --name flush"
@@ -44,7 +48,7 @@ jobs:
with:
cf_username: ${{ secrets[env.CF_USERNAME] }}
cf_password: ${{ secrets[env.CF_PASSWORD] }}
- cf_org: cisa-getgov-prototyping
+ cf_org: cisa-dotgov
cf_space: ${{ github.event.inputs.environment }}
full_command: "cf run-task getgov-${{ github.event.inputs.environment }} --command 'python manage.py migrate' --name migrate"
@@ -53,6 +57,6 @@ jobs:
with:
cf_username: ${{ secrets[env.CF_USERNAME] }}
cf_password: ${{ secrets[env.CF_PASSWORD] }}
- cf_org: cisa-getgov-prototyping
+ cf_org: cisa-dotgov
cf_space: ${{ github.event.inputs.environment }}
full_command: "cf run-task getgov-${{ github.event.inputs.environment }} --command 'python manage.py load' --name loaddata"
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index d745f76c7..ab15c660f 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -9,6 +9,22 @@ There are a handful of things we do not commit to the repository:
- Compliance documentation that includes IP addresses
- Secrets of any kind
+## Branch naming convention
+
+For developers, you can auto-deploy your code to your sandbox (if applicable) by naming your branch thusly: jsd/123-feature-description
+Where 'jsd' stands for your initials and sandbox environment name (if you were called John Smith Doe), and 123 matches the ticket number if applicable.
+
+## Approvals
+
+When a code change is made that is not user facing, then the following is required:
+- a developer approves the PR
+
+When a code change is made that is user facing, beyond content updates, then the following are required:
+- a developer approves the PR
+- a designer approves the PR or checks off all relevant items in this checklist
+
+Content or document updates require a single person to approve.
+
## Project Management
We use [Github Projects](https://docs.github.com/en/issues/planning-and-tracking-with-projects/learning-about-projects/about-projects) for project management and tracking.
diff --git a/docs/architecture/decisions/0006-cloud-gov.md b/docs/architecture/decisions/0006-cloud-gov.md
index 618166fe4..c730da4b2 100644
--- a/docs/architecture/decisions/0006-cloud-gov.md
+++ b/docs/architecture/decisions/0006-cloud-gov.md
@@ -8,7 +8,7 @@ Accepted
## Context
-We need a place to run our application for the registrar. Cloud.gov is a FIMSA Moderate Fedramped solution that supports our language and framework selections.
+We need a place to run our application for the registrar. Cloud.gov is a FISMA Moderate FedRAMP'd solution that supports our language and framework selections.
## Decision
@@ -16,10 +16,10 @@ To use cloud.gov to host our application(s).
## Consequences
-Choosing cloud.gov for our solution means we are locked into its opinionated choices for our infrastructure. It forces us to run 12-factor applications. It doesn't support brokering for services we may need like email notifications.
-
-It also means the compliance lift is much lighter. We do not need to prove we are compliance for the majority of our infrastructure and our runtime enviornment.
+* Choosing Cloud.gov for our solution means we are assisted by its opinionated choices for our infrastructure. For example, it forces us to run 12-factor applications.
+* It doesn't support brokering for services we may need like email notifications.
+* The compliance lift is lighter. We can inherit Cloud.gov's controls for the majority of our infrastructure and our runtime enviornment.
## Alternatives Considered
-Run our application on in either CISA's Azure or AWS environment with a continerized deployment.
+Run our application on in either CISA's Azure or AWS environment with a containerized deployment.
diff --git a/docs/architecture/decisions/0021-django-admin.md b/docs/architecture/decisions/0021-django-admin.md
index 6e9580932..add6992cd 100644
--- a/docs/architecture/decisions/0021-django-admin.md
+++ b/docs/architecture/decisions/0021-django-admin.md
@@ -34,6 +34,13 @@ In contrast to building an admin interface from scratch where development activi
involve _building up_, leveraging Django Admin will require carefully _pairing back_ the functionalities available to
users such as analysts.
+On accessibility: Django admin is almost fully accessible out-of-the-box, the exceptions being tables, checkboxes, and
+color contrast. We have remedied the first 2 with template overrides and the 3rd with theming (see below).
+
+On USWDS and theming: Django admin brings its own high level design framework. We have determined that theming on top of Django (scss)
+is easy and worthwhile, while overwriting Django's templates with USWDS is hard and provides little return on investment
+([research PR](https://github.com/cisagov/getgov/pull/831)).
+
While we anticipate that Django Admin will meet (or even exceed) the user needs that we are aware of today, it is still
an open question whether Django Admin will be the long-term administrator tool of choice. A pivot away from Django Admin
in the future would of course mean starting from scratch at a later date, and potentially juggling two separate admin
diff --git a/docs/architecture/decisions/0022-submit-domain-request-user-flow.md b/docs/architecture/decisions/0022-submit-domain-request-user-flow.md
new file mode 100644
index 000000000..cc0ca83b8
--- /dev/null
+++ b/docs/architecture/decisions/0022-submit-domain-request-user-flow.md
@@ -0,0 +1,23 @@
+# 22. Submit Domain Request User Flow
+
+Date: 2023-07-18
+
+## Status
+
+Accepted
+
+## Context
+
+Historically, the .gov vendor managed initial identity verification and organizational affiliation for users that request a .gov domain. With the new registrar, _any user with a valid Login.gov account_ will be able to make a request. As a primary layer of abuse prevention (i.e., DDoSing the registry program with illegitimate requests), we need a way to stop new users from submitting multiple domain requests before they are known to the .gov registry. In this case, "known" means they have at least one approved domain application or existing domain.
+
+## Considered Options
+
+**Option 1:** Users will not be able to submit any new applications if they have 0 prior approved applications OR prior registered .gov domains. We would add a page alert informing the user that they cannot submit their application because they have an application in one of these "3" statuses (Submitted, In Review or Action Needed). They would still be able to create and edit new applications, just not submit them. The benefits of this option are that it would allow users to have multiple applications essentially in "draft mode" that are queued up and ready for submission after they are permitted to submit.
+
+**Option 2:** Users will not be able to submit any new applications if they have 0 prior approved applications OR prior registered .gov domains. Additionally, we would remove the ability to edit any application with the started/withdrawn/rejected status, or start a new application. The benefit of this option is that a user would not be able to begin an action (submitting an application) that they are not allowed to complete.
+
+## Decision
+
+We have decided to go with option 1. New users of the registrar will need to have at least one approved application OR prior registered .gov domain in order to submit another application. We chose this option because we would like to allow users be able to work on applications, even if they are unable to submit them.
+
+A [user flow diagram](https://miro.com/app/board/uXjVM3jz3Bs=/?share_link_id=875307531981) demonstrates our decision.
diff --git a/docs/developer/README.md b/docs/developer/README.md
index bc102a64b..de97b6107 100644
--- a/docs/developer/README.md
+++ b/docs/developer/README.md
@@ -18,6 +18,20 @@ If you're new to Django, see [Getting Started with Django](https://www.djangopro
Visit the running application at [http://localhost:8080](http://localhost:8080).
+
+### Troubleshooting
+
+* If you are using Windows, you may need to change your [line endings](https://docs.github.com/en/get-started/getting-started-with-git/configuring-git-to-handle-line-endings). If not, you may not be able to run manage.py.
+* Unix based operating systems (like macOS or Linux) handle line separators [differently than Windows does](https://superuser.com/questions/374028/how-are-n-and-r-handled-differently-on-linux-and-windows). This can break bash scripts in particular. In the case of manage.py, it uses *#!/usr/bin/env python* to access the Python executable. Since the script is still thinking in terms of unix line seperators, it may look for the executable *python\r* rather than *python* (since Windows cannot read the carriage return on its own) - thus leading to the error `usr/bin/env: 'python\r' no such file or directory`
+* If you'd rather not change this globally, add a `.gitattributes` file in the project root with `* text eol=lf` as the text content, and [refresh the repo](https://docs.github.com/en/get-started/getting-started-with-git/configuring-git-to-handle-line-endings#refreshing-a-repository-after-changing-line-endings)
+* If you are using a Mac with a M1 chip, and see this error `The chromium binary is not available for arm64.` or an error involving `puppeteer`, try adding this line below into your `.bashrc` or `.zshrc`.
+
+```
+export DOCKER_DEFAULT_PLATFORM=linux/amd64
+```
+
+When completed, don't forget to rerun `docker-compose up`!
+
## Branch Conventions
We use the branch convention of `initials/branch-topic` (ex: `lmm/fix-footer`). This allows for automated deployment to a developer sandbox namespaced to the initials.
@@ -66,7 +80,7 @@ The endpoint /admin can be used to view and manage site content, including but n
1. Login via login.gov
2. Go to the home page and make sure you can see the part where you can submit an application
3. Go to /admin and it will tell you that UUID is not authorized, copy that UUID for use in 4
-4. in src/registrar/fixtures.py add to the ADMINS list in that file by adding your UUID as your username along with your first and last name. See below:
+4. in src/registrar/fixtures.py add to the `ADMINS` list in that file by adding your UUID as your username along with your first and last name. See below:
```
ADMINS = [
@@ -79,8 +93,32 @@ The endpoint /admin can be used to view and manage site content, including but n
]
```
-5. In the browser, navigate to /admins. To verify that all is working correctly, under "domain applications" you should see fake domains with various fake statuses.
+5. In the browser, navigate to /admin. To verify that all is working correctly, under "domain applications" you should see fake domains with various fake statuses.
+6. Add an optional email key/value pair
+### Adding an Analyst to /admin
+Analysts are a variant of the admin role with limited permissions. The process for adding an Analyst is much the same as adding an admin:
+
+1. Login via login.gov (if you already exist as an admin, you will need to create a separate login.gov account for this: i.e. first.last+1@email.com)
+2. Go to the home page and make sure you can see the part where you can submit an application
+3. Go to /admin and it will tell you that UUID is not authorized, copy that UUID for use in 4 (this will be a different UUID than the one obtained from creating an admin)
+4. in src/registrar/fixtures.py add to the `STAFF` list in that file by adding your UUID as your username along with your first and last name. See below:
+
+```
+ STAFF = [
+ {
+ "username": "",
+ "first_name": "",
+ "last_name": "",
+ },
+ ...
+ ]
+```
+
+5. In the browser, navigate to /admin. To verify that all is working correctly, verify that you can only see a sub-section of the modules and some are set to view-only.
+6. Add an optional email key/value pair
+
+Do note that if you wish to have both an analyst and admin account, append `-Analyst` to your first and last name, or use a completely different first/last name to avoid confusion. Example: `Bob-Analyst`
## Adding to CODEOWNERS (optional)
The CODEOWNERS file sets the tagged individuals as default reviewers on any Pull Request that changes files that they are marked as owners of.
@@ -166,6 +204,17 @@ from .common import less_console_noise
#
```
+### Accessibility Testing in the browser
+
+We use the [ANDI](https://www.ssa.gov/accessibility/andi/help/install.html) browser extension
+from ssa.gov for accessibility testing outside the pipeline.
+
+ANDI will get blocked by our CSP settings, so you will need to install the
+[Disable Content-Security-Policy extension](https://chrome.google.com/webstore/detail/disable-content-security/ieelmcmcagommplceebfedjlakkhpden)
+and activate it for the page you'd like to test.
+
+Note - refresh after enabling the extension on a page but before clicking ANDI.
+
### Accessibility Scanning
The tool `pa11y-ci` is used to scan pages for compliance with a set of
@@ -203,7 +252,7 @@ Assets are stored in `registrar/assets` during development and served from `regi
We utilize the [uswds-compile tool](https://designsystem.digital.gov/documentation/getting-started/developers/phase-two-compile/) from USWDS to compile and package USWDS assets.
-## Making and view style changes
+## Making and viewing style changes
When you run `docker-compose up` the `node` service in the container will begin to watch for changes in the `registrar/assets` folder, and will recompile once any changes are made.
diff --git a/docs/django-admin/roles.md b/docs/django-admin/roles.md
index 431380300..ab4867184 100644
--- a/docs/django-admin/roles.md
+++ b/docs/django-admin/roles.md
@@ -17,4 +17,5 @@ Staff
auditlog | log entry | can view log entry
registrar | contact | can view contact
registrar | domain application | can change domain application
-registrar | domain | can view domain
\ No newline at end of file
+registrar | domain | can view domain
+registrar | user | can view user
\ No newline at end of file
diff --git a/ops/manifests/manifest-dk.yaml b/ops/manifests/manifest-dk.yaml
new file mode 100644
index 000000000..87de8a496
--- /dev/null
+++ b/ops/manifests/manifest-dk.yaml
@@ -0,0 +1,29 @@
+---
+applications:
+- name: getgov-dk
+ buildpacks:
+ - python_buildpack
+ path: ../../src
+ instances: 1
+ memory: 512M
+ stack: cflinuxfs4
+ timeout: 180
+ command: ./run.sh
+ health-check-type: http
+ health-check-http-endpoint: /health
+ env:
+ # Send stdout and stderr straight to the terminal without buffering
+ PYTHONUNBUFFERED: yup
+ # Tell Django where to find its configuration
+ DJANGO_SETTINGS_MODULE: registrar.config.settings
+ # Tell Django where it is being hosted
+ DJANGO_BASE_URL: https://getgov-dk.app.cloud.gov
+ # Tell Django how much stuff to log
+ DJANGO_LOG_LEVEL: INFO
+ # Public site base URL
+ GETGOV_PUBLIC_SITE_URL: https://federalist-877ab29f-16f6-4f12-961c-96cf064cf070.sites.pages.cloud.gov/site/cisagov/getgov-home/
+ routes:
+ - route: getgov-dk.app.cloud.gov
+ services:
+ - getgov-credentials
+ - getgov-dk-database
diff --git a/ops/manifests/manifest-nl.yaml b/ops/manifests/manifest-nl.yaml
new file mode 100644
index 000000000..dcdb02794
--- /dev/null
+++ b/ops/manifests/manifest-nl.yaml
@@ -0,0 +1,29 @@
+---
+applications:
+- name: getgov-nl
+ buildpacks:
+ - python_buildpack
+ path: ../../src
+ instances: 1
+ memory: 512M
+ stack: cflinuxfs4
+ timeout: 180
+ command: ./run.sh
+ health-check-type: http
+ health-check-http-endpoint: /health
+ env:
+ # Send stdout and stderr straight to the terminal without buffering
+ PYTHONUNBUFFERED: yup
+ # Tell Django where to find its configuration
+ DJANGO_SETTINGS_MODULE: registrar.config.settings
+ # Tell Django where it is being hosted
+ DJANGO_BASE_URL: https://getgov-nl.app.cloud.gov
+ # Tell Django how much stuff to log
+ DJANGO_LOG_LEVEL: INFO
+ # default public site location
+ GETGOV_PUBLIC_SITE_URL: https://beta.get.gov
+ routes:
+ - route: getgov-nl.app.cloud.gov
+ services:
+ - getgov-credentials
+ - getgov-nl-database
diff --git a/ops/manifests/manifest-rh.yaml b/ops/manifests/manifest-rh.yaml
new file mode 100644
index 000000000..d8bf4cb77
--- /dev/null
+++ b/ops/manifests/manifest-rh.yaml
@@ -0,0 +1,29 @@
+---
+applications:
+- name: getgov-rh
+ buildpacks:
+ - python_buildpack
+ path: ../../src
+ instances: 1
+ memory: 512M
+ stack: cflinuxfs4
+ timeout: 180
+ command: ./run.sh
+ health-check-type: http
+ health-check-http-endpoint: /health
+ env:
+ # Send stdout and stderr straight to the terminal without buffering
+ PYTHONUNBUFFERED: yup
+ # Tell Django where to find its configuration
+ DJANGO_SETTINGS_MODULE: registrar.config.settings
+ # Tell Django where it is being hosted
+ DJANGO_BASE_URL: https://getgov-rh.app.cloud.gov
+ # Tell Django how much stuff to log
+ DJANGO_LOG_LEVEL: INFO
+ # default public site location
+ GETGOV_PUBLIC_SITE_URL: https://beta.get.gov
+ routes:
+ - route: getgov-rh.app.cloud.gov
+ services:
+ - getgov-credentials
+ - getgov-rh-database
diff --git a/ops/manifests/manifest-za.yaml b/ops/manifests/manifest-za.yaml
new file mode 100644
index 000000000..fbacb6912
--- /dev/null
+++ b/ops/manifests/manifest-za.yaml
@@ -0,0 +1,29 @@
+---
+applications:
+- name: getgov-za
+ buildpacks:
+ - python_buildpack
+ path: ../../src
+ instances: 1
+ memory: 512M
+ stack: cflinuxfs4
+ timeout: 180
+ command: ./run.sh
+ health-check-type: http
+ health-check-http-endpoint: /health
+ env:
+ # Send stdout and stderr straight to the terminal without buffering
+ PYTHONUNBUFFERED: yup
+ # Tell Django where to find its configuration
+ DJANGO_SETTINGS_MODULE: registrar.config.settings
+ # Tell Django where it is being hosted
+ DJANGO_BASE_URL: https://getgov-za.app.cloud.gov
+ # Tell Django how much stuff to log
+ DJANGO_LOG_LEVEL: INFO
+ # default public site location
+ GETGOV_PUBLIC_SITE_URL: https://beta.get.gov
+ routes:
+ - route: getgov-za.app.cloud.gov
+ services:
+ - getgov-credentials
+ - getgov-za-database
diff --git a/ops/scripts/create_dev_sandbox.sh b/ops/scripts/create_dev_sandbox.sh
index f180ada8d..5eeed9c10 100755
--- a/ops/scripts/create_dev_sandbox.sh
+++ b/ops/scripts/create_dev_sandbox.sh
@@ -21,9 +21,9 @@ then
git checkout -b new-dev-sandbox-$1
fi
-cf target -o cisa-getgov-prototyping
+cf target -o cisa-dotgov
-read -p "Are you logged in to the cisa-getgov-prototyping CF org above? (y/n) " -n 1 -r
+read -p "Are you logged in to the cisa-dotgov CF org above? (y/n) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]
then
@@ -49,9 +49,9 @@ sed -i '' '/getgov-staging.app.cloud.gov/ {a\
echo "Creating new cloud.gov space for $1..."
cf create-space $1
-cf target -o "cisa-getgov-prototyping" -s $1
-cf bind-security-group public_networks_egress cisa-getgov-prototyping --space $1
-cf bind-security-group trusted_local_networks_egress cisa-getgov-prototyping --space $1
+cf target -o "cisa-dotgov" -s $1
+cf bind-security-group public_networks_egress cisa-dotgov --space $1
+cf bind-security-group trusted_local_networks_egress cisa-dotgov --space $1
echo "Creating new cloud.gov DB for $1. This usually takes about 5 minutes..."
cf create-service aws-rds micro-psql getgov-$1-database
@@ -91,7 +91,7 @@ cd ..
cf push getgov-$1 -f ops/manifests/manifest-$1.yaml
read -p "Please provide the email of the space developer: " -r
-cf set-space-role $REPLY cisa-getgov-prototyping $1 SpaceDeveloper
+cf set-space-role $REPLY cisa-dotgov $1 SpaceDeveloper
read -p "Should we run migrations? (y/n) " -n 1 -r
echo
diff --git a/ops/scripts/deploy.sh b/ops/scripts/deploy.sh
index d8dd45fbc..50f0f5bfa 100755
--- a/ops/scripts/deploy.sh
+++ b/ops/scripts/deploy.sh
@@ -4,7 +4,7 @@
../ops/scripts/build.sh
# Deploy to sandbox
-cf target -o cisa-getgov-prototyping -s $1
+cf target -o cisa-dotgov -s $1
cf push getgov-$1 -f ../ops/manifests/manifest-$1.yaml
# migrations need to be run manually. Developers can use this command
diff --git a/ops/scripts/destroy_dev_sandbox.sh b/ops/scripts/destroy_dev_sandbox.sh
index 47a7f26d8..9e233b2f1 100755
--- a/ops/scripts/destroy_dev_sandbox.sh
+++ b/ops/scripts/destroy_dev_sandbox.sh
@@ -20,9 +20,9 @@ then
git checkout -b remove-dev-sandbox-$1
fi
-cf target -o cisa-getgov-prototyping -s $1
+cf target -o cisa-dotgov -s $1
-read -p "Are you logged in to the cisa-getgov-prototyping CF org above? (y/n) " -n 1 -r
+read -p "Are you logged in to the cisa-dotgov CF org above? (y/n) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]
then
diff --git a/ops/scripts/rotate_cloud_secrets.sh b/ops/scripts/rotate_cloud_secrets.sh
index aa77c39a8..23e4aa590 100755
--- a/ops/scripts/rotate_cloud_secrets.sh
+++ b/ops/scripts/rotate_cloud_secrets.sh
@@ -9,8 +9,8 @@ if [ -z "$1" ]; then
exit 1
fi
-cf target -o cisa-getgov-prototyping -s $1
-read -p "Are you logged in to the cisa-getgov-prototyping CF org above and targeting the correct space? (y/n) " -n 1 -r
+cf target -o cisa-dotgov -s $1
+read -p "Are you logged in to the cisa-dotgov CF org above and targeting the correct space? (y/n) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]
then
diff --git a/src/package-lock.json b/src/package-lock.json
index 18a5de770..dc1464ee8 100644
--- a/src/package-lock.json
+++ b/src/package-lock.json
@@ -4086,9 +4086,9 @@
}
},
"node_modules/normalize-package-data/node_modules/semver": {
- "version": "5.7.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
- "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+ "version": "5.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
+ "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
"dev": true,
"bin": {
"semver": "bin/semver"
@@ -10148,9 +10148,9 @@
},
"dependencies": {
"semver": {
- "version": "5.7.1",
- "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
- "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+ "version": "5.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
+ "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
"dev": true
}
}
diff --git a/src/registrar/admin.py b/src/registrar/admin.py
index 27a20a35f..6f5846bc5 100644
--- a/src/registrar/admin.py
+++ b/src/registrar/admin.py
@@ -1,16 +1,16 @@
import logging
from django.contrib import admin, messages
-from django.contrib.auth.admin import UserAdmin
+from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.contenttypes.models import ContentType
from django.http.response import HttpResponseRedirect
from django.urls import reverse
+from registrar.models.utility.admin_sort_fields import AdminSortFields
from . import models
logger = logging.getLogger(__name__)
-class AuditedAdmin(admin.ModelAdmin):
-
+class AuditedAdmin(admin.ModelAdmin, AdminSortFields):
"""Custom admin to make auditing easier."""
def history_view(self, request, object_id, extra_context=None):
@@ -23,9 +23,13 @@ class AuditedAdmin(admin.ModelAdmin):
)
)
+ def formfield_for_foreignkey(self, db_field, request, **kwargs):
+ """Used to sort dropdown fields alphabetically but can be expanded upon"""
+ form_field = super().formfield_for_foreignkey(db_field, request, **kwargs)
+ return self.form_field_order_helper(form_field, db_field)
+
class ListHeaderAdmin(AuditedAdmin):
-
"""Custom admin to add a descriptive subheader to list views."""
def changelist_view(self, request, extra_context=None):
@@ -93,12 +97,29 @@ class UserContactInline(admin.StackedInline):
model = models.Contact
-class MyUserAdmin(UserAdmin):
+class MyUserAdmin(BaseUserAdmin):
"""Custom user admin class to use our inlines."""
inlines = [UserContactInline]
+ def get_list_display(self, request):
+ if not request.user.is_superuser:
+ # Customize the list display for staff users
+ return ("email", "first_name", "last_name", "is_staff", "is_superuser")
+
+ # Use the default list display for non-staff users
+ return super().get_list_display(request)
+
+ def get_fieldsets(self, request, obj=None):
+ if not request.user.is_superuser:
+ # If the user doesn't have permission to change the model,
+ # show a read-only fieldset
+ return ((None, {"fields": []}),)
+
+ # If the user has permission to change the model, show all fields
+ return super().get_fieldsets(request, obj)
+
class HostIPInline(admin.StackedInline):
@@ -224,7 +245,6 @@ class DomainAdmin(ListHeaderAdmin):
class ContactAdmin(ListHeaderAdmin):
-
"""Custom contact admin class to add search."""
search_fields = ["email", "first_name", "last_name"]
@@ -336,18 +356,27 @@ class DomainApplicationAdmin(ListHeaderAdmin):
pass
elif obj.status == models.DomainApplication.SUBMITTED:
# This is an fsm in model which will throw an error if the
- # transition condition is violated, so we call it on the
- # original object which has the right status value, and pass
- # the updated object which contains the up-to-date data
- # for the side effects (like an email send). Same
- # comment applies to original_obj method calls below.
- original_obj.submit(updated_domain_application=obj)
- elif obj.status == models.DomainApplication.INVESTIGATING:
- original_obj.in_review(updated_domain_application=obj)
+ # transition condition is violated, so we roll back the
+ # status to what it was before the admin user changed it and
+ # let the fsm method set it. Same comment applies to
+ # transition method calls below.
+ obj.status = original_obj.status
+ obj.submit()
+ elif obj.status == models.DomainApplication.IN_REVIEW:
+ obj.status = original_obj.status
+ obj.in_review()
+ elif obj.status == models.DomainApplication.ACTION_NEEDED:
+ obj.status = original_obj.status
+ obj.action_needed()
elif obj.status == models.DomainApplication.APPROVED:
- original_obj.approve(updated_domain_application=obj)
+ obj.status = original_obj.status
+ obj.approve()
elif obj.status == models.DomainApplication.WITHDRAWN:
- original_obj.withdraw()
+ obj.status = original_obj.status
+ obj.withdraw()
+ elif obj.status == models.DomainApplication.REJECTED:
+ obj.status = original_obj.status
+ obj.reject()
else:
logger.warning("Unknown status selected in django admin")
diff --git a/src/registrar/assets/sass/_theme/_admin.scss b/src/registrar/assets/sass/_theme/_admin.scss
new file mode 100644
index 000000000..b87257344
--- /dev/null
+++ b/src/registrar/assets/sass/_theme/_admin.scss
@@ -0,0 +1,172 @@
+@use "cisa_colors" as *;
+@use "uswds-core" as *;
+
+// We'll use Django's CSS vars: https://docs.djangoproject.com/en/4.2/ref/contrib/admin/#theming-support
+// and assign USWDS theme vars whenever possible
+// If needed (see below), we'll use the USWDS hex value
+// As a last resort, we'll use CISA colors to supplement the palette
+:root,
+html[data-theme="light"] {
+ --primary: #{$theme-color-primary};
+ --secondary: #{$theme-color-primary-darkest};
+ --accent: #{$theme-color-accent-cool};
+ // --primary-fg: #fff;
+
+ // USWDS theme vars that are set to a token, such as #{$theme-color-base-darker}
+ // would interpolate to 'gray-cool-70' and output invalid CSS, so we use the hex
+ // source value instead: https://designsystem.digital.gov/design-tokens/color/system-tokens/
+ --body-fg: #3d4551;
+ // --body-bg: #fff;
+ --body-quiet-color: #{$theme-color-base-dark};
+ // --body-loud-color: #000;
+
+ --header-color: var( --primary-fg);
+ --header-branding-color: var( --primary-fg);
+ // --header-bg: var(--secondary);
+ // --header-link-color: var(--primary-fg);
+
+ --breadcrumbs-fg: #{$theme-color-accent-cool-lightest};
+ // --breadcrumbs-link-fg: var(--body-bg);
+ --breadcrumbs-bg: #{$theme-color-primary-dark};
+
+ // #{$theme-link-color} would interpolate to 'primary', so we use the source value instead
+ --link-fg: #{$theme-color-primary};
+ --link-hover-color: #{$theme-color-primary-darker};
+ // $theme-link-visited-color - violet-70v
+ --link-selected-fg: #54278f;
+
+ --hairline-color: #{$dhs-gray-15};
+ // $theme-color-base-lightest - gray-5
+ --border-color: #f0f0f0;
+
+ --error-fg: #{$theme-color-error};
+
+ --message-success-bg: #{$theme-color-success-lighter};
+ // $theme-color-warning-lighter - yellow-5
+ --message-warning-bg: #faf3d1;
+ --message-error-bg: #{$theme-color-error-lighter};
+
+ --darkened-bg: #{$dhs-gray-15}; /* A bit darker than --body-bg */
+ --selected-bg: var(--border-color); /* E.g. selected table cells */
+ --selected-row: var(--message-warning-bg);
+
+ // --button-fg: #fff;
+ // --button-bg: var(--secondary);
+ --button-hover-bg: #{$theme-color-primary-darker};
+ --default-button-bg: #{$theme-color-primary-dark};
+ --default-button-hover-bg: #{$theme-color-primary-darkest};
+ // #{$theme-color-base} - 'gray-cool-50'
+ --close-button-bg: #71767a;
+ // #{$theme-color-base-darker} - 'gray-cool-70'
+ --close-button-hover-bg: #3d4551;
+ --delete-button-bg: #{$theme-color-error};
+ --delete-button-hover-bg: #{$theme-color-error-dark};
+
+ // --object-tools-fg: var(--button-fg);
+ // --object-tools-bg: var(--close-button-bg);
+ // --object-tools-hover-bg: var(--close-button-hover-bg);
+}
+
+// Fold dark theme settings into our main CSS
+// https://docs.djangoproject.com/en/4.2/ref/contrib/admin/#theming-support > dark theme note
+@media (prefers-color-scheme: dark) {
+ :root,
+ html[data-theme="dark"] {
+ // Edit the primary to meet accessibility requ.
+ --primary: #23485a;
+ --primary-fg: #f7f7f7;
+
+ --body-fg: #eeeeee;
+ --body-bg: #121212;
+ --body-quiet-color: #e0e0e0;
+ --body-loud-color: #ffffff;
+
+ --breadcrumbs-link-fg: #e0e0e0;
+ --breadcrumbs-bg: var(--primary);
+
+ --link-fg: #81d4fa;
+ --link-hover-color: #4ac1f7;
+ --link-selected-fg: #6f94c6;
+
+ --hairline-color: #272727;
+ --border-color: #353535;
+
+ --error-fg: #e35f5f;
+ --message-success-bg: #006b1b;
+ --message-warning-bg: #583305;
+ --message-error-bg: #570808;
+
+ --darkened-bg: #212121;
+ --selected-bg: #1b1b1b;
+ --selected-row: #00363a;
+
+ --close-button-bg: #333333;
+ --close-button-hover-bg: #666666;
+ }
+
+ // Dark mode django (bug due to scss cascade) and USWDS tables
+ .change-list .usa-table,
+ .change-list .usa-table--striped tbody tr:nth-child(odd) td,
+ .change-list .usa-table--borderless thead th,
+ .change-list .usa-table thead td,
+ .change-list .usa-table thead th,
+ body.dashboard,
+ body.change-list,
+ body.change-form {
+ color: var(--body-fg);
+ }
+}
+
+// Firefox needs this to be specifically set
+html[data-theme="dark"] {
+ .change-list .usa-table,
+ .change-list .usa-table--striped tbody tr:nth-child(odd) td,
+ .change-list .usa-table--borderless thead th,
+ .change-list .usa-table thead td,
+ .change-list .usa-table thead th,
+ body.dashboard,
+ body.change-list,
+ body.change-form {
+ color: var(--body-fg);
+ }
+}
+
+#branding h1 a:link, #branding h1 a:visited {
+ color: var(--primary-fg);
+}
+
+#branding h1,
+h1, h2, h3 {
+ font-weight: font-weight('bold');
+}
+
+table > caption > a {
+ font-weight: font-weight('bold');
+ text-transform: none;
+}
+
+.change-list {
+ .usa-table--striped tbody tr:nth-child(odd) td,
+ .usa-table--striped tbody tr:nth-child(odd) th,
+ .usa-table td,
+ .usa-table th {
+ background-color: transparent;
+ }
+}
+
+#nav-sidebar {
+ padding-top: 20px;
+}
+
+// 'Delete button' layout bug
+.submit-row a.deletelink {
+ height: auto!important;
+}
+
+// Keep th from collapsing
+.min-width-25 {
+ min-width: 25px;
+}
+.min-width-81 {
+ min-width: 81px;
+}
diff --git a/src/registrar/assets/sass/_theme/cisa_colors.scss b/src/registrar/assets/sass/_theme/_cisa_colors.scss
similarity index 100%
rename from src/registrar/assets/sass/_theme/cisa_colors.scss
rename to src/registrar/assets/sass/_theme/_cisa_colors.scss
diff --git a/src/registrar/assets/sass/_theme/styles.scss b/src/registrar/assets/sass/_theme/styles.scss
index aef29a7f5..27d844760 100644
--- a/src/registrar/assets/sass/_theme/styles.scss
+++ b/src/registrar/assets/sass/_theme/styles.scss
@@ -9,3 +9,7 @@
/*--------------------------------------------------
--- Custom Styles ---------------------------------*/
@forward "uswds-theme-custom-styles";
+
+/*--------------------------------------------------
+--- Admin ---------------------------------*/
+@forward "admin";
diff --git a/src/registrar/config/settings.py b/src/registrar/config/settings.py
index 90918c929..8b53fa82a 100644
--- a/src/registrar/config/settings.py
+++ b/src/registrar/config/settings.py
@@ -571,12 +571,16 @@ SECURE_SSL_REDIRECT = True
ALLOWED_HOSTS = [
"getgov-stable.app.cloud.gov",
"getgov-staging.app.cloud.gov",
+ "getgov-nl.app.cloud.gov",
+ "getgov-rh.app.cloud.gov",
+ "getgov-za.app.cloud.gov",
"getgov-gd.app.cloud.gov",
"getgov-rb.app.cloud.gov",
"getgov-ko.app.cloud.gov",
"getgov-ab.app.cloud.gov",
"getgov-bl.app.cloud.gov",
"getgov-rjm.app.cloud.gov",
+ "getgov-dk.app.cloud.gov",
"get.gov",
]
diff --git a/src/registrar/config/urls.py b/src/registrar/config/urls.py
index c21d0206c..0f136c932 100644
--- a/src/registrar/config/urls.py
+++ b/src/registrar/config/urls.py
@@ -4,7 +4,6 @@ For more information see:
https://docs.djangoproject.com/en/4.0/topics/http/urls/
"""
-from django.conf import settings
from django.contrib import admin
from django.urls import include, path
from django.views.generic import RedirectView
@@ -45,6 +44,10 @@ for step, view in [
urlpatterns = [
path("", views.index, name="home"),
+ path(
+ "admin/logout/",
+ RedirectView.as_view(pattern_name="logout", permanent=False),
+ ),
path("admin/", admin.site.urls),
path(
"application//edit/",
@@ -114,20 +117,6 @@ urlpatterns = [
),
]
-
-if not settings.DEBUG:
- urlpatterns += [
- # redirect to login.gov
- path(
- "admin/login/", RedirectView.as_view(pattern_name="login", permanent=False)
- ),
- # redirect to login.gov
- path(
- "admin/logout/",
- RedirectView.as_view(pattern_name="logout", permanent=False),
- ),
- ]
-
# we normally would guard these with `if settings.DEBUG` but tests run with
# DEBUG = False even when these apps have been loaded because settings.DEBUG
# was actually True. Instead, let's add these URLs any time we are able to
diff --git a/src/registrar/fixtures.py b/src/registrar/fixtures.py
index b47ed4aef..0b1b8926d 100644
--- a/src/registrar/fixtures.py
+++ b/src/registrar/fixtures.py
@@ -57,6 +57,21 @@ class UserFixture:
"first_name": "Ryan",
"last_name": "Brooks",
},
+ {
+ "username": "30001ee7-0467-4df2-8db2-786e79606060",
+ "first_name": "Zander",
+ "last_name": "Adkinson",
+ },
+ {
+ "username": "bb21f687-c773-4df3-9243-111cfd4c0be4",
+ "first_name": "Paul",
+ "last_name": "Kuykendall",
+ },
+ {
+ "username": "2a88a97b-be96-4aad-b99e-0b605b492c78",
+ "first_name": "Rebecca",
+ "last_name": "Hsieh",
+ },
]
STAFF = [
@@ -64,12 +79,28 @@ class UserFixture:
"username": "319c490d-453b-43d9-bc4d-7d6cd8ff6844",
"first_name": "Rachid-Analyst",
"last_name": "Mrad-Analyst",
+ "email": "rachid.mrad@gmail.com",
},
{
"username": "b6a15987-5c88-4e26-8de2-ca71a0bdb2cd",
"first_name": "Alysia-Analyst",
"last_name": "Alysia-Analyst",
},
+ {
+ "username": "2cc0cde8-8313-4a50-99d8-5882e71443e8",
+ "first_name": "Zander-Analyst",
+ "last_name": "Adkinson-Analyst",
+ },
+ {
+ "username": "57ab5847-7789-49fe-a2f9-21d38076d699",
+ "first_name": "Paul-Analyst",
+ "last_name": "Kuykendall-Analyst",
+ },
+ {
+ "username": "e474e7a9-71ca-449d-833c-8a6e094dd117",
+ "first_name": "Rebecca-Analyst",
+ "last_name": "Hsieh-Analyst",
+ },
]
STAFF_PERMISSIONS = [
@@ -85,6 +116,7 @@ class UserFixture:
"permissions": ["change_domainapplication"],
},
{"app_label": "registrar", "model": "domain", "permissions": ["view_domain"]},
+ {"app_label": "registrar", "model": "user", "permissions": ["view_user"]},
]
@classmethod
@@ -98,6 +130,8 @@ class UserFixture:
user.is_superuser = True
user.first_name = admin["first_name"]
user.last_name = admin["last_name"]
+ if "email" in admin.keys():
+ user.email = admin["email"]
user.is_staff = True
user.is_active = True
user.save()
@@ -115,6 +149,8 @@ class UserFixture:
user.is_superuser = False
user.first_name = staff["first_name"]
user.last_name = staff["last_name"]
+ if "email" in admin.keys():
+ user.email = admin["email"]
user.is_staff = True
user.is_active = True
@@ -201,11 +237,11 @@ class DomainApplicationFixture:
"organization_name": "Example - Submitted but pending Investigation",
},
{
- "status": "investigating",
+ "status": "in review",
"organization_name": "Example - In Investigation",
},
{
- "status": "investigating",
+ "status": "in review",
"organization_name": "Example - Approved",
},
{
@@ -377,9 +413,9 @@ class DomainFixture(DomainApplicationFixture):
return
for user in users:
- # approve one of each users investigating status domains
+ # approve one of each users in review status domains
application = DomainApplication.objects.filter(
- creator=user, status=DomainApplication.INVESTIGATING
+ creator=user, status=DomainApplication.IN_REVIEW
).last()
logger.debug(f"Approving {application} for {user}")
application.approve()
diff --git a/src/registrar/migrations/0028_alter_domainapplication_status.py b/src/registrar/migrations/0028_alter_domainapplication_status.py
new file mode 100644
index 000000000..61b1c0505
--- /dev/null
+++ b/src/registrar/migrations/0028_alter_domainapplication_status.py
@@ -0,0 +1,32 @@
+# Generated by Django 4.2.2 on 2023-07-12 21:31
+# Generated by Django 4.2.2 on 2023-07-13 17:56
+# hand merged
+
+from django.db import migrations
+import django_fsm
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("registrar", "0027_alter_domaininformation_address_line1_and_more"),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name="domainapplication",
+ name="status",
+ field=django_fsm.FSMField(
+ choices=[
+ ("started", "started"),
+ ("submitted", "submitted"),
+ ("in review", "in review"),
+ ("action needed", "action needed"),
+ ("approved", "approved"),
+ ("withdrawn", "withdrawn"),
+ ("rejected", "rejected"),
+ ],
+ default="started",
+ max_length=50,
+ ),
+ ),
+ ]
diff --git a/src/registrar/models/domain_application.py b/src/registrar/models/domain_application.py
index a50cb34b8..67f1ee5d9 100644
--- a/src/registrar/models/domain_application.py
+++ b/src/registrar/models/domain_application.py
@@ -18,18 +18,22 @@ class DomainApplication(TimeStampedModel):
"""A registrant's application for a new domain."""
- # #### Contants for choice fields ####
+ # #### Constants for choice fields ####
STARTED = "started"
SUBMITTED = "submitted"
- INVESTIGATING = "investigating"
+ IN_REVIEW = "in review"
+ ACTION_NEEDED = "action needed"
APPROVED = "approved"
WITHDRAWN = "withdrawn"
+ REJECTED = "rejected"
STATUS_CHOICES = [
(STARTED, STARTED),
(SUBMITTED, SUBMITTED),
- (INVESTIGATING, INVESTIGATING),
+ (IN_REVIEW, IN_REVIEW),
+ (ACTION_NEEDED, ACTION_NEEDED),
(APPROVED, APPROVED),
(WITHDRAWN, WITHDRAWN),
+ (REJECTED, REJECTED),
]
class StateTerritoryChoices(models.TextChoices):
@@ -497,16 +501,13 @@ class DomainApplication(TimeStampedModel):
except EmailSendingError:
logger.warning("Failed to send confirmation email", exc_info=True)
- @transition(field="status", source=[STARTED, WITHDRAWN], target=SUBMITTED)
- def submit(self, updated_domain_application=None):
+ @transition(
+ field="status", source=[STARTED, ACTION_NEEDED, WITHDRAWN], target=SUBMITTED
+ )
+ def submit(self):
"""Submit an application that is started.
- As a side effect, an email notification is sent.
-
- This method is called in admin.py on the original application
- which has the correct status value, but is passed the changed
- application which has the up-to-date data that we'll use
- in the email."""
+ As a side effect, an email notification is sent."""
# check our conditions here inside the `submit` method so that we
# can raise more informative exceptions
@@ -522,53 +523,46 @@ class DomainApplication(TimeStampedModel):
if not DraftDomain.string_could_be_domain(self.requested_domain.name):
raise ValueError("Requested domain is not a valid domain name.")
- if updated_domain_application is not None:
- # A DomainApplication is being passed to this method (ie from admin)
- updated_domain_application._send_status_update_email(
- "submission confirmation",
- "emails/submission_confirmation.txt",
- "emails/submission_confirmation_subject.txt",
- )
- else:
- # Or this method is called with the right application
- # for context, ie from views/application.py
- self._send_status_update_email(
- "submission confirmation",
- "emails/submission_confirmation.txt",
- "emails/submission_confirmation_subject.txt",
- )
+ self._send_status_update_email(
+ "submission confirmation",
+ "emails/submission_confirmation.txt",
+ "emails/submission_confirmation_subject.txt",
+ )
- @transition(field="status", source=SUBMITTED, target=INVESTIGATING)
- def in_review(self, updated_domain_application):
+ @transition(field="status", source=SUBMITTED, target=IN_REVIEW)
+ def in_review(self):
"""Investigate an application that has been submitted.
- As a side effect, an email notification is sent.
+ As a side effect, an email notification is sent."""
- This method is called in admin.py on the original application
- which has the correct status value, but is passed the changed
- application which has the up-to-date data that we'll use
- in the email."""
-
- updated_domain_application._send_status_update_email(
+ self._send_status_update_email(
"application in review",
"emails/status_change_in_review.txt",
"emails/status_change_in_review_subject.txt",
)
- @transition(field="status", source=[SUBMITTED, INVESTIGATING], target=APPROVED)
- def approve(self, updated_domain_application=None):
+ @transition(field="status", source=[IN_REVIEW, REJECTED], target=ACTION_NEEDED)
+ def action_needed(self):
+ """Send back an application that is under investigation or rejected.
+
+ As a side effect, an email notification is sent."""
+
+ self._send_status_update_email(
+ "action needed",
+ "emails/status_change_action_needed.txt",
+ "emails/status_change_action_needed_subject.txt",
+ )
+
+ @transition(
+ field="status", source=[SUBMITTED, IN_REVIEW, REJECTED], target=APPROVED
+ )
+ def approve(self):
"""Approve an application that has been submitted.
This has substantial side-effects because it creates another database
object for the approved Domain and makes the user who created the
application into an admin on that domain. It also triggers an email
- notification.
-
- This method is called in admin.py on the original application
- which has the correct status value, but is passed the changed
- application which has the up-to-date data that we'll use
- in the email.
- """
+ notification."""
# create the domain
Domain = apps.get_model("registrar.Domain")
@@ -587,24 +581,28 @@ class DomainApplication(TimeStampedModel):
user=self.creator, domain=created_domain, role=UserDomainRole.Roles.ADMIN
)
- if updated_domain_application is not None:
- # A DomainApplication is being passed to this method (ie from admin)
- updated_domain_application._send_status_update_email(
- "application approved",
- "emails/status_change_approved.txt",
- "emails/status_change_approved_subject.txt",
- )
- else:
- self._send_status_update_email(
- "application approved",
- "emails/status_change_approved.txt",
- "emails/status_change_approved_subject.txt",
- )
+ self._send_status_update_email(
+ "application approved",
+ "emails/status_change_approved.txt",
+ "emails/status_change_approved_subject.txt",
+ )
- @transition(field="status", source=[SUBMITTED, INVESTIGATING], target=WITHDRAWN)
+ @transition(field="status", source=[SUBMITTED, IN_REVIEW], target=WITHDRAWN)
def withdraw(self):
"""Withdraw an application that has been submitted."""
+ @transition(field="status", source=[IN_REVIEW, APPROVED], target=REJECTED)
+ def reject(self):
+ """Reject an application that has been submitted.
+
+ As a side effect, an email notification is sent, similar to in_review"""
+
+ self._send_status_update_email(
+ "action needed",
+ "emails/status_change_rejected.txt",
+ "emails/status_change_rejected_subject.txt",
+ )
+
# ## Form policies ###
#
# These methods control what questions need to be answered by applicants
diff --git a/src/registrar/models/utility/admin_form_order_helper.py b/src/registrar/models/utility/admin_form_order_helper.py
new file mode 100644
index 000000000..acc26db11
--- /dev/null
+++ b/src/registrar/models/utility/admin_form_order_helper.py
@@ -0,0 +1,63 @@
+import logging
+from typing import Dict
+from django.forms import ModelChoiceField
+
+logger = logging.getLogger(__name__)
+
+
+class SortingDict:
+ """Stores a sorting dictionary object"""
+
+ _sorting_dict: Dict[type, type] = {}
+
+ def __init__(self, model_list, sort_list):
+ self._sorting_dict = {
+ "dropDownSelected": self.convert_list_to_dict(model_list),
+ "sortBy": sort_list,
+ }
+
+ # Used in __init__ for model_list for performance reasons
+ def convert_list_to_dict(self, value_list):
+ """Used internally to convert model_list to a dictionary"""
+ return {item: item for item in value_list}
+
+ def get_dict(self):
+ """Grabs the associated dictionary item,
+ has two fields: 'dropDownSelected': model_list and 'sortBy': sort_list"""
+ # This should never happen so we need to log this
+ if self._sorting_dict is None:
+ raise ValueError("_sorting_dict was None")
+ return self._sorting_dict
+
+
+class AdminFormOrderHelper:
+ """A helper class to order a dropdown field in Django Admin,
+ takes the fields you want to order by as an array"""
+
+ # Used to keep track of how we want to order_by certain FKs
+ _sorting_list: list[SortingDict] = []
+
+ def __init__(self, sort: list[SortingDict]):
+ self._sorting_list = sort
+
+ def get_ordered_form_field(self, form_field, db_field) -> ModelChoiceField | None:
+ """Orders the queryset for a ModelChoiceField
+ based on the order_by_dict dictionary"""
+ _order_by_list = []
+
+ for item in self._sorting_list:
+ item_dict = item.get_dict()
+ drop_down_selected = item_dict.get("dropDownSelected")
+ sort_by = item_dict.get("sortBy")
+
+ if db_field.name in drop_down_selected:
+ _order_by_list = sort_by
+ # Exit loop when order_by_list is found
+ break
+
+ # Only order if we choose to do so
+ # noqa for the linter... reduces readability otherwise
+ if _order_by_list is not None and _order_by_list != []: # noqa
+ form_field.queryset = form_field.queryset.order_by(*_order_by_list)
+
+ return form_field
diff --git a/src/registrar/models/utility/admin_sort_fields.py b/src/registrar/models/utility/admin_sort_fields.py
new file mode 100644
index 000000000..8037c6df0
--- /dev/null
+++ b/src/registrar/models/utility/admin_sort_fields.py
@@ -0,0 +1,27 @@
+from registrar.models.utility.admin_form_order_helper import (
+ AdminFormOrderHelper,
+ SortingDict,
+)
+
+
+class AdminSortFields:
+ # Used to keep track of how we want to order_by certain FKs
+ foreignkey_orderby_dict: list[SortingDict] = [
+ # foreign_key - order_by
+ # Handles fields that are sorted by 'first_name / last_name
+ SortingDict(
+ ["submitter", "authorizing_official", "investigator", "creator", "user"],
+ ["first_name", "last_name"],
+ ),
+ # Handles fields that are sorted by 'name'
+ SortingDict(["domain", "requested_domain"], ["name"]),
+ SortingDict(["domain_application"], ["requested_domain__name"]),
+ ]
+
+ # For readability purposes, but can be replaced with a one liner
+ def form_field_order_helper(self, form_field, db_field):
+ """A shorthand for AdminFormOrderHelper(foreignkey_orderby_dict)
+ .get_ordered_form_field(form_field, db_field)"""
+
+ form = AdminFormOrderHelper(self.foreignkey_orderby_dict)
+ return form.get_ordered_form_field(form_field, db_field)
diff --git a/src/registrar/templates/admin/app_list.html b/src/registrar/templates/admin/app_list.html
new file mode 100644
index 000000000..fb5934470
--- /dev/null
+++ b/src/registrar/templates/admin/app_list.html
@@ -0,0 +1,56 @@
+{% load i18n %}
+
+{% if app_list %}
+ {% for app in app_list %}
+
+
+
+
+ {% comment %}
+ .gov - hardcode the row checkboxes using the custom filters to extract
+ the value attribute's value, and a label based on the anchor elements's
+ text. Then edit the for loop to keep django from generating the row select
+ checkboxes.
+ {% endcomment %}
+
+ {% for result in results %}
+ {% if result.form.non_field_errors %}
+
{{ result.form.non_field_errors }}
+ {% endif %}
+
+
+ {% with result_value=result.0|extract_value %}
+ {% with result_label=result.1|extract_a_text %}
+
{% endfor %}
+
+
+
+
+
+ {% for result in results %}
+ {% if result.form.non_field_errors %}
+
{{ result.form.non_field_errors }}
+ {% endif %}
+
{% for item in result %}{{ item }}{% endfor %}
+ {% endfor %}
+
+{% endif %}
+
+
+
+
+{% endif %}
\ No newline at end of file
diff --git a/src/registrar/templates/application_authorizing_official.html b/src/registrar/templates/application_authorizing_official.html
index dead1974f..2ac99be80 100644
--- a/src/registrar/templates/application_authorizing_official.html
+++ b/src/registrar/templates/application_authorizing_official.html
@@ -6,13 +6,13 @@
Who is the authorizing official for your organization?
-
Your authorizing official is the person within your organization who can authorize your domain request. This is generally the highest-ranking or highest-elected official in your organization.
+
Your authorizing official is the person within your organization who can authorize your domain request. This person must be in a role of significant, executive responsibility within the organization.
{% include "includes/ao_example.html" %}
-
We might contact your authorizing official, or their office, to double check that they approve this request. Read more about who can serve as an authorizing official.
+
We typically don’t reach out to the authorizing official, but if contact is necessary, our practice is to coordinate first with you, the requestor. Read more about who can serve as an authorizing official.
{% endblock %}
diff --git a/src/registrar/templates/application_org_election.html b/src/registrar/templates/application_org_election.html
index d2432b79d..45776af5c 100644
--- a/src/registrar/templates/application_org_election.html
+++ b/src/registrar/templates/application_org_election.html
@@ -6,7 +6,10 @@
Is your organization an election office? *
-
Answer “yes” if the primary purpose of your organization is to manage elections.
+
An election office is a government entity whose primary responsibility is overseeing elections and/or conducting voter registration.
+
+
Answer “yes” only if the main purpose of your organization is to serve as an election office.
We’d like to contact other employees in your organization about your domain request. For example, they could be involved in managing your organization or its technical infrastructure. This information will help us assess your eligibility for a .gov domain. These contacts should be in addition to you and your authorizing official. They should be employees of your organization.
+
To help us assess your eligibility for a .gov domain, please provide contact information for other employees from your organization.
+
+
They should be clearly and publicly affiliated with your organization and familiar with your domain request.
+
They don't need to be involved with the technical management of your domain (although they can be).
+
We typically don’t reach out to these employees, but if contact is necessary, our practice is to coordinate first with you, the requestor.
+
+
{% endblock %}
diff --git a/src/registrar/templates/application_status.html b/src/registrar/templates/application_status.html
index 3d0f44a9f..99f6a1d4c 100644
--- a/src/registrar/templates/application_status.html
+++ b/src/registrar/templates/application_status.html
@@ -20,8 +20,9 @@
Status:
{% if domainapplication.status == 'approved' %} Approved
- {% elif domainapplication.status == 'investigating' %} In Review
- {% elif domainapplication.status == 'submitted' %} Received
+ {% elif domainapplication.status == 'in review' %} In Review
+ {% elif domainapplication.status == 'rejected' %} Rejected
+ {% elif domainapplication.status == 'submitted' %} Submitted
{% else %}ERROR Please contact technical support/dev
{% endif %}
diff --git a/src/registrar/templates/application_tribal_government.html b/src/registrar/templates/application_tribal_government.html
index 83fbc98ff..3c2a8999f 100644
--- a/src/registrar/templates/application_tribal_government.html
+++ b/src/registrar/templates/application_tribal_government.html
@@ -3,7 +3,17 @@
{% block form_fields %}
- {% input_with_errors forms.0.tribe_name %}
+
+ {% with sublabel_text="Please include the entire name of your tribe as recognized by the Bureau of Indian Affairs." %}
+ {% with link_text="Bureau of Indian Affairs" %}
+ {% with link_href="https://www.federalregister.gov/documents/2023/01/12/2023-00504/indian-entities-recognized-by-and-eligible-to-receive-services-from-the-united-states-bureau-of" %}
+ {% with target_blank="true" %}
+ {% input_with_errors forms.0.tribe_name %}
+ {% endwith %}
+ {% endwith %}
+ {% endwith %}
+ {% endwith %}
+