diff --git a/.github/workflows/deploy-sandbox.yaml b/.github/workflows/deploy-sandbox.yaml
index bc2b2d0b1..7c2a0fe6a 100644
--- a/.github/workflows/deploy-sandbox.yaml
+++ b/.github/workflows/deploy-sandbox.yaml
@@ -5,18 +5,18 @@ run-name: Build and deploy developer sandbox for branch ${{ github.head_ref }}
on:
pull_request:
- branches:
- - 'ik/**'
- - 'rjm/**'
- - 'jon/**'
- - 'sspj/**'
- - 'mr/**'
- - 'nmb/**'
- - 'ab/**'
- - 'bl/**'
jobs:
variables:
+ if: |
+ startsWith(github.head_ref, 'ik/')
+ || startsWith(github.head_ref, 'jon')
+ || startsWith(github.head_ref, 'sspj/')
+ || startsWith(github.head_ref, 'mr/')
+ || startsWith(github.head_ref, 'nmb/')
+ || startsWith(github.head_ref, 'ab/')
+ || startsWith(github.head_ref, 'bl/')
+ || startsWith(github.head_ref, 'rjm/')
outputs:
environment: ${{ steps.var.outputs.environment}}
runs-on: "ubuntu-latest"
@@ -40,7 +40,7 @@ jobs:
docker compose run node npx gulp compile
- name: Collect static assets
working-directory: ./src
- run: docker compose run app python manage.py collectstatic
+ run: docker compose run app python manage.py collectstatic --no-input
- name: Deploy to cloud.gov sandbox
uses: 18f/cg-deploy-action@main
env:
@@ -69,4 +69,4 @@ jobs:
owner: context.repo.owner,
repo: context.repo.repo,
body: '🥳 Successfully deployed to developer sandbox **[${{ env.ENVIRONMENT }}](https://getgov-${{ env.ENVIRONMENT }}.app.cloud.gov/)**.'
- })
\ No newline at end of file
+ })
diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
index 448711f48..6a7f0c324 100644
--- a/.github/workflows/test.yaml
+++ b/.github/workflows/test.yaml
@@ -32,6 +32,17 @@ jobs:
working-directory: ./src
run: docker compose run app python manage.py test
+ django-migrations-complete:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Check for complete migrations
+ working-directory: ./src
+ run: |
+ docker compose run app ./manage.py makemigrations --dry-run --verbosity 3 && \
+ docker compose run app ./manage.py makemigrations --check
+
pa11y-scan:
runs-on: ubuntu-20.04
steps:
@@ -53,5 +64,6 @@ jobs:
- name: run pa11y
working-directory: ./src
run: |
+ sleep 10;
npm i -g pa11y-ci
pa11y-ci
diff --git a/docs/developer/README.md b/docs/developer/README.md
index 91c6ca5ec..a5e1d312e 100644
--- a/docs/developer/README.md
+++ b/docs/developer/README.md
@@ -22,6 +22,24 @@ Visit the running application at [http://localhost:8080](http://localhost:8080).
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.
+## Merging and PRs
+
+History preservation and merge contexts are more important to us than a clean and linear history, so we will merge instead of rebasing.
+To bring your feature branch up-to-date wih main:
+
+```
+git checkout main
+git pull
+git checkout
+git merge orgin/main
+git push
+```
+
+Resources:
+- [https://frontend.turing.edu/lessons/module-3/merge-vs-rebase.html](https://frontend.turing.edu/lessons/module-3/merge-vs-rebase.html)
+- [https://www.atlassian.com/git/tutorials/merging-vs-rebasing](https://www.atlassian.com/git/tutorials/merging-vs-rebasing)
+- [https://www.simplilearn.com/git-rebase-vs-merge-article](https://www.simplilearn.com/git-rebase-vs-merge-article)
+
## Setting Vars
Non-secret environment variables for local development are set in [src/docker-compose.yml](../../src/docker-compose.yml).
@@ -41,6 +59,28 @@ cp ./.env-example .env
Get the secrets from Cloud.gov by running `cf env getgov-YOURSANDBOX`. More information is available in [rotate_application_secrets.md](../operations/runbooks/rotate_application_secrets.md).
+## Adding user to /admin
+
+The endpoint /admin can be used to view and manage site content, including but not limited to user information and the list of current applications in the database. To be able to view and use /admin locally:
+
+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:
+
+```
+ ADMINS = [
+ {
+ "username": "",
+ "first_name": "",
+ "last_name": "",
+ },
+ ...
+ ]
+```
+
+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.
+
## Viewing Logs
If you run via `docker-compose up`, you'll see the logs in your terminal.
diff --git a/docs/product/product_strategy.md b/docs/product/product_strategy.md
index 9b7bba301..1a17727d1 100644
--- a/docs/product/product_strategy.md
+++ b/docs/product/product_strategy.md
@@ -21,4 +21,4 @@ CISA lacks a scalable, efficient, and secure method of managing the .gov TLD pro
| **Growth and use:** Regular growth in the overall number of .gov domains registered, with clear increases in election orgs, major metro areas, and state legislatures/courts | - Raw count of registered .gov domains increases
- Number of YoY applications per month increases
- Percent of 100 most populous cities, counties, etc. (per Census data) using .gov domains increases |
| **Data:** The program maintains authoritative contacts at, metadata about, and hostname information for all registered .gov domains, and is able to track that .gov domains are actually used | - Time-to-generate internal reports decreases
- Results of periodic data quality audit show improvements month-over-month |
| **User satisfaction:** Getting a .gov domain is as easy and intuitive as possible | - Completion rate of form improves
- Time from domain request to approval decreases
- Number of domains requiring analyst data changes decreases |
-| **Program reputation and experience:** The .gov program is viewed as trustworthy and responsive | - Response time for inquiries decreases
- Resolution time decreases
- Rate of repeat issues for tickets decreases
- Number of SLTT organizations in CoP increases |
+| **Program reputation and experience:** The .gov program is viewed as trustworthy and responsive | - Response time for inquiries decreases
- Resolution time decreases
- Rate of repeat issues for tickets decreases
- Number of SLTT organizations in Community of Practice increases |
diff --git a/ops/manifests/manifest-ab.yaml b/ops/manifests/manifest-ab.yaml
index a08a7ef24..062ad335c 100644
--- a/ops/manifests/manifest-ab.yaml
+++ b/ops/manifests/manifest-ab.yaml
@@ -6,7 +6,7 @@ applications:
path: ../../src
instances: 1
memory: 512M
- stack: cflinuxfs3
+ stack: cflinuxfs4
timeout: 180
command: ./run.sh
health-check-type: http
diff --git a/ops/manifests/manifest-bl.yaml b/ops/manifests/manifest-bl.yaml
index f9d635c32..bb8723f2d 100644
--- a/ops/manifests/manifest-bl.yaml
+++ b/ops/manifests/manifest-bl.yaml
@@ -6,7 +6,7 @@ applications:
path: ../../src
instances: 1
memory: 512M
- stack: cflinuxfs3
+ stack: cflinuxfs4
timeout: 180
command: ./run.sh
health-check-type: http
diff --git a/ops/manifests/manifest-ik.yaml b/ops/manifests/manifest-ik.yaml
index 6de3d3905..2652b709a 100644
--- a/ops/manifests/manifest-ik.yaml
+++ b/ops/manifests/manifest-ik.yaml
@@ -6,7 +6,7 @@ applications:
path: ../../src
instances: 1
memory: 512M
- stack: cflinuxfs3
+ stack: cflinuxfs4
timeout: 180
command: ./run.sh
health-check-type: http
diff --git a/ops/manifests/manifest-jon.yaml b/ops/manifests/manifest-jon.yaml
index 44b530731..4b32235fa 100644
--- a/ops/manifests/manifest-jon.yaml
+++ b/ops/manifests/manifest-jon.yaml
@@ -6,7 +6,7 @@ applications:
path: ../../src
instances: 1
memory: 512M
- stack: cflinuxfs3
+ stack: cflinuxfs4
timeout: 180
command: ./run.sh
health-check-type: http
diff --git a/ops/manifests/manifest-mr.yaml b/ops/manifests/manifest-mr.yaml
index 3150d092d..4280f0558 100644
--- a/ops/manifests/manifest-mr.yaml
+++ b/ops/manifests/manifest-mr.yaml
@@ -6,7 +6,7 @@ applications:
path: ../../src
instances: 1
memory: 512M
- stack: cflinuxfs3
+ stack: cflinuxfs4
timeout: 180
command: ./run.sh
health-check-type: http
diff --git a/ops/manifests/manifest-nmb.yaml b/ops/manifests/manifest-nmb.yaml
index 6d8b806e4..8352d8ed1 100644
--- a/ops/manifests/manifest-nmb.yaml
+++ b/ops/manifests/manifest-nmb.yaml
@@ -6,7 +6,7 @@ applications:
path: ../../src
instances: 1
memory: 512M
- stack: cflinuxfs3
+ stack: cflinuxfs4
timeout: 180
command: ./run.sh
health-check-type: http
diff --git a/ops/manifests/manifest-rjm.yaml b/ops/manifests/manifest-rjm.yaml
index e8da5a62e..c5b51c3b5 100644
--- a/ops/manifests/manifest-rjm.yaml
+++ b/ops/manifests/manifest-rjm.yaml
@@ -6,7 +6,7 @@ applications:
path: ../../src
instances: 1
memory: 512M
- stack: cflinuxfs3
+ stack: cflinuxfs4
timeout: 180
command: ./run.sh
health-check-type: http
diff --git a/ops/manifests/manifest-sspj.yaml b/ops/manifests/manifest-sspj.yaml
index 2bbdcef59..db1c29a1b 100644
--- a/ops/manifests/manifest-sspj.yaml
+++ b/ops/manifests/manifest-sspj.yaml
@@ -6,7 +6,7 @@ applications:
path: ../../src
instances: 1
memory: 512M
- stack: cflinuxfs3
+ stack: cflinuxfs4
timeout: 180
command: ./run.sh
health-check-type: http
diff --git a/ops/manifests/manifest-stable.yaml b/ops/manifests/manifest-stable.yaml
index 70f394956..619e5fc7a 100644
--- a/ops/manifests/manifest-stable.yaml
+++ b/ops/manifests/manifest-stable.yaml
@@ -6,7 +6,7 @@ applications:
path: ../../src
instances: 1
memory: 512M
- stack: cflinuxfs3
+ stack: cflinuxfs4
timeout: 180
command: ./run.sh
health-check-type: http
diff --git a/ops/scripts/manifest-sandbox-template.yaml b/ops/scripts/manifest-sandbox-template.yaml
index aafc8e481..ba814b3d5 100644
--- a/ops/scripts/manifest-sandbox-template.yaml
+++ b/ops/scripts/manifest-sandbox-template.yaml
@@ -6,7 +6,7 @@ applications:
path: ../../src
instances: 1
memory: 512M
- stack: cflinuxfs3
+ stack: cflinuxfs4
timeout: 180
command: ./run.sh
health-check-type: http
diff --git a/src/.pa11yci b/src/.pa11yci
index 1c6e245a1..e641d58e4 100644
--- a/src/.pa11yci
+++ b/src/.pa11yci
@@ -1,7 +1,7 @@
{
"defaults": {
"concurrency": 1,
- "timeout": 10000,
+ "timeout": 30000,
"hideElements": "a[href='/whoami/']"
},
diff --git a/src/Pipfile.lock b/src/Pipfile.lock
index aedc30c8b..e77116ef5 100644
--- a/src/Pipfile.lock
+++ b/src/Pipfile.lock
@@ -24,19 +24,19 @@
},
"boto3": {
"hashes": [
- "sha256:9a0a29179957cb26fa8c3c1fddf66b18efaeaf633e08db5fb53815ffb0421419",
- "sha256:eb8cde24a4c5755c35126e8cd460e6b51c63d04292419e7e95721232720c7e5b"
+ "sha256:38ca632be379963f2a2749b5f63a81fe1679913b954914f470ad282c77674bbc",
+ "sha256:4d575c180312bec6108852bae12e6396b9d1bb404154d652c57ee849c62fbb83"
],
"index": "pypi",
- "version": "==1.26.69"
+ "version": "==1.26.122"
},
"botocore": {
"hashes": [
- "sha256:2a4ab8bcb3177daa425019e125c09996b9a6a1a62bb0baaaeeb86ffd552719cc",
- "sha256:7e1bebca013544fbc298cb58603bfccd5f71b49c720a5c33c07cf5dfc8145a1f"
+ "sha256:9e4984a9e9777c6b949aa1e98323fa35480d9f99d447af7e179ae611f7ed5af9",
+ "sha256:c3b41078d235761b9c5dc22f534a76952622ef96787b96bbd10242ec4d73f2a5"
],
"markers": "python_version >= '3.7'",
- "version": "==1.29.69"
+ "version": "==1.29.122"
},
"cachetools": {
"hashes": [
@@ -133,126 +133,109 @@
},
"charset-normalizer": {
"hashes": [
- "sha256:00d3ffdaafe92a5dc603cb9bd5111aaa36dfa187c8285c543be562e61b755f6b",
- "sha256:024e606be3ed92216e2b6952ed859d86b4cfa52cd5bc5f050e7dc28f9b43ec42",
- "sha256:0298eafff88c99982a4cf66ba2efa1128e4ddaca0b05eec4c456bbc7db691d8d",
- "sha256:02a51034802cbf38db3f89c66fb5d2ec57e6fe7ef2f4a44d070a593c3688667b",
- "sha256:083c8d17153ecb403e5e1eb76a7ef4babfc2c48d58899c98fcaa04833e7a2f9a",
- "sha256:0a11e971ed097d24c534c037d298ad32c6ce81a45736d31e0ff0ad37ab437d59",
- "sha256:0bf2dae5291758b6f84cf923bfaa285632816007db0330002fa1de38bfcb7154",
- "sha256:0c0a590235ccd933d9892c627dec5bc7511ce6ad6c1011fdf5b11363022746c1",
- "sha256:0f438ae3532723fb6ead77e7c604be7c8374094ef4ee2c5e03a3a17f1fca256c",
- "sha256:109487860ef6a328f3eec66f2bf78b0b72400280d8f8ea05f69c51644ba6521a",
- "sha256:11b53acf2411c3b09e6af37e4b9005cba376c872503c8f28218c7243582df45d",
- "sha256:12db3b2c533c23ab812c2b25934f60383361f8a376ae272665f8e48b88e8e1c6",
- "sha256:14e76c0f23218b8f46c4d87018ca2e441535aed3632ca134b10239dfb6dadd6b",
- "sha256:16a8663d6e281208d78806dbe14ee9903715361cf81f6d4309944e4d1e59ac5b",
- "sha256:292d5e8ba896bbfd6334b096e34bffb56161c81408d6d036a7dfa6929cff8783",
- "sha256:2c03cc56021a4bd59be889c2b9257dae13bf55041a3372d3295416f86b295fb5",
- "sha256:2e396d70bc4ef5325b72b593a72c8979999aa52fb8bcf03f701c1b03e1166918",
- "sha256:2edb64ee7bf1ed524a1da60cdcd2e1f6e2b4f66ef7c077680739f1641f62f555",
- "sha256:31a9ddf4718d10ae04d9b18801bd776693487cbb57d74cc3458a7673f6f34639",
- "sha256:356541bf4381fa35856dafa6a965916e54bed415ad8a24ee6de6e37deccf2786",
- "sha256:358a7c4cb8ba9b46c453b1dd8d9e431452d5249072e4f56cfda3149f6ab1405e",
- "sha256:37f8febc8ec50c14f3ec9637505f28e58d4f66752207ea177c1d67df25da5aed",
- "sha256:39049da0ffb96c8cbb65cbf5c5f3ca3168990adf3551bd1dee10c48fce8ae820",
- "sha256:39cf9ed17fe3b1bc81f33c9ceb6ce67683ee7526e65fde1447c772afc54a1bb8",
- "sha256:3ae1de54a77dc0d6d5fcf623290af4266412a7c4be0b1ff7444394f03f5c54e3",
- "sha256:3b590df687e3c5ee0deef9fc8c547d81986d9a1b56073d82de008744452d6541",
- "sha256:3e45867f1f2ab0711d60c6c71746ac53537f1684baa699f4f668d4c6f6ce8e14",
- "sha256:3fc1c4a2ffd64890aebdb3f97e1278b0cc72579a08ca4de8cd2c04799a3a22be",
- "sha256:4457ea6774b5611f4bed5eaa5df55f70abde42364d498c5134b7ef4c6958e20e",
- "sha256:44ba614de5361b3e5278e1241fda3dc1838deed864b50a10d7ce92983797fa76",
- "sha256:4a8fcf28c05c1f6d7e177a9a46a1c52798bfe2ad80681d275b10dcf317deaf0b",
- "sha256:4b0d02d7102dd0f997580b51edc4cebcf2ab6397a7edf89f1c73b586c614272c",
- "sha256:502218f52498a36d6bf5ea77081844017bf7982cdbe521ad85e64cabee1b608b",
- "sha256:503e65837c71b875ecdd733877d852adbc465bd82c768a067badd953bf1bc5a3",
- "sha256:5995f0164fa7df59db4746112fec3f49c461dd6b31b841873443bdb077c13cfc",
- "sha256:59e5686dd847347e55dffcc191a96622f016bc0ad89105e24c14e0d6305acbc6",
- "sha256:601f36512f9e28f029d9481bdaf8e89e5148ac5d89cffd3b05cd533eeb423b59",
- "sha256:608862a7bf6957f2333fc54ab4399e405baad0163dc9f8d99cb236816db169d4",
- "sha256:62595ab75873d50d57323a91dd03e6966eb79c41fa834b7a1661ed043b2d404d",
- "sha256:70990b9c51340e4044cfc394a81f614f3f90d41397104d226f21e66de668730d",
- "sha256:71140351489970dfe5e60fc621ada3e0f41104a5eddaca47a7acb3c1b851d6d3",
- "sha256:72966d1b297c741541ca8cf1223ff262a6febe52481af742036a0b296e35fa5a",
- "sha256:74292fc76c905c0ef095fe11e188a32ebd03bc38f3f3e9bcb85e4e6db177b7ea",
- "sha256:761e8904c07ad053d285670f36dd94e1b6ab7f16ce62b9805c475b7aa1cffde6",
- "sha256:772b87914ff1152b92a197ef4ea40efe27a378606c39446ded52c8f80f79702e",
- "sha256:79909e27e8e4fcc9db4addea88aa63f6423ebb171db091fb4373e3312cb6d603",
- "sha256:7e189e2e1d3ed2f4aebabd2d5b0f931e883676e51c7624826e0a4e5fe8a0bf24",
- "sha256:7eb33a30d75562222b64f569c642ff3dc6689e09adda43a082208397f016c39a",
- "sha256:81d6741ab457d14fdedc215516665050f3822d3e56508921cc7239f8c8e66a58",
- "sha256:8499ca8f4502af841f68135133d8258f7b32a53a1d594aa98cc52013fff55678",
- "sha256:84c3990934bae40ea69a82034912ffe5a62c60bbf6ec5bc9691419641d7d5c9a",
- "sha256:87701167f2a5c930b403e9756fab1d31d4d4da52856143b609e30a1ce7160f3c",
- "sha256:88600c72ef7587fe1708fd242b385b6ed4b8904976d5da0893e31df8b3480cb6",
- "sha256:8ac7b6a045b814cf0c47f3623d21ebd88b3e8cf216a14790b455ea7ff0135d18",
- "sha256:8b8af03d2e37866d023ad0ddea594edefc31e827fee64f8de5611a1dbc373174",
- "sha256:8c7fe7afa480e3e82eed58e0ca89f751cd14d767638e2550c77a92a9e749c317",
- "sha256:8eade758719add78ec36dc13201483f8e9b5d940329285edcd5f70c0a9edbd7f",
- "sha256:911d8a40b2bef5b8bbae2e36a0b103f142ac53557ab421dc16ac4aafee6f53dc",
- "sha256:93ad6d87ac18e2a90b0fe89df7c65263b9a99a0eb98f0a3d2e079f12a0735837",
- "sha256:95dea361dd73757c6f1c0a1480ac499952c16ac83f7f5f4f84f0658a01b8ef41",
- "sha256:9ab77acb98eba3fd2a85cd160851816bfce6871d944d885febf012713f06659c",
- "sha256:9cb3032517f1627cc012dbc80a8ec976ae76d93ea2b5feaa9d2a5b8882597579",
- "sha256:9cf4e8ad252f7c38dd1f676b46514f92dc0ebeb0db5552f5f403509705e24753",
- "sha256:9d9153257a3f70d5f69edf2325357251ed20f772b12e593f3b3377b5f78e7ef8",
- "sha256:a152f5f33d64a6be73f1d30c9cc82dfc73cec6477ec268e7c6e4c7d23c2d2291",
- "sha256:a16418ecf1329f71df119e8a65f3aa68004a3f9383821edcb20f0702934d8087",
- "sha256:a60332922359f920193b1d4826953c507a877b523b2395ad7bc716ddd386d866",
- "sha256:a8d0fc946c784ff7f7c3742310cc8a57c5c6dc31631269876a88b809dbeff3d3",
- "sha256:ab5de034a886f616a5668aa5d098af2b5385ed70142090e2a31bcbd0af0fdb3d",
- "sha256:c22d3fe05ce11d3671297dc8973267daa0f938b93ec716e12e0f6dee81591dc1",
- "sha256:c2ac1b08635a8cd4e0cbeaf6f5e922085908d48eb05d44c5ae9eabab148512ca",
- "sha256:c512accbd6ff0270939b9ac214b84fb5ada5f0409c44298361b2f5e13f9aed9e",
- "sha256:c75ffc45f25324e68ab238cb4b5c0a38cd1c3d7f1fb1f72b5541de469e2247db",
- "sha256:c95a03c79bbe30eec3ec2b7f076074f4281526724c8685a42872974ef4d36b72",
- "sha256:cadaeaba78750d58d3cc6ac4d1fd867da6fc73c88156b7a3212a3cd4819d679d",
- "sha256:cd6056167405314a4dc3c173943f11249fa0f1b204f8b51ed4bde1a9cd1834dc",
- "sha256:db72b07027db150f468fbada4d85b3b2729a3db39178abf5c543b784c1254539",
- "sha256:df2c707231459e8a4028eabcd3cfc827befd635b3ef72eada84ab13b52e1574d",
- "sha256:e62164b50f84e20601c1ff8eb55620d2ad25fb81b59e3cd776a1902527a788af",
- "sha256:e696f0dd336161fca9adbb846875d40752e6eba585843c768935ba5c9960722b",
- "sha256:eaa379fcd227ca235d04152ca6704c7cb55564116f8bc52545ff357628e10602",
- "sha256:ebea339af930f8ca5d7a699b921106c6e29c617fe9606fa7baa043c1cdae326f",
- "sha256:f4c39b0e3eac288fedc2b43055cfc2ca7a60362d0e5e87a637beac5d801ef478",
- "sha256:f5057856d21e7586765171eac8b9fc3f7d44ef39425f85dbcccb13b3ebea806c",
- "sha256:f6f45710b4459401609ebebdbcfb34515da4fc2aa886f95107f556ac69a9147e",
- "sha256:f97e83fa6c25693c7a35de154681fcc257c1c41b38beb0304b9c4d2d9e164479",
- "sha256:f9d0c5c045a3ca9bedfc35dca8526798eb91a07aa7a2c0fee134c6c6f321cbd7",
- "sha256:ff6f3db31555657f3163b15a6b7c6938d08df7adbfc9dd13d9d19edad678f1e8"
+ "sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6",
+ "sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1",
+ "sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e",
+ "sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373",
+ "sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62",
+ "sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230",
+ "sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be",
+ "sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c",
+ "sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0",
+ "sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448",
+ "sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f",
+ "sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649",
+ "sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d",
+ "sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0",
+ "sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706",
+ "sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a",
+ "sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59",
+ "sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23",
+ "sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5",
+ "sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb",
+ "sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e",
+ "sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e",
+ "sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c",
+ "sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28",
+ "sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d",
+ "sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41",
+ "sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974",
+ "sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce",
+ "sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f",
+ "sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1",
+ "sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d",
+ "sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8",
+ "sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017",
+ "sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31",
+ "sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7",
+ "sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8",
+ "sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e",
+ "sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14",
+ "sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd",
+ "sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d",
+ "sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795",
+ "sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b",
+ "sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b",
+ "sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b",
+ "sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203",
+ "sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f",
+ "sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19",
+ "sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1",
+ "sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a",
+ "sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac",
+ "sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9",
+ "sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0",
+ "sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137",
+ "sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f",
+ "sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6",
+ "sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5",
+ "sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909",
+ "sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f",
+ "sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0",
+ "sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324",
+ "sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755",
+ "sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb",
+ "sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854",
+ "sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c",
+ "sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60",
+ "sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84",
+ "sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0",
+ "sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b",
+ "sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1",
+ "sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531",
+ "sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1",
+ "sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11",
+ "sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326",
+ "sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df",
+ "sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab"
],
- "markers": "python_version >= '3.6'",
- "version": "==3.0.1"
+ "markers": "python_full_version >= '3.7.0'",
+ "version": "==3.1.0"
},
"cryptography": {
"hashes": [
- "sha256:0f8da300b5c8af9f98111ffd512910bc792b4c77392a9523624680f7956a99d4",
- "sha256:35f7c7d015d474f4011e859e93e789c87d21f6f4880ebdc29896a60403328f1f",
- "sha256:4789d1e3e257965e960232345002262ede4d094d1a19f4d3b52e48d4d8f3b885",
- "sha256:5aa67414fcdfa22cf052e640cb5ddc461924a045cacf325cd164e65312d99502",
- "sha256:5d2d8b87a490bfcd407ed9d49093793d0f75198a35e6eb1a923ce1ee86c62b41",
- "sha256:6687ef6d0a6497e2b58e7c5b852b53f62142cfa7cd1555795758934da363a965",
- "sha256:6f8ba7f0328b79f08bdacc3e4e66fb4d7aab0c3584e0bd41328dce5262e26b2e",
- "sha256:706843b48f9a3f9b9911979761c91541e3d90db1ca905fd63fee540a217698bc",
- "sha256:807ce09d4434881ca3a7594733669bd834f5b2c6d5c7e36f8c00f691887042ad",
- "sha256:83e17b26de248c33f3acffb922748151d71827d6021d98c70e6c1a25ddd78505",
- "sha256:96f1157a7c08b5b189b16b47bc9db2332269d6680a196341bf30046330d15388",
- "sha256:aec5a6c9864be7df2240c382740fcf3b96928c46604eaa7f3091f58b878c0bb6",
- "sha256:b0afd054cd42f3d213bf82c629efb1ee5f22eba35bf0eec88ea9ea7304f511a2",
- "sha256:c5caeb8188c24888c90b5108a441c106f7faa4c4c075a2bcae438c6e8ca73cef",
- "sha256:ced4e447ae29ca194449a3f1ce132ded8fcab06971ef5f618605aacaa612beac",
- "sha256:d1f6198ee6d9148405e49887803907fe8962a23e6c6f83ea7d98f1c0de375695",
- "sha256:e124352fd3db36a9d4a21c1aa27fd5d051e621845cb87fb851c08f4f75ce8be6",
- "sha256:e422abdec8b5fa8462aa016786680720d78bdce7a30c652b7fadf83a4ba35336",
- "sha256:ef8b72fa70b348724ff1218267e7f7375b8de4e8194d1636ee60510aae104cd0",
- "sha256:f0c64d1bd842ca2633e74a1a28033d139368ad959872533b1bab8c80e8240a0c",
- "sha256:f24077a3b5298a5a06a8e0536e3ea9ec60e4c7ac486755e5fb6e6ea9b3500106",
- "sha256:fdd188c8a6ef8769f148f88f859884507b954cc64db6b52f66ef199bb9ad660a",
- "sha256:fe913f20024eb2cb2f323e42a64bdf2911bb9738a15dba7d3cce48151034e3a8"
+ "sha256:05dc219433b14046c476f6f09d7636b92a1c3e5808b9a6536adf4932b3b2c440",
+ "sha256:0dcca15d3a19a66e63662dc8d30f8036b07be851a8680eda92d079868f106288",
+ "sha256:142bae539ef28a1c76794cca7f49729e7c54423f615cfd9b0b1fa90ebe53244b",
+ "sha256:3daf9b114213f8ba460b829a02896789751626a2a4e7a43a28ee77c04b5e4958",
+ "sha256:48f388d0d153350f378c7f7b41497a54ff1513c816bcbbcafe5b829e59b9ce5b",
+ "sha256:4df2af28d7bedc84fe45bd49bc35d710aede676e2a4cb7fc6d103a2adc8afe4d",
+ "sha256:4f01c9863da784558165f5d4d916093737a75203a5c5286fde60e503e4276c7a",
+ "sha256:7a38250f433cd41df7fcb763caa3ee9362777fdb4dc642b9a349721d2bf47404",
+ "sha256:8f79b5ff5ad9d3218afb1e7e20ea74da5f76943ee5edb7f76e56ec5161ec782b",
+ "sha256:956ba8701b4ffe91ba59665ed170a2ebbdc6fc0e40de5f6059195d9f2b33ca0e",
+ "sha256:a04386fb7bc85fab9cd51b6308633a3c271e3d0d3eae917eebab2fac6219b6d2",
+ "sha256:a95f4802d49faa6a674242e25bfeea6fc2acd915b5e5e29ac90a32b1139cae1c",
+ "sha256:adc0d980fd2760c9e5de537c28935cc32b9353baaf28e0814df417619c6c8c3b",
+ "sha256:aecbb1592b0188e030cb01f82d12556cf72e218280f621deed7d806afd2113f9",
+ "sha256:b12794f01d4cacfbd3177b9042198f3af1c856eedd0a98f10f141385c809a14b",
+ "sha256:c0764e72b36a3dc065c155e5b22f93df465da9c39af65516fe04ed3c68c92636",
+ "sha256:c33c0d32b8594fa647d2e01dbccc303478e16fdd7cf98652d5b3ed11aa5e5c99",
+ "sha256:cbaba590180cba88cb99a5f76f90808a624f18b169b90a4abb40c1fd8c19420e",
+ "sha256:d5a1bd0e9e2031465761dfa920c16b0065ad77321d8a8c1f5ee331021fda65e9"
],
"markers": "python_version >= '3.6'",
- "version": "==39.0.1"
+ "version": "==40.0.2"
},
"defusedxml": {
"hashes": [
@@ -264,10 +247,10 @@
},
"dj-database-url": {
"hashes": [
- "sha256:5c2993b91801c0f78a8b19e642b497b90831124cbade0c265900d4c1037b4730",
- "sha256:b23b15046cb38180e0c95207bcc90fe5e9dbde8eef16065907dd85cf4ca7036c"
+ "sha256:9c9e5f7224f62635a787e9cc3c6762c9be2b19541a21e3c08fa573bd01609b4b",
+ "sha256:a35a9f0f43775ca6f90d819dc456233ef7bcc76b47377d5d908b75c7eb320624"
],
- "version": "==1.2.0"
+ "version": "==2.0.0"
},
"dj-email-url": {
"hashes": [
@@ -278,11 +261,11 @@
},
"django": {
"hashes": [
- "sha256:bceb0fe1a386781af0788cae4108622756cd05e7775448deec04a71ddf87685d",
- "sha256:c6fe7ebe7c017fe59f1029821dae0acb5a2ddcd6c9a0138fd20a8bfefac914bc"
+ "sha256:ad33ed68db9398f5dfb33282704925bce044bef4261cd4fb59e4e7f9ae505a78",
+ "sha256:c36e2ab12824e2ac36afa8b2515a70c53c7742f0d6eaefa7311ec379558db997"
],
"index": "pypi",
- "version": "==4.1.6"
+ "version": "==4.2"
},
"django-allow-cidr": {
"hashes": [
@@ -328,11 +311,11 @@
"phonenumberslite"
],
"hashes": [
- "sha256:9edad2b2602af25f2aefc73c4cf53eaf7abf9e17d73c1c4372bd3052bebb26f9",
- "sha256:de3e47b986b4959949762c16fd8fe26b3e462ef3e5531ed00950bd20c698576a"
+ "sha256:4eaab35fe9a163046dc3a47188771385c56a788e0e11b7bbcc662e1e6b7b9104",
+ "sha256:63721dbdc7424cd594a08d80f550e790cf6e7c903cbc0fb4dd9d86baac8b8c51"
],
"index": "pypi",
- "version": "==7.0.2"
+ "version": "==7.1.0"
},
"django-widget-tweaks": {
"hashes": [
@@ -355,11 +338,11 @@
},
"faker": {
"hashes": [
- "sha256:17cf85aeb0363a3384ccd4c1f52b52ec8f414c7afaab74ae1f4c3e09a06e14de",
- "sha256:21c3c6c45183308151c14f62afe59bf54ace68f663e0180973698ba2a9a3b2c4"
+ "sha256:49060d40e6659e116f53353c5771ad2f2cbcd12b15771f49e3000a3a451f13ec",
+ "sha256:ac903ba8cb5adbce2cdd15e5536118d484bbe01126f3c774dd9f6df77b61232d"
],
"index": "pypi",
- "version": "==17.0.0"
+ "version": "==18.6.0"
},
"furl": {
"hashes": [
@@ -488,95 +471,86 @@
},
"packaging": {
"hashes": [
- "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2",
- "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"
+ "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61",
+ "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"
],
"markers": "python_version >= '3.7'",
- "version": "==23.0"
+ "version": "==23.1"
},
"phonenumberslite": {
"hashes": [
- "sha256:61a9fcf4ce688fabcce8f566fe160dc2984299112ceaa306a18922e4e4d94745",
- "sha256:bceb85adeb84eb71bb3bd1afa5163633d513b9e836b486b86fd40df17dc3b074"
+ "sha256:90e7ad011dc571c9ba76a0816d7fc92a7de8944dcb273f074edfb48f20f18f75",
+ "sha256:9d8162427baa4a0fdcb4902c5ca5936d2c165374c0dc6693227c68e5852d5c88"
],
- "version": "==8.13.6"
+ "version": "==8.13.11"
},
"psycopg2-binary": {
"hashes": [
- "sha256:00475004e5ed3e3bf5e056d66e5dcdf41a0dc62efcd57997acd9135c40a08a50",
- "sha256:01ad49d68dd8c5362e4bfb4158f2896dc6e0c02e87b8a3770fc003459f1a4425",
- "sha256:024030b13bdcbd53d8a93891a2cf07719715724fc9fee40243f3bd78b4264b8f",
- "sha256:02551647542f2bf89073d129c73c05a25c372fc0a49aa50e0de65c3c143d8bd0",
- "sha256:043a9fd45a03858ff72364b4b75090679bd875ee44df9c0613dc862ca6b98460",
- "sha256:05b3d479425e047c848b9782cd7aac9c6727ce23181eb9647baf64ffdfc3da41",
- "sha256:0775d6252ccb22b15da3b5d7adbbf8cfe284916b14b6dc0ff503a23edb01ee85",
- "sha256:1764546ffeaed4f9428707be61d68972eb5ede81239b46a45843e0071104d0dd",
- "sha256:1e491e6489a6cb1d079df8eaa15957c277fdedb102b6a68cfbf40c4994412fd0",
- "sha256:212757ffcecb3e1a5338d4e6761bf9c04f750e7d027117e74aa3cd8a75bb6fbd",
- "sha256:215d6bf7e66732a514f47614f828d8c0aaac9a648c46a831955cb103473c7147",
- "sha256:25382c7d174c679ce6927c16b6fbb68b10e56ee44b1acb40671e02d29f2fce7c",
- "sha256:2abccab84d057723d2ca8f99ff7b619285d40da6814d50366f61f0fc385c3903",
- "sha256:2d964eb24c8b021623df1c93c626671420c6efadbdb8655cb2bd5e0c6fa422ba",
- "sha256:2ec46ed947801652c9643e0b1dc334cfb2781232e375ba97312c2fc256597632",
- "sha256:2ef892cabdccefe577088a79580301f09f2a713eb239f4f9f62b2b29cafb0577",
- "sha256:33e632d0885b95a8b97165899006c40e9ecdc634a529dca7b991eb7de4ece41c",
- "sha256:3520d7af1ebc838cc6084a3281145d5cd5bdd43fdef139e6db5af01b92596cb7",
- "sha256:3d790f84201c3698d1bfb404c917f36e40531577a6dda02e45ba29b64d539867",
- "sha256:3fc33295cfccad697a97a76dec3f1e94ad848b7b163c3228c1636977966b51e2",
- "sha256:422e3d43b47ac20141bc84b3d342eead8d8099a62881a501e97d15f6addabfe9",
- "sha256:426c2ae999135d64e6a18849a7d1ad0e1bd007277e4a8f4752eaa40a96b550ff",
- "sha256:46512486be6fbceef51d7660dec017394ba3e170299d1dc30928cbedebbf103a",
- "sha256:46850a640df62ae940e34a163f72e26aca1f88e2da79148e1862faaac985c302",
- "sha256:484405b883630f3e74ed32041a87456c5e0e63a8e3429aa93e8714c366d62bd1",
- "sha256:4e7904d1920c0c89105c0517dc7e3f5c20fb4e56ba9cdef13048db76947f1d79",
- "sha256:56b2957a145f816726b109ee3d4e6822c23f919a7d91af5a94593723ed667835",
- "sha256:5c6527c8efa5226a9e787507652dd5ba97b62d29b53c371a85cd13f957fe4d42",
- "sha256:5cbc554ba47ecca8cd3396ddaca85e1ecfe3e48dd57dc5e415e59551affe568e",
- "sha256:5d28ecdf191db558d0c07d0f16524ee9d67896edf2b7990eea800abeb23ebd61",
- "sha256:5fc447058d083b8c6ac076fc26b446d44f0145308465d745fba93a28c14c9e32",
- "sha256:63e318dbe52709ed10d516a356f22a635e07a2e34c68145484ed96a19b0c4c68",
- "sha256:68d81a2fe184030aa0c5c11e518292e15d342a667184d91e30644c9d533e53e1",
- "sha256:6e63814ec71db9bdb42905c925639f319c80e7909fb76c3b84edc79dadef8d60",
- "sha256:6f8a9bcab7b6db2e3dbf65b214dfc795b4c6b3bb3af922901b6a67f7cb47d5f8",
- "sha256:70831e03bd53702c941da1a1ad36c17d825a24fbb26857b40913d58df82ec18b",
- "sha256:74eddec4537ab1f701a1647214734bc52cee2794df748f6ae5908e00771f180a",
- "sha256:7b3751857da3e224f5629400736a7b11e940b5da5f95fa631d86219a1beaafec",
- "sha256:7cf1d44e710ca3a9ce952bda2855830fe9f9017ed6259e01fcd71ea6287565f5",
- "sha256:7d07f552d1e412f4b4e64ce386d4c777a41da3b33f7098b6219012ba534fb2c2",
- "sha256:7d88db096fa19d94f433420eaaf9f3c45382da2dd014b93e4bf3215639047c16",
- "sha256:7ee3095d02d6f38bd7d9a5358fcc9ea78fcdb7176921528dd709cc63f40184f5",
- "sha256:902844f9c4fb19b17dfa84d9e2ca053d4a4ba265723d62ea5c9c26b38e0aa1e6",
- "sha256:937880290775033a743f4836aa253087b85e62784b63fd099ee725d567a48aa1",
- "sha256:95076399ec3b27a8f7fa1cc9a83417b1c920d55cf7a97f718a94efbb96c7f503",
- "sha256:9c38d3869238e9d3409239bc05bc27d6b7c99c2a460ea337d2814b35fb4fea1b",
- "sha256:9e32cedc389bcb76d9f24ea8a012b3cb8385ee362ea437e1d012ffaed106c17d",
- "sha256:9ffdc51001136b699f9563b1c74cc1f8c07f66ef7219beb6417a4c8aaa896c28",
- "sha256:a0adef094c49f242122bb145c3c8af442070dc0e4312db17e49058c1702606d4",
- "sha256:a36a0e791805aa136e9cbd0ffa040d09adec8610453ee8a753f23481a0057af5",
- "sha256:a7e518a0911c50f60313cb9e74a169a65b5d293770db4770ebf004245f24b5c5",
- "sha256:af0516e1711995cb08dc19bbd05bec7dbdebf4185f68870595156718d237df3e",
- "sha256:b8104f709590fff72af801e916817560dbe1698028cd0afe5a52d75ceb1fce5f",
- "sha256:b911dfb727e247340d36ae20c4b9259e4a64013ab9888ccb3cbba69b77fd9636",
- "sha256:b9a794cef1d9c1772b94a72eec6da144c18e18041d294a9ab47669bc77a80c1d",
- "sha256:b9c33d4aef08dfecbd1736ceab8b7b3c4358bf10a0121483e5cd60d3d308cc64",
- "sha256:b9d38a4656e4e715d637abdf7296e98d6267df0cc0a8e9a016f8ba07e4aa3eeb",
- "sha256:bcda1c84a1c533c528356da5490d464a139b6e84eb77cc0b432e38c5c6dd7882",
- "sha256:bef7e3f9dc6f0c13afdd671008534be5744e0e682fb851584c8c3a025ec09720",
- "sha256:c15ba5982c177bc4b23a7940c7e4394197e2d6a424a2d282e7c236b66da6d896",
- "sha256:c5254cbd4f4855e11cebf678c1a848a3042d455a22a4ce61349c36aafd4c2267",
- "sha256:c5682a45df7d9642eff590abc73157c887a68f016df0a8ad722dcc0f888f56d7",
- "sha256:c5e65c6ac0ae4bf5bef1667029f81010b6017795dcb817ba5c7b8a8d61fab76f",
- "sha256:d4c7b3a31502184e856df1f7bbb2c3735a05a8ce0ade34c5277e1577738a5c91",
- "sha256:d892bfa1d023c3781a3cab8dd5af76b626c483484d782e8bd047c180db590e4c",
- "sha256:dbc332beaf8492b5731229a881807cd7b91b50dbbbaf7fe2faf46942eda64a24",
- "sha256:dc85b3777068ed30aff8242be2813038a929f2084f69e43ef869daddae50f6ee",
- "sha256:e59137cdb970249ae60be2a49774c6dfb015bd0403f05af1fe61862e9626642d",
- "sha256:e67b3c26e9b6d37b370c83aa790bbc121775c57bfb096c2e77eacca25fd0233b",
- "sha256:e72c91bda9880f097c8aa3601a2c0de6c708763ba8128006151f496ca9065935",
- "sha256:f95b8aca2703d6a30249f83f4fe6a9abf2e627aa892a5caaab2267d56be7ab69"
+ "sha256:02c0f3757a4300cf379eb49f543fb7ac527fb00144d39246ee40e1df684ab514",
+ "sha256:02c6e3cf3439e213e4ee930308dc122d6fb4d4bea9aef4a12535fbd605d1a2fe",
+ "sha256:0645376d399bfd64da57148694d78e1f431b1e1ee1054872a5713125681cf1be",
+ "sha256:0892ef645c2fabb0c75ec32d79f4252542d0caec1d5d949630e7d242ca4681a3",
+ "sha256:0d236c2825fa656a2d98bbb0e52370a2e852e5a0ec45fc4f402977313329174d",
+ "sha256:0e0f754d27fddcfd74006455b6e04e6705d6c31a612ec69ddc040a5468e44b4e",
+ "sha256:15e2ee79e7cf29582ef770de7dab3d286431b01c3bb598f8e05e09601b890081",
+ "sha256:1876843d8e31c89c399e31b97d4b9725a3575bb9c2af92038464231ec40f9edb",
+ "sha256:1f64dcfb8f6e0c014c7f55e51c9759f024f70ea572fbdef123f85318c297947c",
+ "sha256:2ab652e729ff4ad76d400df2624d223d6e265ef81bb8aa17fbd63607878ecbee",
+ "sha256:30637a20623e2a2eacc420059be11527f4458ef54352d870b8181a4c3020ae6b",
+ "sha256:34b9ccdf210cbbb1303c7c4db2905fa0319391bd5904d32689e6dd5c963d2ea8",
+ "sha256:38601cbbfe600362c43714482f43b7c110b20cb0f8172422c616b09b85a750c5",
+ "sha256:441cc2f8869a4f0f4bb408475e5ae0ee1f3b55b33f350406150277f7f35384fc",
+ "sha256:498807b927ca2510baea1b05cc91d7da4718a0f53cb766c154c417a39f1820a0",
+ "sha256:4ac30da8b4f57187dbf449294d23b808f8f53cad6b1fc3623fa8a6c11d176dd0",
+ "sha256:4c727b597c6444a16e9119386b59388f8a424223302d0c06c676ec8b4bc1f963",
+ "sha256:4d67fbdaf177da06374473ef6f7ed8cc0a9dc640b01abfe9e8a2ccb1b1402c1f",
+ "sha256:4dfb4be774c4436a4526d0c554af0cc2e02082c38303852a36f6456ece7b3503",
+ "sha256:4ea29fc3ad9d91162c52b578f211ff1c931d8a38e1f58e684c45aa470adf19e2",
+ "sha256:51537e3d299be0db9137b321dfb6a5022caaab275775680e0c3d281feefaca6b",
+ "sha256:61b047a0537bbc3afae10f134dc6393823882eb263088c271331602b672e52e9",
+ "sha256:6460c7a99fc939b849431f1e73e013d54aa54293f30f1109019c56a0b2b2ec2f",
+ "sha256:65bee1e49fa6f9cf327ce0e01c4c10f39165ee76d35c846ade7cb0ec6683e303",
+ "sha256:65c07febd1936d63bfde78948b76cd4c2a411572a44ac50719ead41947d0f26b",
+ "sha256:71f14375d6f73b62800530b581aed3ada394039877818b2d5f7fc77e3bb6894d",
+ "sha256:7a40c00dbe17c0af5bdd55aafd6ff6679f94a9be9513a4c7e071baf3d7d22a70",
+ "sha256:7e13a5a2c01151f1208d5207e42f33ba86d561b7a89fca67c700b9486a06d0e2",
+ "sha256:7f0438fa20fb6c7e202863e0d5ab02c246d35efb1d164e052f2f3bfe2b152bd0",
+ "sha256:8122cfc7cae0da9a3077216528b8bb3629c43b25053284cc868744bfe71eb141",
+ "sha256:8338a271cb71d8da40b023a35d9c1e919eba6cbd8fa20a54b748a332c355d896",
+ "sha256:84d2222e61f313c4848ff05353653bf5f5cf6ce34df540e4274516880d9c3763",
+ "sha256:8a6979cf527e2603d349a91060f428bcb135aea2be3201dff794813256c274f1",
+ "sha256:8a76e027f87753f9bd1ab5f7c9cb8c7628d1077ef927f5e2446477153a602f2c",
+ "sha256:964b4dfb7c1c1965ac4c1978b0f755cc4bd698e8aa2b7667c575fb5f04ebe06b",
+ "sha256:9972aad21f965599ed0106f65334230ce826e5ae69fda7cbd688d24fa922415e",
+ "sha256:a8c28fd40a4226b4a84bdf2d2b5b37d2c7bd49486b5adcc200e8c7ec991dfa7e",
+ "sha256:ae102a98c547ee2288637af07393dd33f440c25e5cd79556b04e3fca13325e5f",
+ "sha256:af335bac6b666cc6aea16f11d486c3b794029d9df029967f9938a4bed59b6a19",
+ "sha256:afe64e9b8ea66866a771996f6ff14447e8082ea26e675a295ad3bdbffdd72afb",
+ "sha256:b4b24f75d16a89cc6b4cdff0eb6a910a966ecd476d1e73f7ce5985ff1328e9a6",
+ "sha256:b6c8288bb8a84b47e07013bb4850f50538aa913d487579e1921724631d02ea1b",
+ "sha256:b83456c2d4979e08ff56180a76429263ea254c3f6552cd14ada95cff1dec9bb8",
+ "sha256:bfb13af3c5dd3a9588000910178de17010ebcccd37b4f9794b00595e3a8ddad3",
+ "sha256:c3dba7dab16709a33a847e5cd756767271697041fbe3fe97c215b1fc1f5c9848",
+ "sha256:c48d8f2db17f27d41fb0e2ecd703ea41984ee19362cbce52c097963b3a1b4365",
+ "sha256:c7e62ab8b332147a7593a385d4f368874d5fe4ad4e341770d4983442d89603e3",
+ "sha256:c83a74b68270028dc8ee74d38ecfaf9c90eed23c8959fca95bd703d25b82c88e",
+ "sha256:cacbdc5839bdff804dfebc058fe25684cae322987f7a38b0168bc1b2df703fb1",
+ "sha256:cf4499e0a83b7b7edcb8dabecbd8501d0d3a5ef66457200f77bde3d210d5debb",
+ "sha256:cfec476887aa231b8548ece2e06d28edc87c1397ebd83922299af2e051cf2827",
+ "sha256:d26e0342183c762de3276cca7a530d574d4e25121ca7d6e4a98e4f05cb8e4df7",
+ "sha256:d4e6036decf4b72d6425d5b29bbd3e8f0ff1059cda7ac7b96d6ac5ed34ffbacd",
+ "sha256:d57c3fd55d9058645d26ae37d76e61156a27722097229d32a9e73ed54819982a",
+ "sha256:dfa74c903a3c1f0d9b1c7e7b53ed2d929a4910e272add6700c38f365a6002820",
+ "sha256:e3ed340d2b858d6e6fb5083f87c09996506af483227735de6964a6100b4e6a54",
+ "sha256:e78e6e2a00c223e164c417628572a90093c031ed724492c763721c2e0bc2a8df",
+ "sha256:e9182eb20f41417ea1dd8e8f7888c4d7c6e805f8a7c98c1081778a3da2bee3e4",
+ "sha256:e99e34c82309dd78959ba3c1590975b5d3c862d6f279f843d47d26ff89d7d7e1",
+ "sha256:f6a88f384335bb27812293fdb11ac6aee2ca3f51d3c7820fe03de0a304ab6249",
+ "sha256:f81e65376e52f03422e1fb475c9514185669943798ed019ac50410fb4c4df232",
+ "sha256:ffe9dc0a884a8848075e576c1de0290d85a533a9f6e9c4e564f19adf8f6e54a7"
],
"index": "pypi",
- "version": "==2.9.5"
+ "version": "==2.9.6"
},
"pycparser": {
"hashes": [
@@ -641,19 +615,19 @@
},
"python-dotenv": {
"hashes": [
- "sha256:1c93de8f636cde3ce377292818d0e440b6e45a82f215c3744979151fa8151c49",
- "sha256:41e12e0318bebc859fcc4d97d4db8d20ad21721a6aa5047dd59f090391cb549a"
+ "sha256:a8df96034aae6d2d50a4ebe8216326c61c3eb64836776504fcca410e5937a3ba",
+ "sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a"
],
- "markers": "python_version >= '3.7'",
- "version": "==0.21.1"
+ "markers": "python_version >= '3.8'",
+ "version": "==1.0.0"
},
"requests": {
"hashes": [
- "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa",
- "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf"
+ "sha256:e8f3c9be120d3333921d213eef078af392fba3933ab7ed2d1cba3b56f2568c3b",
+ "sha256:f2e34a75f4749019bb0e3effb66683630e4ffeaf75819fb51bebef1bf5aef059"
],
"index": "pypi",
- "version": "==2.28.2"
+ "version": "==2.29.0"
},
"s3transfer": {
"hashes": [
@@ -665,11 +639,11 @@
},
"setuptools": {
"hashes": [
- "sha256:16ccf598aab3b506593c17378473978908a2734d7336755a8769b480906bec1c",
- "sha256:b440ee5f7e607bb8c9de15259dba2583dd41a38879a7abc1d43a71c59524da48"
+ "sha256:23aaf86b85ca52ceb801d32703f12d77517b2556af839621c641fca11287952b",
+ "sha256:f104fa03692a2602fa0fec6c6a9e63b6c8a968de13e17c026957dd1f53d80990"
],
"markers": "python_version >= '3.7'",
- "version": "==67.2.0"
+ "version": "==67.7.2"
},
"six": {
"hashes": [
@@ -681,35 +655,35 @@
},
"sqlparse": {
"hashes": [
- "sha256:0323c0ec29cd52bceabc1b4d9d579e311f3e4961b98d174201d5622a23b85e34",
- "sha256:69ca804846bb114d2ec380e4360a8a340db83f0ccf3afceeb1404df028f57268"
+ "sha256:5430a4fe2ac7d0f93e66f1efc6e1338a41884b7ddf2a350cedd20ccc4d9d28f3",
+ "sha256:d446183e84b8349fa3061f0fe7f06ca94ba65b426946ffebe6e3e8295332420c"
],
"markers": "python_version >= '3.5'",
- "version": "==0.4.3"
+ "version": "==0.4.4"
},
"typing-extensions": {
"hashes": [
- "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa",
- "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"
+ "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb",
+ "sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4"
],
"markers": "python_version >= '3.7'",
- "version": "==4.4.0"
+ "version": "==4.5.0"
},
"urllib3": {
"hashes": [
- "sha256:076907bf8fd355cde77728471316625a4d2f7e713c125f51953bb5b3eecf4f72",
- "sha256:75edcdc2f7d85b137124a6c3c9fc3933cdeaa12ecb9a6a959f22797a0feca7e1"
+ "sha256:8a388717b9476f934a21484e8c8e61875ab60644d29b9b39e11e4b9dc1c6b305",
+ "sha256:aa751d169e23c7479ce47a0cb0da579e3ede798f994f5816a74e4f4500dcea42"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
- "version": "==1.26.14"
+ "version": "==1.26.15"
},
"whitenoise": {
"hashes": [
- "sha256:cf8ecf56d86ba1c734fdb5ef6127312e39e92ad5947fef9033dc9e43ba2777d9",
- "sha256:fe0af31504ab08faa1ec7fc02845432096e40cc1b27e6a7747263d7b30fb51fa"
+ "sha256:599dc6ca57e48929dfeffb2e8e187879bfe2aed0d49ca419577005b7f2cc930b",
+ "sha256:a02d6660ad161ff17e3042653c8e3f5ecbb2a2481a006bde125b9efb9a30113a"
],
"index": "pypi",
- "version": "==6.3.0"
+ "version": "==6.4.0"
}
},
"develop": {
@@ -723,66 +697,66 @@
},
"bandit": {
"hashes": [
- "sha256:2d63a8c573417bae338962d4b9b06fbc6080f74ecd955a092849e1e65c717bd2",
- "sha256:412d3f259dab4077d0e7f0c11f50f650cc7d10db905d98f6520a95a18049658a"
+ "sha256:75665181dc1e0096369112541a056c59d1c5f66f9bb74a8d686c3c362b83f549",
+ "sha256:bdfc739baa03b880c2d15d0431b31c658ffc348e907fe197e54e0389dd59e11e"
],
"index": "pypi",
- "version": "==1.7.4"
+ "version": "==1.7.5"
},
"beautifulsoup4": {
"hashes": [
- "sha256:0e79446b10b3ecb499c1556f7e228a53e64a2bfcebd455f370d8927cb5b59e39",
- "sha256:bc4bdda6717de5a2987436fb8d72f45dc90dd856bdfd512a1314ce90349a0106"
+ "sha256:492bbc69dca35d12daac71c4db1bfff0c876c00ef4a2ffacce226d4638eb72da",
+ "sha256:bd2520ca0d9d7d12694a53d44ac482d181b4ec1888909b035a3dbf40d0f57d4a"
],
- "markers": "python_version >= '3.6'",
- "version": "==4.11.2"
+ "markers": "python_full_version >= '3.6.0'",
+ "version": "==4.12.2"
},
"black": {
"hashes": [
- "sha256:0052dba51dec07ed029ed61b18183942043e00008ec65d5028814afaab9a22fd",
- "sha256:0680d4380db3719ebcfb2613f34e86c8e6d15ffeabcf8ec59355c5e7b85bb555",
- "sha256:121ca7f10b4a01fd99951234abdbd97728e1240be89fde18480ffac16503d481",
- "sha256:162e37d49e93bd6eb6f1afc3e17a3d23a823042530c37c3c42eeeaf026f38468",
- "sha256:2a951cc83ab535d248c89f300eccbd625e80ab880fbcfb5ac8afb5f01a258ac9",
- "sha256:2bf649fda611c8550ca9d7592b69f0637218c2369b7744694c5e4902873b2f3a",
- "sha256:382998821f58e5c8238d3166c492139573325287820963d2f7de4d518bd76958",
- "sha256:49f7b39e30f326a34b5c9a4213213a6b221d7ae9d58ec70df1c4a307cf2a1580",
- "sha256:57c18c5165c1dbe291d5306e53fb3988122890e57bd9b3dcb75f967f13411a26",
- "sha256:7a0f701d314cfa0896b9001df70a530eb2472babb76086344e688829efd97d32",
- "sha256:8178318cb74f98bc571eef19068f6ab5613b3e59d4f47771582f04e175570ed8",
- "sha256:8b70eb40a78dfac24842458476135f9b99ab952dd3f2dab738c1881a9b38b753",
- "sha256:9880d7d419bb7e709b37e28deb5e68a49227713b623c72b2b931028ea65f619b",
- "sha256:9afd3f493666a0cd8f8df9a0200c6359ac53940cbde049dcb1a7eb6ee2dd7074",
- "sha256:a29650759a6a0944e7cca036674655c2f0f63806ddecc45ed40b7b8aa314b651",
- "sha256:a436e7881d33acaf2536c46a454bb964a50eff59b21b51c6ccf5a40601fbef24",
- "sha256:a59db0a2094d2259c554676403fa2fac3473ccf1354c1c63eccf7ae65aac8ab6",
- "sha256:a8471939da5e824b891b25751955be52ee7f8a30a916d570a5ba8e0f2eb2ecad",
- "sha256:b0bd97bea8903f5a2ba7219257a44e3f1f9d00073d6cc1add68f0beec69692ac",
- "sha256:b6a92a41ee34b883b359998f0c8e6eb8e99803aa8bf3123bf2b2e6fec505a221",
- "sha256:bb460c8561c8c1bec7824ecbc3ce085eb50005883a6203dcfb0122e95797ee06",
- "sha256:bfffba28dc52a58f04492181392ee380e95262af14ee01d4bc7bb1b1c6ca8d27",
- "sha256:c1c476bc7b7d021321e7d93dc2cbd78ce103b84d5a4cf97ed535fbc0d6660648",
- "sha256:c91dfc2c2a4e50df0026f88d2215e166616e0c80e86004d0003ece0488db2739",
- "sha256:e6663f91b6feca5d06f2ccd49a10f254f9298cc1f7f49c46e498a0771b507104"
+ "sha256:064101748afa12ad2291c2b91c960be28b817c0c7eaa35bec09cc63aa56493c5",
+ "sha256:0945e13506be58bf7db93ee5853243eb368ace1c08a24c65ce108986eac65915",
+ "sha256:11c410f71b876f961d1de77b9699ad19f939094c3a677323f43d7a29855fe326",
+ "sha256:1c7b8d606e728a41ea1ccbd7264677e494e87cf630e399262ced92d4a8dac940",
+ "sha256:1d06691f1eb8de91cd1b322f21e3bfc9efe0c7ca1f0e1eb1db44ea367dff656b",
+ "sha256:3238f2aacf827d18d26db07524e44741233ae09a584273aa059066d644ca7b30",
+ "sha256:32daa9783106c28815d05b724238e30718f34155653d4d6e125dc7daec8e260c",
+ "sha256:35d1381d7a22cc5b2be2f72c7dfdae4072a3336060635718cc7e1ede24221d6c",
+ "sha256:3a150542a204124ed00683f0db1f5cf1c2aaaa9cc3495b7a3b5976fb136090ab",
+ "sha256:48f9d345675bb7fbc3dd85821b12487e1b9a75242028adad0333ce36ed2a6d27",
+ "sha256:50cb33cac881766a5cd9913e10ff75b1e8eb71babf4c7104f2e9c52da1fb7de2",
+ "sha256:562bd3a70495facf56814293149e51aa1be9931567474993c7942ff7d3533961",
+ "sha256:67de8d0c209eb5b330cce2469503de11bca4085880d62f1628bd9972cc3366b9",
+ "sha256:6b39abdfb402002b8a7d030ccc85cf5afff64ee90fa4c5aebc531e3ad0175ddb",
+ "sha256:6f3c333ea1dd6771b2d3777482429864f8e258899f6ff05826c3a4fcc5ce3f70",
+ "sha256:714290490c18fb0126baa0fca0a54ee795f7502b44177e1ce7624ba1c00f2331",
+ "sha256:7c3eb7cea23904399866c55826b31c1f55bbcd3890ce22ff70466b907b6775c2",
+ "sha256:92c543f6854c28a3c7f39f4d9b7694f9a6eb9d3c5e2ece488c327b6e7ea9b266",
+ "sha256:a6f6886c9869d4daae2d1715ce34a19bbc4b95006d20ed785ca00fa03cba312d",
+ "sha256:a8a968125d0a6a404842fa1bf0b349a568634f856aa08ffaff40ae0dfa52e7c6",
+ "sha256:c7ab5790333c448903c4b721b59c0d80b11fe5e9803d8703e84dcb8da56fec1b",
+ "sha256:e114420bf26b90d4b9daa597351337762b63039752bdf72bf361364c1aa05925",
+ "sha256:e198cf27888ad6f4ff331ca1c48ffc038848ea9f031a3b40ba36aced7e22f2c8",
+ "sha256:ec751418022185b0c1bb7d7736e6933d40bbb14c14a0abcf9123d1b159f98dd4",
+ "sha256:f0bd2f4a58d6666500542b26354978218a9babcdc972722f4bf90779524515f3"
],
"index": "pypi",
- "version": "==23.1.0"
+ "version": "==23.3.0"
},
"blinker": {
"hashes": [
- "sha256:1eb563df6fdbc39eeddc177d953203f99f097e9bf0e2b8f9f3cf18b6ca425e36",
- "sha256:923e5e2f69c155f2cc42dafbbd70e16e3fde24d2d4aa2ab72fbe386238892462"
+ "sha256:4afd3de66ef3a9f8067559fb7a1cbe555c17dcbe15971b05d1b625c3e7abe213",
+ "sha256:c3d739772abb7bc2860abf5f2ec284223d9ad5c76da018234f6f50d6f31ab1f0"
],
- "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
- "version": "==1.5"
+ "markers": "python_version >= '3.7'",
+ "version": "==1.6.2"
},
"boto3": {
"hashes": [
- "sha256:9a0a29179957cb26fa8c3c1fddf66b18efaeaf633e08db5fb53815ffb0421419",
- "sha256:eb8cde24a4c5755c35126e8cd460e6b51c63d04292419e7e95721232720c7e5b"
+ "sha256:38ca632be379963f2a2749b5f63a81fe1679913b954914f470ad282c77674bbc",
+ "sha256:4d575c180312bec6108852bae12e6396b9d1bb404154d652c57ee849c62fbb83"
],
"index": "pypi",
- "version": "==1.26.69"
+ "version": "==1.26.122"
},
"boto3-mocking": {
"hashes": [
@@ -794,27 +768,27 @@
},
"boto3-stubs": {
"hashes": [
- "sha256:94edd1d802fb57e5265300b11760619ab1ba1f1aef3aa3f43ca0ca0399855ee8",
- "sha256:b8cbdd22bea8024a1c94b16f2d1e7d0e99b83d36503ae2674dfcc752492b6847"
+ "sha256:401e7fe51d88a51b527d883d195ed20c7f57aeb2c0aea24bbb3e911b6d2ad3aa",
+ "sha256:743a37bfd7d1eed4d67cdf825283abc1d93b7900b81d7426aab7e691e075c897"
],
"index": "pypi",
- "version": "==1.26.69"
+ "version": "==1.26.122"
},
"botocore": {
"hashes": [
- "sha256:2a4ab8bcb3177daa425019e125c09996b9a6a1a62bb0baaaeeb86ffd552719cc",
- "sha256:7e1bebca013544fbc298cb58603bfccd5f71b49c720a5c33c07cf5dfc8145a1f"
+ "sha256:9e4984a9e9777c6b949aa1e98323fa35480d9f99d447af7e179ae611f7ed5af9",
+ "sha256:c3b41078d235761b9c5dc22f534a76952622ef96787b96bbd10242ec4d73f2a5"
],
"markers": "python_version >= '3.7'",
- "version": "==1.29.69"
+ "version": "==1.29.122"
},
"botocore-stubs": {
"hashes": [
- "sha256:15a5160119909788a5f346db12853f6675705d9856360fed8474c45ea4f9e018",
- "sha256:cff293f3d5418110be4966ecc58a86146cc1e000984e2ea34350f9ef8ef88dd8"
+ "sha256:59873a3b535ec3ff0b6bf5f41c9f8a0f8c48032a871bea4d6e4faebbbfc68e8b",
+ "sha256:e6e6c527a6cac0ec69dd1b755d530c9b2dab01d423ce47bdc636dd01ebb01b1b"
],
"markers": "python_version >= '3.7' and python_version < '4.0'",
- "version": "==1.29.69"
+ "version": "==1.29.122"
},
"click": {
"hashes": [
@@ -826,35 +800,35 @@
},
"django": {
"hashes": [
- "sha256:bceb0fe1a386781af0788cae4108622756cd05e7775448deec04a71ddf87685d",
- "sha256:c6fe7ebe7c017fe59f1029821dae0acb5a2ddcd6c9a0138fd20a8bfefac914bc"
+ "sha256:ad33ed68db9398f5dfb33282704925bce044bef4261cd4fb59e4e7f9ae505a78",
+ "sha256:c36e2ab12824e2ac36afa8b2515a70c53c7742f0d6eaefa7311ec379558db997"
],
"index": "pypi",
- "version": "==4.1.6"
+ "version": "==4.2"
},
"django-debug-toolbar": {
"hashes": [
- "sha256:24ef1a7d44d25e60d7951e378454c6509bf536dce7e7d9d36e7c387db499bc27",
- "sha256:879f8a4672d41621c06a4d322dcffa630fc4df056cada6e417ed01db0e5e0478"
+ "sha256:89619f6e0ea1057dca47bfc429ed99b237ef70074dabc065a7faa5f00e1459cf",
+ "sha256:bad339d68520652ddc1580c76f136fcbc3e020fd5ed96510a89a02ec81bb3fb1"
],
"index": "pypi",
- "version": "==3.8.1"
+ "version": "==4.0.0"
},
"django-stubs": {
"hashes": [
- "sha256:b081d64d923171f79d4e57899b0980da847e4046b91166e3658a6151645a36c5",
- "sha256:d53bcd4975a54ca5c9abbbd33b61f40d44191971018f2ea54f73b0a6a99e1a8b"
+ "sha256:93baff824f0a056e71036b423b942a74f07b909e45e3fa38185b910f597c5c08",
+ "sha256:d2c671989efb3f7b0fa91e461909ad5a5a52155fe7fe6d1f2058cb88e3afb123"
],
"index": "pypi",
- "version": "==1.14.0"
+ "version": "==4.2.0"
},
"django-stubs-ext": {
"hashes": [
- "sha256:4fd8cdbc68d1a421f21bb7e0d9e76d50f6a4b504d350ba786405daf536e90c21",
- "sha256:d729fbc7fe8970a7e26b35956c35b48502516f011d523c0577bdfb02ed956284"
+ "sha256:55b2e3077f883e0131a7596f8ff8b19f8fc3ca325a3318ccacf5331acb2601e4",
+ "sha256:7789f0caeca7152fef07ad6b94dec7310a05d0b8dab77f7979e19db0037b5127"
],
"markers": "python_version >= '3.7'",
- "version": "==0.7.0"
+ "version": "==4.2.0"
},
"django-webtest": {
"hashes": [
@@ -882,11 +856,11 @@
},
"gitpython": {
"hashes": [
- "sha256:769c2d83e13f5d938b7688479da374c4e3d49f71549aaf462b646db9602ea6f8",
- "sha256:cd455b0000615c60e286208ba540271af9fe531fa6a87cc590a7298785ab2882"
+ "sha256:8ce3bcf69adfdf7c7d503e78fd3b1c492af782d58893b650adb2ac8912ddd573",
+ "sha256:f04893614f6aa713a60cbbe1e6a97403ef633103cdd0ef5eb6efe0deb98dbe8d"
],
"markers": "python_version >= '3.7'",
- "version": "==3.1.30"
+ "version": "==3.1.31"
},
"jmespath": {
"hashes": [
@@ -896,6 +870,14 @@
"markers": "python_version >= '3.7'",
"version": "==1.0.1"
},
+ "markdown-it-py": {
+ "hashes": [
+ "sha256:5a35f8d1870171d9acc47b99612dc146129b631baf04970128b568f190d0cc30",
+ "sha256:7c9a5e412688bc771c67432cbfebcdd686c93ce6484913dccf06cb5a0bea35a1"
+ ],
+ "markers": "python_version >= '3.7'",
+ "version": "==2.2.0"
+ },
"mccabe": {
"hashes": [
"sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325",
@@ -904,37 +886,45 @@
"markers": "python_version >= '3.6'",
"version": "==0.7.0"
},
+ "mdurl": {
+ "hashes": [
+ "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8",
+ "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"
+ ],
+ "markers": "python_version >= '3.7'",
+ "version": "==0.1.2"
+ },
"mypy": {
"hashes": [
- "sha256:01b1b9e1ed40544ef486fa8ac022232ccc57109f379611633ede8e71630d07d2",
- "sha256:0ab090d9240d6b4e99e1fa998c2d0aa5b29fc0fb06bd30e7ad6183c95fa07593",
- "sha256:14d776869a3e6c89c17eb943100f7868f677703c8a4e00b3803918f86aafbc52",
- "sha256:1ace23f6bb4aec4604b86c4843276e8fa548d667dbbd0cb83a3ae14b18b2db6c",
- "sha256:2efa963bdddb27cb4a0d42545cd137a8d2b883bd181bbc4525b568ef6eca258f",
- "sha256:2f6ac8c87e046dc18c7d1d7f6653a66787a4555085b056fe2d599f1f1a2a2d21",
- "sha256:3ae4c7a99e5153496243146a3baf33b9beff714464ca386b5f62daad601d87af",
- "sha256:3cfad08f16a9c6611e6143485a93de0e1e13f48cfb90bcad7d5fde1c0cec3d36",
- "sha256:4e5175026618c178dfba6188228b845b64131034ab3ba52acaffa8f6c361f805",
- "sha256:50979d5efff8d4135d9db293c6cb2c42260e70fb010cbc697b1311a4d7a39ddb",
- "sha256:5cd187d92b6939617f1168a4fe68f68add749902c010e66fe574c165c742ed88",
- "sha256:5cfca124f0ac6707747544c127880893ad72a656e136adc935c8600740b21ff5",
- "sha256:5e398652d005a198a7f3c132426b33c6b85d98aa7dc852137a2a3be8890c4072",
- "sha256:67cced7f15654710386e5c10b96608f1ee3d5c94ca1da5a2aad5889793a824c1",
- "sha256:7306edca1c6f1b5fa0bc9aa645e6ac8393014fa82d0fa180d0ebc990ebe15964",
- "sha256:7cc2c01dfc5a3cbddfa6c13f530ef3b95292f926329929001d45e124342cd6b7",
- "sha256:87edfaf344c9401942883fad030909116aa77b0fa7e6e8e1c5407e14549afe9a",
- "sha256:8845125d0b7c57838a10fd8925b0f5f709d0e08568ce587cc862aacce453e3dd",
- "sha256:92024447a339400ea00ac228369cd242e988dd775640755fa4ac0c126e49bb74",
- "sha256:a86b794e8a56ada65c573183756eac8ac5b8d3d59daf9d5ebd72ecdbb7867a43",
- "sha256:bb2782a036d9eb6b5a6efcdda0986774bf798beef86a62da86cb73e2a10b423d",
- "sha256:be78077064d016bc1b639c2cbcc5be945b47b4261a4f4b7d8923f6c69c5c9457",
- "sha256:c7cf862aef988b5fbaa17764ad1d21b4831436701c7d2b653156a9497d92c83c",
- "sha256:e0626db16705ab9f7fa6c249c017c887baf20738ce7f9129da162bb3075fc1af",
- "sha256:f34495079c8d9da05b183f9f7daec2878280c2ad7cc81da686ef0b484cea2ecf",
- "sha256:fe523fcbd52c05040c7bee370d66fee8373c5972171e4fbc323153433198592d"
+ "sha256:023fe9e618182ca6317ae89833ba422c411469156b690fde6a315ad10695a521",
+ "sha256:031fc69c9a7e12bcc5660b74122ed84b3f1c505e762cc4296884096c6d8ee140",
+ "sha256:2de7babe398cb7a85ac7f1fd5c42f396c215ab3eff731b4d761d68d0f6a80f48",
+ "sha256:2e93a8a553e0394b26c4ca683923b85a69f7ccdc0139e6acd1354cc884fe0128",
+ "sha256:390bc685ec209ada4e9d35068ac6988c60160b2b703072d2850457b62499e336",
+ "sha256:3a2d219775a120581a0ae8ca392b31f238d452729adbcb6892fa89688cb8306a",
+ "sha256:3efde4af6f2d3ccf58ae825495dbb8d74abd6d176ee686ce2ab19bd025273f41",
+ "sha256:4a99fe1768925e4a139aace8f3fb66db3576ee1c30b9c0f70f744ead7e329c9f",
+ "sha256:4b41412df69ec06ab141808d12e0bf2823717b1c363bd77b4c0820feaa37249e",
+ "sha256:4c8d8c6b80aa4a1689f2a179d31d86ae1367ea4a12855cc13aa3ba24bb36b2d8",
+ "sha256:4d19f1a239d59f10fdc31263d48b7937c585810288376671eaf75380b074f238",
+ "sha256:4e4a682b3f2489d218751981639cffc4e281d548f9d517addfd5a2917ac78119",
+ "sha256:695c45cea7e8abb6f088a34a6034b1d273122e5530aeebb9c09626cea6dca4cb",
+ "sha256:701189408b460a2ff42b984e6bd45c3f41f0ac9f5f58b8873bbedc511900086d",
+ "sha256:70894c5345bea98321a2fe84df35f43ee7bb0feec117a71420c60459fc3e1eed",
+ "sha256:8293a216e902ac12779eb7a08f2bc39ec6c878d7c6025aa59464e0c4c16f7eb9",
+ "sha256:8d26b513225ffd3eacece727f4387bdce6469192ef029ca9dd469940158bc89e",
+ "sha256:a197ad3a774f8e74f21e428f0de7f60ad26a8d23437b69638aac2764d1e06a6a",
+ "sha256:bea55fc25b96c53affab852ad94bf111a3083bc1d8b0c76a61dd101d8a388cf5",
+ "sha256:c9a084bce1061e55cdc0493a2ad890375af359c766b8ac311ac8120d3a472950",
+ "sha256:d0e9464a0af6715852267bf29c9553e4555b61f5904a4fc538547a4d67617937",
+ "sha256:d8e9187bfcd5ffedbe87403195e1fc340189a68463903c39e2b63307c9fa0394",
+ "sha256:eaeaa0888b7f3ccb7bcd40b50497ca30923dba14f385bde4af78fac713d6d6f6",
+ "sha256:f46af8d162f3d470d8ffc997aaf7a269996d205f9d746124a179d3abe05ac602",
+ "sha256:f70a40410d774ae23fcb4afbbeca652905a04de7948eaf0b1789c8d1426b72d1",
+ "sha256:fe91be1c51c90e2afe6827601ca14353bbf3953f343c2129fa1e247d55fd95ba"
],
"index": "pypi",
- "version": "==1.0.0"
+ "version": "==1.2.0"
},
"mypy-extensions": {
"hashes": [
@@ -954,19 +944,19 @@
},
"packaging": {
"hashes": [
- "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2",
- "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"
+ "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61",
+ "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"
],
"markers": "python_version >= '3.7'",
- "version": "==23.0"
+ "version": "==23.1"
},
"pathspec": {
"hashes": [
- "sha256:3a66eb970cbac598f9e5ccb5b2cf58930cd8e3ed86d393d541eaf2d8b1705229",
- "sha256:64d338d4e0914e91c1792321e6907b5a593f1ab1851de7fc269557a21b30ebbc"
+ "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687",
+ "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293"
],
"markers": "python_version >= '3.7'",
- "version": "==0.11.0"
+ "version": "==0.11.1"
},
"pbr": {
"hashes": [
@@ -978,11 +968,11 @@
},
"platformdirs": {
"hashes": [
- "sha256:8a1228abb1ef82d788f74139988b137e78692984ec7b08eaa6c65f1723af28f9",
- "sha256:b1d5eb14f221506f50d6604a561f4c5786d9e80355219694a1b244bcd96f4567"
+ "sha256:47692bc24c1958e8b0f13dd727307cff1db103fca36399f457da8e05f222fdc4",
+ "sha256:7954a68d0ba23558d753f73437c55f89027cf8f5108c19844d4b82e5af396335"
],
"markers": "python_version >= '3.7'",
- "version": "==3.0.0"
+ "version": "==3.5.0"
},
"pycodestyle": {
"hashes": [
@@ -1000,6 +990,14 @@
"markers": "python_version >= '3.6'",
"version": "==3.0.1"
},
+ "pygments": {
+ "hashes": [
+ "sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c",
+ "sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1"
+ ],
+ "markers": "python_version >= '3.7'",
+ "version": "==2.15.1"
+ },
"python-dateutil": {
"hashes": [
"sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86",
@@ -1054,6 +1052,14 @@
"markers": "python_version >= '3.6'",
"version": "==6.0"
},
+ "rich": {
+ "hashes": [
+ "sha256:2d11b9b8dd03868f09b4fffadc84a6a8cda574e40dc90821bd845720ebb8e89c",
+ "sha256:69cdf53799e63f38b95b9bf9c875f8c90e78dd62b2f00c13a911c7a3b9fa4704"
+ ],
+ "markers": "python_full_version >= '3.7.0'",
+ "version": "==13.3.5"
+ },
"s3transfer": {
"hashes": [
"sha256:06176b74f3a15f61f1b4f25a1fc29a4429040b7647133a463da8fa5bd28d5ecd",
@@ -1080,19 +1086,19 @@
},
"soupsieve": {
"hashes": [
- "sha256:3b2503d3c7084a42b1ebd08116e5f81aadfaea95863628c80a3b774a11b7c759",
- "sha256:fc53893b3da2c33de295667a0e19f078c14bf86544af307354de5fcf12a3f30d"
+ "sha256:1c1bfee6819544a3447586c889157365a27e10d88cde3ad3da0cf0ddf646feb8",
+ "sha256:89d12b2d5dfcd2c9e8c22326da9d9aa9cb3dfab0a83a024f05704076ee8d35ea"
],
- "markers": "python_version >= '3.6'",
- "version": "==2.3.2.post1"
+ "markers": "python_version >= '3.7'",
+ "version": "==2.4.1"
},
"sqlparse": {
"hashes": [
- "sha256:0323c0ec29cd52bceabc1b4d9d579e311f3e4961b98d174201d5622a23b85e34",
- "sha256:69ca804846bb114d2ec380e4360a8a340db83f0ccf3afceeb1404df028f57268"
+ "sha256:5430a4fe2ac7d0f93e66f1efc6e1338a41884b7ddf2a350cedd20ccc4d9d28f3",
+ "sha256:d446183e84b8349fa3061f0fe7f06ca94ba65b426946ffebe6e3e8295332420c"
],
"markers": "python_version >= '3.5'",
- "version": "==0.4.3"
+ "version": "==0.4.4"
},
"stevedore": {
"hashes": [
@@ -1112,79 +1118,79 @@
},
"types-awscrt": {
"hashes": [
- "sha256:35e27f667e882ef7d02f1829499df30c0881d4c3ba12ff698c796b79e87ce09e",
- "sha256:8df494aff24bdce188381304d6e762b797e2e3e67f610dcb7c2a3725f08cbf67"
+ "sha256:40854d9d7ce055620d5d41e5adc84df11b879aedbd2cf20de84e73f084aa5797",
+ "sha256:fe38c6fd71199a9f739b69a7c2f3a574585457c4f63730a62830628a7bffc5b0"
],
"markers": "python_version >= '3.7' and python_version < '4.0'",
- "version": "==0.16.10"
+ "version": "==0.16.16"
},
"types-cachetools": {
"hashes": [
- "sha256:cd20f3835b7bc008522e42c685ae02d708a34042f83a2638c0ab741b2b6c454f",
- "sha256:ed00554a55a66ae62a50f8c55f71b68d76c8f375d7b9c07480f629564b5c7fac"
+ "sha256:67fa46d51a650896770aee0ba80f0e61dc4a7d1373198eec1bc0622263eaa256",
+ "sha256:c0c5fa00199017d974c935bf043c467d5204e4f835141e489b48765b5ac1d960"
],
"index": "pypi",
- "version": "==5.3.0.0"
+ "version": "==5.3.0.5"
},
"types-pytz": {
"hashes": [
- "sha256:10ec7d009a02340f1cecd654ac03f0c29b6088a03b63d164401fc52df45936b2",
- "sha256:918f9c3e7a950ba7e7d6f84b18a7cacabc8886cb7125fb1927ff1c752b4b59de"
+ "sha256:4fc2a7fbbc315f0b6630e0b899fd6c743705abe1094d007b0e612d10da15e0f3",
+ "sha256:ecdc70d543aaf3616a7e48631543a884f74205f284cefd6649ddf44c6a820aac"
],
- "version": "==2022.7.1.0"
+ "version": "==2023.3.0.0"
},
"types-pyyaml": {
"hashes": [
- "sha256:24e76b938d58e68645271eeb149af6022d1da99788e481f959bd284b164f39a1",
- "sha256:77b74d0874482f2b42dd566b7277b0a220068595e0fb42689d0c0560f3d1ae9e"
+ "sha256:5aed5aa66bd2d2e158f75dda22b059570ede988559f030cf294871d3b647e3e8",
+ "sha256:c51b1bd6d99ddf0aa2884a7a328810ebf70a4262c292195d3f4f9a0005f9eeb6"
],
- "version": "==6.0.12.6"
+ "version": "==6.0.12.9"
},
"types-requests": {
"hashes": [
- "sha256:dbc2933635860e553ffc59f5e264264981358baffe6342b925e3eb8261f866ee",
- "sha256:fd530aab3fc4f05ee36406af168f0836e6f00f1ee51a0b96b7311f82cb675230"
+ "sha256:0d580652ce903f643f8c3b494dd01d29367ea57cea0c7ad7f65cf3169092edb0",
+ "sha256:cc1aba862575019306b2ed134eb1ea994cab1c887a22e18d3383e6dd42e9789b"
],
"index": "pypi",
- "version": "==2.28.11.12"
+ "version": "==2.28.11.17"
},
"types-s3transfer": {
"hashes": [
- "sha256:2cf1e07cf4d1a5a2a68d89c654f45d9c3b678d39f7fe03a6f36903b6dbd3bcbc",
- "sha256:cfcbee11c16d60af3feb3dbffea3a85b32129235b562912dece310a45ae83a2c"
+ "sha256:40e665643f0647832d51c4a26d8a8275cda9134b02bf22caf28198b79bcad382",
+ "sha256:d9c669b30fdd61347720434aacb8ecc4645d900712a70b10f495104f9039c07b"
],
"markers": "python_version >= '3.7' and python_version < '4.0'",
- "version": "==0.6.0.post5"
+ "version": "==0.6.0.post7"
},
"types-urllib3": {
"hashes": [
- "sha256:5630e578246d170d91ebe3901788cd28d53c4e044dc2e2488e3b0d55fb6895d8",
- "sha256:e8f25c8bb85cde658c72ee931e56e7abd28803c26032441eea9ff4a4df2b0c31"
+ "sha256:04235e792139cf3624b25d38faab593456738fbdb7439634046172e3b1339400",
+ "sha256:697102ddf4f781eed6f692353f40cee1098643526f5a8b99f49d2ede90fd3754"
],
- "version": "==1.26.25.5"
+ "version": "==1.26.25.11"
},
"typing-extensions": {
"hashes": [
- "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa",
- "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"
+ "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb",
+ "sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4"
],
"markers": "python_version >= '3.7'",
- "version": "==4.4.0"
+ "version": "==4.5.0"
},
"urllib3": {
"hashes": [
- "sha256:076907bf8fd355cde77728471316625a4d2f7e713c125f51953bb5b3eecf4f72",
- "sha256:75edcdc2f7d85b137124a6c3c9fc3933cdeaa12ecb9a6a959f22797a0feca7e1"
+ "sha256:8a388717b9476f934a21484e8c8e61875ab60644d29b9b39e11e4b9dc1c6b305",
+ "sha256:aa751d169e23c7479ce47a0cb0da579e3ede798f994f5816a74e4f4500dcea42"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
- "version": "==1.26.14"
+ "version": "==1.26.15"
},
"waitress": {
"hashes": [
"sha256:7500c9625927c8ec60f54377d590f67b30c8e70ef4b8894214ac6e4cad233d2a",
"sha256:780a4082c5fbc0fde6a2fcfe5e26e6efc1e8f425730863c04085769781f51eba"
],
- "markers": "python_version >= '3.7'",
+ "markers": "python_full_version >= '3.7.0'",
"version": "==2.1.2"
},
"webob": {
@@ -1200,7 +1206,7 @@
"sha256:2a001a9efa40d2a7e5d9cd8d1527c75f41814eb6afce2c3d207402547b1e5ead",
"sha256:54bd969725838d9861a9fa27f8d971f79d275d94ae255f5c501f53bb6d9929eb"
],
- "markers": "python_version >= '3.6' and python_version < '4.0'",
+ "markers": "python_version >= '3.6' and python_version < '4'",
"version": "==3.0.0"
}
}
diff --git a/src/djangooidc/tests/test_views.py b/src/djangooidc/tests/test_views.py
index a3086db50..30caf3713 100644
--- a/src/djangooidc/tests/test_views.py
+++ b/src/djangooidc/tests/test_views.py
@@ -49,7 +49,7 @@ class ViewsTest(TestCase):
# assert
self.assertEqual(response.status_code, 500)
self.assertTemplateUsed(response, "500.html")
- self.assertIn("Server Error", response.content.decode("utf-8"))
+ self.assertIn("server error", response.content.decode("utf-8"))
def test_login_callback_reads_next(self, mock_client):
# setup
diff --git a/src/docker-compose.yml b/src/docker-compose.yml
index 636243617..4399c5b70 100644
--- a/src/docker-compose.yml
+++ b/src/docker-compose.yml
@@ -62,6 +62,9 @@ services:
- POSTGRES_PASSWORD=feedabee
node:
+ build:
+ context: .
+ dockerfile: node.Dockerfile
image: node
volumes:
- .:/app
diff --git a/src/registrar/admin.py b/src/registrar/admin.py
index 30c8e8b89..439dfd9f9 100644
--- a/src/registrar/admin.py
+++ b/src/registrar/admin.py
@@ -55,6 +55,7 @@ admin.site.register(models.UserDomainRole, AuditedAdmin)
admin.site.register(models.Contact, AuditedAdmin)
admin.site.register(models.DomainInvitation, AuditedAdmin)
admin.site.register(models.DomainApplication, AuditedAdmin)
+admin.site.register(models.DomainInformation, AuditedAdmin)
admin.site.register(models.Domain, AuditedAdmin)
admin.site.register(models.Host, MyHostAdmin)
admin.site.register(models.Nameserver, MyHostAdmin)
diff --git a/src/registrar/assets/js/get-gov.js b/src/registrar/assets/js/get-gov.js
index c9f6d77af..57dc6d2e3 100644
--- a/src/registrar/assets/js/get-gov.js
+++ b/src/registrar/assets/js/get-gov.js
@@ -230,4 +230,34 @@ function handleValidationClick(e) {
})();
+/**
+ * An IIFE that attaches a click handler for our dynamic nameservers form
+ *
+ * Only does something on a single page, but it should be fast enough to run
+ * it everywhere.
+ */
+(function prepareForms() {
+ let serverForm = document.querySelectorAll(".server-form")
+ let container = document.querySelector("#form-container")
+ let addButton = document.querySelector("#add-form")
+ let totalForms = document.querySelector("#id_form-TOTAL_FORMS")
+ let formNum = serverForm.length-1
+ addButton.addEventListener('click', addForm)
+
+ function addForm(e){
+ let newForm = serverForm[2].cloneNode(true)
+ let formNumberRegex = RegExp(`form-(\\d){1}-`,'g')
+ let formLabelRegex = RegExp(`Name server (\\d){1}`, 'g')
+ let formExampleRegex = RegExp(`ns(\\d){1}`, 'g')
+
+ formNum++
+ newForm.innerHTML = newForm.innerHTML.replace(formNumberRegex, `form-${formNum}-`)
+ newForm.innerHTML = newForm.innerHTML.replace(formLabelRegex, `Name server ${formNum+1}`)
+ newForm.innerHTML = newForm.innerHTML.replace(formExampleRegex, `ns${formNum+1}`)
+ container.insertBefore(newForm, addButton)
+ newForm.querySelector("input").value = ""
+
+ totalForms.setAttribute('value', `${formNum+1}`)
+ }
+})();
diff --git a/src/registrar/assets/sass/_theme/_uswds-theme-custom-styles.scss b/src/registrar/assets/sass/_theme/_uswds-theme-custom-styles.scss
index d43e82c9b..ff2614fb8 100644
--- a/src/registrar/assets/sass/_theme/_uswds-theme-custom-styles.scss
+++ b/src/registrar/assets/sass/_theme/_uswds-theme-custom-styles.scss
@@ -417,8 +417,18 @@ footer {
color: color('primary');
}
+.usa-identifier__logo {
+ height: units(7);
+}
+
abbr[title] {
// workaround for underlining abbr element
border-bottom: none;
text-decoration: none;
}
+
+.usa-textarea {
+ @include at-media('tablet') {
+ height: units('mobile');
+ }
+}
\ No newline at end of file
diff --git a/src/registrar/config/urls.py b/src/registrar/config/urls.py
index 511290583..204e5ca92 100644
--- a/src/registrar/config/urls.py
+++ b/src/registrar/config/urls.py
@@ -78,6 +78,11 @@ urlpatterns = [
),
path("domain/", views.DomainView.as_view(), name="domain"),
path("domain//users", views.DomainUsersView.as_view(), name="domain-users"),
+ path(
+ "domain//nameservers",
+ views.DomainNameserversView.as_view(),
+ name="domain-nameservers",
+ ),
path(
"domain//users/add",
views.DomainAddUserView.as_view(),
diff --git a/src/registrar/fixtures.py b/src/registrar/fixtures.py
index ee226b7cb..0161527e1 100644
--- a/src/registrar/fixtures.py
+++ b/src/registrar/fixtures.py
@@ -44,6 +44,21 @@ class UserFixture:
"first_name": "Neil",
"last_name": "Martinsen-Burrell",
},
+ {
+ "username": "7185e6cd-d3c8-4adc-90a3-ceddba71d24f",
+ "first_name": "Jon",
+ "last_name": "Roberts",
+ },
+ {
+ "username": "5f283494-31bd-49b5-b024-a7e7cae00848",
+ "first_name": "Rachid",
+ "last_name": "Mrad",
+ },
+ {
+ "username": "eb2214cd-fc0c-48c0-9dbd-bc4cd6820c74",
+ "first_name": "Alysia",
+ "last_name": "Broddrick",
+ },
]
@classmethod
diff --git a/src/registrar/forms/__init__.py b/src/registrar/forms/__init__.py
index d48dd037b..6c1b5d8cf 100644
--- a/src/registrar/forms/__init__.py
+++ b/src/registrar/forms/__init__.py
@@ -1,2 +1,2 @@
from .application_wizard import *
-from .domain import DomainAddUserForm
+from .domain import DomainAddUserForm, NameserverFormset
diff --git a/src/registrar/forms/application_wizard.py b/src/registrar/forms/application_wizard.py
index ce3ef6e9f..333c9c6b1 100644
--- a/src/registrar/forms/application_wizard.py
+++ b/src/registrar/forms/application_wizard.py
@@ -5,7 +5,7 @@ from typing import Callable
from phonenumber_field.formfields import PhoneNumberField # type: ignore
from django import forms
-from django.core.validators import RegexValidator
+from django.core.validators import RegexValidator, MaxLengthValidator
from django.urls import reverse
from django.utils.safestring import mark_safe
@@ -315,6 +315,12 @@ class TypeOfWorkForm(RegistrarForm):
# label has to end in a space to get the label_suffix to show
label="What type of work does your organization do? ",
widget=forms.Textarea(),
+ validators=[
+ MaxLengthValidator(
+ 1000,
+ message="Response must be less than 1000 characters.",
+ )
+ ],
error_messages={"required": "Enter the type of work your organization does."},
)
@@ -327,6 +333,12 @@ class TypeOfWorkForm(RegistrarForm):
" support your claims. "
),
widget=forms.Textarea(),
+ validators=[
+ MaxLengthValidator(
+ 1000,
+ message="Response must be less than 1000 characters.",
+ )
+ ],
error_messages={
"required": (
"Describe how your organization is independent of a state government."
@@ -554,6 +566,12 @@ class PurposeForm(RegistrarForm):
purpose = forms.CharField(
label="Purpose",
widget=forms.Textarea(),
+ validators=[
+ MaxLengthValidator(
+ 1000,
+ message="Response must be less than 1000 characters.",
+ )
+ ],
error_messages={
"required": "Describe how you'll use the .gov domain you’re requesting."
},
@@ -696,6 +714,12 @@ class AnythingElseForm(RegistrarForm):
required=False,
label="Anything else we should know?",
widget=forms.Textarea(),
+ validators=[
+ MaxLengthValidator(
+ 1000,
+ message="Response must be less than 1000 characters.",
+ )
+ ],
)
diff --git a/src/registrar/forms/domain.py b/src/registrar/forms/domain.py
index 3d5941eed..cca0bf5c9 100644
--- a/src/registrar/forms/domain.py
+++ b/src/registrar/forms/domain.py
@@ -1,6 +1,7 @@
"""Forms for domain management."""
from django import forms
+from django.forms import formset_factory
class DomainAddUserForm(forms.Form):
@@ -8,3 +9,16 @@ class DomainAddUserForm(forms.Form):
"""Form for adding a user to a domain."""
email = forms.EmailField(label="Email")
+
+
+class DomainNameserverForm(forms.Form):
+
+ """Form for changing nameservers."""
+
+ server = forms.CharField(label="Name server")
+
+
+NameserverFormset = formset_factory(
+ DomainNameserverForm,
+ extra=1,
+)
diff --git a/src/registrar/migrations/0018_domaininformation.py b/src/registrar/migrations/0018_domaininformation.py
new file mode 100644
index 000000000..408fa048b
--- /dev/null
+++ b/src/registrar/migrations/0018_domaininformation.py
@@ -0,0 +1,273 @@
+# Generated by Django 4.1.6 on 2023-05-08 15:30
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("registrar", "0017_alter_domainapplication_status_and_more"),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name="DomainInformation",
+ fields=[
+ (
+ "id",
+ models.BigAutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ ("created_at", models.DateTimeField(auto_now_add=True)),
+ ("updated_at", models.DateTimeField(auto_now=True)),
+ (
+ "organization_type",
+ models.CharField(
+ blank=True,
+ choices=[
+ (
+ "federal",
+ "Federal: an agency of the U.S. government's executive, legislative, or judicial branches",
+ ),
+ (
+ "interstate",
+ "Interstate: an organization of two or more states",
+ ),
+ (
+ "state_or_territory",
+ "State or territory: one of the 50 U.S. states, the District of Columbia, American Samoa, Guam, Northern Mariana Islands, Puerto Rico, or the U.S. Virgin Islands",
+ ),
+ (
+ "tribal",
+ "Tribal: a tribal government recognized by the federal or a state government",
+ ),
+ ("county", "County: a county, parish, or borough"),
+ ("city", "City: a city, town, township, village, etc."),
+ (
+ "special_district",
+ "Special district: an independent organization within a single state",
+ ),
+ (
+ "school_district",
+ "School district: a school district that is not part of a local government",
+ ),
+ ],
+ help_text="Type of Organization",
+ max_length=255,
+ null=True,
+ ),
+ ),
+ (
+ "federally_recognized_tribe",
+ models.BooleanField(
+ help_text="Is the tribe federally recognized", null=True
+ ),
+ ),
+ (
+ "state_recognized_tribe",
+ models.BooleanField(
+ help_text="Is the tribe recognized by a state", null=True
+ ),
+ ),
+ (
+ "tribe_name",
+ models.TextField(blank=True, help_text="Name of tribe", null=True),
+ ),
+ (
+ "federal_agency",
+ models.TextField(blank=True, help_text="Federal agency", null=True),
+ ),
+ (
+ "federal_type",
+ models.CharField(
+ blank=True,
+ choices=[
+ ("executive", "Executive"),
+ ("judicial", "Judicial"),
+ ("legislative", "Legislative"),
+ ],
+ help_text="Federal government branch",
+ max_length=50,
+ null=True,
+ ),
+ ),
+ (
+ "is_election_board",
+ models.BooleanField(
+ blank=True,
+ help_text="Is your organization an election office?",
+ null=True,
+ ),
+ ),
+ (
+ "organization_name",
+ models.TextField(
+ blank=True,
+ db_index=True,
+ help_text="Organization name",
+ null=True,
+ ),
+ ),
+ (
+ "address_line1",
+ models.TextField(blank=True, help_text="Street address", null=True),
+ ),
+ (
+ "address_line2",
+ models.CharField(
+ blank=True,
+ help_text="Street address line 2",
+ max_length=15,
+ null=True,
+ ),
+ ),
+ ("city", models.TextField(blank=True, help_text="City", null=True)),
+ (
+ "state_territory",
+ models.CharField(
+ blank=True,
+ help_text="State, territory, or military post",
+ max_length=2,
+ null=True,
+ ),
+ ),
+ (
+ "zipcode",
+ models.CharField(
+ blank=True,
+ db_index=True,
+ help_text="Zip code",
+ max_length=10,
+ null=True,
+ ),
+ ),
+ (
+ "urbanization",
+ models.TextField(
+ blank=True,
+ help_text="Urbanization (Puerto Rico only)",
+ null=True,
+ ),
+ ),
+ (
+ "type_of_work",
+ models.TextField(
+ blank=True,
+ help_text="Type of work of the organization",
+ null=True,
+ ),
+ ),
+ (
+ "more_organization_information",
+ models.TextField(
+ blank=True,
+ help_text="Further information about the government organization",
+ null=True,
+ ),
+ ),
+ (
+ "purpose",
+ models.TextField(
+ blank=True, help_text="Purpose of your domain", null=True
+ ),
+ ),
+ (
+ "no_other_contacts_rationale",
+ models.TextField(
+ blank=True,
+ help_text="Reason for listing no additional contacts",
+ null=True,
+ ),
+ ),
+ (
+ "anything_else",
+ models.TextField(
+ blank=True, help_text="Anything else we should know?", null=True
+ ),
+ ),
+ (
+ "is_policy_acknowledged",
+ models.BooleanField(
+ blank=True,
+ help_text="Acknowledged .gov acceptable use policy",
+ null=True,
+ ),
+ ),
+ (
+ "security_email",
+ models.EmailField(
+ blank=True,
+ help_text="Security email for public use",
+ max_length=320,
+ null=True,
+ ),
+ ),
+ (
+ "authorizing_official",
+ models.ForeignKey(
+ blank=True,
+ null=True,
+ on_delete=django.db.models.deletion.PROTECT,
+ related_name="information_authorizing_official",
+ to="registrar.contact",
+ ),
+ ),
+ (
+ "creator",
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.PROTECT,
+ related_name="information_created",
+ to=settings.AUTH_USER_MODEL,
+ ),
+ ),
+ (
+ "domain",
+ models.OneToOneField(
+ blank=True,
+ help_text="Domain to which this information belongs",
+ null=True,
+ on_delete=django.db.models.deletion.PROTECT,
+ related_name="domain_info",
+ to="registrar.domain",
+ ),
+ ),
+ (
+ "domain_application",
+ models.OneToOneField(
+ blank=True,
+ help_text="Associated domain application",
+ null=True,
+ on_delete=django.db.models.deletion.PROTECT,
+ related_name="domainapplication_info",
+ to="registrar.domainapplication",
+ ),
+ ),
+ (
+ "other_contacts",
+ models.ManyToManyField(
+ blank=True,
+ related_name="contact_applications_information",
+ to="registrar.contact",
+ ),
+ ),
+ (
+ "submitter",
+ models.ForeignKey(
+ blank=True,
+ null=True,
+ on_delete=django.db.models.deletion.PROTECT,
+ related_name="submitted_applications_information",
+ to="registrar.contact",
+ ),
+ ),
+ ],
+ options={
+ "verbose_name_plural": "Domain Information",
+ },
+ ),
+ ]
diff --git a/src/registrar/migrations/0019_alter_domainapplication_organization_type.py b/src/registrar/migrations/0019_alter_domainapplication_organization_type.py
new file mode 100644
index 000000000..1a7397255
--- /dev/null
+++ b/src/registrar/migrations/0019_alter_domainapplication_organization_type.py
@@ -0,0 +1,47 @@
+# Generated by Django 4.1.6 on 2023-05-09 19:50
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ("registrar", "0018_domaininformation"),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name="domainapplication",
+ name="organization_type",
+ field=models.CharField(
+ blank=True,
+ choices=[
+ (
+ "federal",
+ "Federal: an agency of the U.S. government's executive, legislative, or judicial branches",
+ ),
+ ("interstate", "Interstate: an organization of two or more states"),
+ (
+ "state_or_territory",
+ "State or territory: one of the 50 U.S. states, the District of Columbia, American Samoa, Guam, Northern Mariana Islands, Puerto Rico, or the U.S. Virgin Islands",
+ ),
+ (
+ "tribal",
+ "Tribal: a tribal government recognized by the federal or a state government",
+ ),
+ ("county", "County: a county, parish, or borough"),
+ ("city", "City: a city, town, township, village, etc."),
+ (
+ "special_district",
+ "Special district: an independent organization within a single state",
+ ),
+ (
+ "school_district",
+ "School district: a school district that is not part of a local government",
+ ),
+ ],
+ help_text="Type of organization",
+ max_length=255,
+ null=True,
+ ),
+ ),
+ ]
diff --git a/src/registrar/models/__init__.py b/src/registrar/models/__init__.py
index 0fcfeca40..9351e312d 100644
--- a/src/registrar/models/__init__.py
+++ b/src/registrar/models/__init__.py
@@ -2,6 +2,7 @@ from auditlog.registry import auditlog # type: ignore
from .contact import Contact
from .domain_application import DomainApplication
+from .domain_information import DomainInformation
from .domain import Domain
from .host_ip import HostIP
from .host import Host
@@ -15,6 +16,7 @@ from .website import Website
__all__ = [
"Contact",
"DomainApplication",
+ "DomainInformation",
"Domain",
"DomainInvitation",
"HostIP",
diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py
index dcf103586..7d287595a 100644
--- a/src/registrar/models/domain.py
+++ b/src/registrar/models/domain.py
@@ -1,6 +1,8 @@
import logging
import re
+from typing import List
+
from django.apps import apps
from django.core.exceptions import ValidationError
from django.db import models
@@ -215,6 +217,24 @@ class Domain(TimeStampedModel):
def __str__(self) -> str:
return self.name
+ def nameservers(self) -> List[str]:
+ """A list of the nameservers for this domain.
+
+ TODO: call EPP to get this info instead of returning fake data.
+ """
+ return [
+ # reserved example domain
+ "ns1.example.com",
+ "ns2.example.com",
+ "ns3.example.com",
+ ]
+
+ def set_nameservers(self, new_nameservers: List[str]):
+ """Set the nameservers for this domain."""
+ # TODO: call EPP to set these values in the registry instead of doing
+ # nothing.
+ logger.warn("TODO: Fake setting nameservers to %s", new_nameservers)
+
@property
def roid(self):
return self._get_property("roid")
diff --git a/src/registrar/models/domain_application.py b/src/registrar/models/domain_application.py
index ca7f4d76f..44c93892c 100644
--- a/src/registrar/models/domain_application.py
+++ b/src/registrar/models/domain_application.py
@@ -9,7 +9,7 @@ from django_fsm import FSMField, transition # type: ignore
from .utility.time_stamped_model import TimeStampedModel
from ..utility.email import send_templated_email, EmailSendingError
-
+from itertools import chain
logger = logging.getLogger(__name__)
@@ -520,6 +520,10 @@ class DomainApplication(TimeStampedModel):
Domain = apps.get_model("registrar.Domain")
created_domain, _ = Domain.objects.get_or_create(name=self.requested_domain)
+ # copy the information from domainapplication into domaininformation
+ DomainInformation = apps.get_model("registrar.DomainInformation")
+ DomainInformation.create_from_da(self)
+
# create the permission for the user
UserDomainRole = apps.get_model("registrar.UserDomainRole")
UserDomainRole.objects.get_or_create(
@@ -577,3 +581,26 @@ class DomainApplication(TimeStampedModel):
if self.organization_type == DomainApplication.OrganizationChoices.FEDERAL:
return True
return False
+
+ def to_dict(self):
+ """This is to process to_dict for Domain Information, making it friendly
+ to "copy" it
+
+ More information can be found at this- (This used #5)
+ https://stackoverflow.com/questions/21925671/convert-django-model-object-to-dict-with-all-of-the-fields-intact/29088221#29088221
+ """ # noqa 590
+ opts = self._meta
+ data = {}
+ for field in chain(opts.concrete_fields, opts.private_fields):
+ if field.get_internal_type() in ("ForeignKey", "OneToOneField"):
+ # get the related instance of the FK value
+ fk_id = field.value_from_object(self)
+ if fk_id:
+ data[field.name] = field.related_model.objects.get(id=fk_id)
+ else:
+ data[field.name] = None
+ else:
+ data[field.name] = field.value_from_object(self)
+ for field in opts.many_to_many:
+ data[field.name] = field.value_from_object(self)
+ return data
diff --git a/src/registrar/models/domain_information.py b/src/registrar/models/domain_information.py
new file mode 100644
index 000000000..6561a82b4
--- /dev/null
+++ b/src/registrar/models/domain_information.py
@@ -0,0 +1,250 @@
+from __future__ import annotations
+from .domain_application import DomainApplication
+from .utility.time_stamped_model import TimeStampedModel
+
+import logging
+
+from django.db import models
+
+
+logger = logging.getLogger(__name__)
+
+
+class DomainInformation(TimeStampedModel):
+
+ """A registrant's domain information for that domain, exported from
+ DomainApplication. We use these field from DomainApplication with few exceptation
+ which are 'removed' via pop at the bottom of this file. Most of design for domain
+ management's user information are based on application, but we cannot change
+ the application once approved, so copying them that way we can make changes
+ after its approved. Most fields here are copied from Application."""
+
+ StateTerritoryChoices = DomainApplication.StateTerritoryChoices
+
+ OrganizationChoices = DomainApplication.OrganizationChoices
+
+ BranchChoices = DomainApplication.BranchChoices
+
+ AGENCY_CHOICES = DomainApplication.AGENCY_CHOICES
+
+ # This is the application user who created this application. The contact
+ # information that they gave is in the `submitter` field
+ creator = models.ForeignKey(
+ "registrar.User",
+ on_delete=models.PROTECT,
+ related_name="information_created",
+ )
+
+ domain_application = models.OneToOneField(
+ "registrar.DomainApplication",
+ on_delete=models.PROTECT,
+ blank=True,
+ null=True,
+ related_name="domainapplication_info",
+ help_text="Associated domain application",
+ unique=True,
+ )
+
+ # ##### data fields from the initial form #####
+ organization_type = models.CharField(
+ max_length=255,
+ choices=OrganizationChoices.choices,
+ null=True,
+ blank=True,
+ help_text="Type of Organization",
+ )
+
+ federally_recognized_tribe = models.BooleanField(
+ null=True,
+ help_text="Is the tribe federally recognized",
+ )
+
+ state_recognized_tribe = models.BooleanField(
+ null=True,
+ help_text="Is the tribe recognized by a state",
+ )
+
+ tribe_name = models.TextField(
+ null=True,
+ blank=True,
+ help_text="Name of tribe",
+ )
+
+ federal_agency = models.TextField(
+ null=True,
+ blank=True,
+ help_text="Federal agency",
+ )
+
+ federal_type = models.CharField(
+ max_length=50,
+ choices=BranchChoices.choices,
+ null=True,
+ blank=True,
+ help_text="Federal government branch",
+ )
+
+ is_election_board = models.BooleanField(
+ null=True,
+ blank=True,
+ help_text="Is your organization an election office?",
+ )
+
+ organization_name = models.TextField(
+ null=True,
+ blank=True,
+ help_text="Organization name",
+ db_index=True,
+ )
+ address_line1 = models.TextField(
+ null=True,
+ blank=True,
+ help_text="Street address",
+ )
+ address_line2 = models.CharField(
+ max_length=15,
+ null=True,
+ blank=True,
+ help_text="Street address line 2",
+ )
+ city = models.TextField(
+ null=True,
+ blank=True,
+ help_text="City",
+ )
+ state_territory = models.CharField(
+ max_length=2,
+ null=True,
+ blank=True,
+ help_text="State, territory, or military post",
+ )
+ zipcode = models.CharField(
+ max_length=10,
+ null=True,
+ blank=True,
+ help_text="Zip code",
+ db_index=True,
+ )
+ urbanization = models.TextField(
+ null=True,
+ blank=True,
+ help_text="Urbanization (Puerto Rico only)",
+ )
+
+ type_of_work = models.TextField(
+ null=True,
+ blank=True,
+ help_text="Type of work of the organization",
+ )
+
+ more_organization_information = models.TextField(
+ null=True,
+ blank=True,
+ help_text="Further information about the government organization",
+ )
+
+ authorizing_official = models.ForeignKey(
+ "registrar.Contact",
+ null=True,
+ blank=True,
+ related_name="information_authorizing_official",
+ on_delete=models.PROTECT,
+ )
+
+ domain = models.OneToOneField(
+ "registrar.Domain",
+ on_delete=models.PROTECT,
+ blank=True,
+ null=True,
+ # Access this information via Domain as "domain.domain_info"
+ related_name="domain_info",
+ help_text="Domain to which this information belongs",
+ )
+
+ # This is the contact information provided by the applicant. The
+ # application user who created it is in the `creator` field.
+ submitter = models.ForeignKey(
+ "registrar.Contact",
+ null=True,
+ blank=True,
+ related_name="submitted_applications_information",
+ on_delete=models.PROTECT,
+ )
+
+ purpose = models.TextField(
+ null=True,
+ blank=True,
+ help_text="Purpose of your domain",
+ )
+
+ other_contacts = models.ManyToManyField(
+ "registrar.Contact",
+ blank=True,
+ related_name="contact_applications_information",
+ )
+
+ no_other_contacts_rationale = models.TextField(
+ null=True,
+ blank=True,
+ help_text="Reason for listing no additional contacts",
+ )
+
+ anything_else = models.TextField(
+ null=True,
+ blank=True,
+ help_text="Anything else we should know?",
+ )
+
+ is_policy_acknowledged = models.BooleanField(
+ null=True,
+ blank=True,
+ help_text="Acknowledged .gov acceptable use policy",
+ )
+ security_email = models.EmailField(
+ max_length=320,
+ null=True,
+ blank=True,
+ help_text="Security email for public use",
+ )
+
+ def __str__(self):
+ try:
+ if self.domain and self.domain.name:
+ return self.domain.name
+ else:
+ return f"domain info set up and created by {self.creator}"
+ except Exception:
+ return ""
+
+ @classmethod
+ def create_from_da(cls, domain_application):
+ """Takes in a DomainApplication dict and converts it into DomainInformation"""
+ da_dict = domain_application.to_dict()
+ # remove the id so one can be assinged on creation
+ da_id = da_dict.pop("id")
+ # check if we have a record that corresponds with the domain
+ # application, if so short circuit the create
+ domain_info = cls.objects.filter(domain_application__id=da_id).first()
+ if domain_info:
+ return domain_info
+ # the following information below is not needed in the domain information:
+ da_dict.pop("status")
+ da_dict.pop("current_websites")
+ da_dict.pop("investigator")
+ da_dict.pop("alternative_domains")
+ # use the requested_domain to create information for this domain
+ da_dict["domain"] = da_dict.pop("requested_domain")
+ other_contacts = da_dict.pop("other_contacts")
+ domain_info = cls(**da_dict)
+ domain_info.domain_application = domain_application
+ # Save so the object now have PK
+ # (needed to process the manytomany below before, first)
+ domain_info.save()
+
+ # Process the remaining "many to many" stuff
+ domain_info.other_contacts.add(*other_contacts)
+ domain_info.save()
+ return domain_info
+
+ class Meta:
+ verbose_name_plural = "Domain Information"
diff --git a/src/registrar/public/img/registrar/dotgov_401_illo.svg b/src/registrar/public/img/registrar/dotgov_401_illo.svg
new file mode 100644
index 000000000..71de33eaa
--- /dev/null
+++ b/src/registrar/public/img/registrar/dotgov_401_illo.svg
@@ -0,0 +1,20 @@
+
diff --git a/src/registrar/public/img/registrar/dotgov_404_illo.svg b/src/registrar/public/img/registrar/dotgov_404_illo.svg
new file mode 100644
index 000000000..3c9adab7e
--- /dev/null
+++ b/src/registrar/public/img/registrar/dotgov_404_illo.svg
@@ -0,0 +1,18 @@
+
diff --git a/src/registrar/public/img/registrar/dotgov_500_illo.svg b/src/registrar/public/img/registrar/dotgov_500_illo.svg
new file mode 100644
index 000000000..6dd538644
--- /dev/null
+++ b/src/registrar/public/img/registrar/dotgov_500_illo.svg
@@ -0,0 +1,59 @@
+
diff --git a/src/registrar/templates/401.html b/src/registrar/templates/401.html
index 64bcec563..9fe0194ed 100644
--- a/src/registrar/templates/401.html
+++ b/src/registrar/templates/401.html
@@ -1,28 +1,45 @@
{% extends "base.html" %}
-{% load i18n %}
+{% load i18n static %}
-{% block title %}{% translate "Unauthorized" %}{% endblock %}
+{% block title %}{% translate "Unauthorized | " %}{% endblock %}
{% block content %}
- {% translate "Unauthorized" %}
+
+
+
+ {% translate "You are not authorized to view this page" %}
+
+
+ {% translate "Status 401" %}
+
- {% if friendly_message %}
-
{{ friendly_message }}
- {% else %}
-
{% translate "Authorization failed." %}
- {% endif %}
-
- {% translate "Would you like to try logging in again?" %}
-
+ {% if friendly_message %}
+
{{ friendly_message }}
+ {% else %}
+
{% translate "Authorization failed." %}
+ {% endif %}
+
+ You must be an authorized user and need to be signed in to view this page.
+ Would you like to try logging in again?
+
+
+ If you would like help with this error contact us
+
- {% if log_identifier %}
-
Here's a unique identifier for this error.
-
{{ log_identifier }}
-
{% translate "Please include it if you contact us." %}
- {% endif %}
-
- TODO: Content team to create a "how to contact us" footer for the error pages
+ {% if log_identifier %}
+
Here's a unique identifier for this error.
+
{{ log_identifier }}
+
{% translate "Please include it if you contact us." %}
+ {% endif %}
+
+
+

+
+
{% endblock %}
diff --git a/src/registrar/templates/403.html b/src/registrar/templates/403.html
new file mode 100644
index 000000000..cc8c98656
--- /dev/null
+++ b/src/registrar/templates/403.html
@@ -0,0 +1,45 @@
+{% extends "base.html" %}
+{% load i18n static %}
+
+{% block title %}{% translate "Forbidden | " %}{% endblock %}
+
+{% block content %}
+
+
+
+
+ {% translate "You do not have the right permissions to view this page." %}
+
+
+ {% translate "Status 403" %}
+
+
+
+ {% if friendly_message %}
+
{{ friendly_message }}
+ {% else %}
+
{% translate "Forbidden." %}
+ {% endif %}
+
+ You must be an authorized user and need to be signed in to view this page.
+ Would you like to try logging in again?
+
+
+ If you would like help with this error contact us
+
+
+ {% if log_identifier %}
+
Here's a unique identifier for this error.
+
{{ log_identifier }}
+
{% translate "Please include it if you contact us." %}
+ {% endif %}
+
+
+

+
+
+
+{% endblock %}
diff --git a/src/registrar/templates/404.html b/src/registrar/templates/404.html
index f715e0fa9..76a301187 100644
--- a/src/registrar/templates/404.html
+++ b/src/registrar/templates/404.html
@@ -1,15 +1,31 @@
-
{% extends "base.html" %}
-{% load i18n %}
+{% load i18n static %}
-{% block title %}{% translate "Page not found" %}{% endblock %}
+{% block title %}{% translate "Page not found | " %}{% endblock %}
{% block content %}
+
+
+
+ {% translate "We couldn’t find that page" %}
+
+
+ {% translate "Status 404" %}
+
-
{% translate "Page not found" %}
+
Try going to the homepage. If you can’t find what you’re looking for, contact us.
+
+
+
+
+

+
+
- {% translate "The requested page could not be found." %}
{% endblock %}
diff --git a/src/registrar/templates/500.html b/src/registrar/templates/500.html
index 5fbd30d2d..7b7e1dfed 100644
--- a/src/registrar/templates/500.html
+++ b/src/registrar/templates/500.html
@@ -1,24 +1,39 @@
{% extends "base.html" %}
-{% load i18n %}
+{% load i18n static %}
-{% block title %}{% translate "Server error" %}{% endblock %}
+{% block title %}{% translate "Server error | " %}{% endblock %}
{% block content %}
- {% translate "Server Error" %}
+
+
+
+ {% translate "We're having some trouble" %}
+
+
+ {% translate "Status 500 – server error" %}
+
+ {% if friendly_message %}
+
{{ friendly_message }}
+ {% else %}
+
+ Sorry! Try waiting a few minutes and then reloading the page.
+ Contact us if you need help.
+
+ {% endif %}
- {% if friendly_message %}
-
{{ friendly_message }}
- {% else %}
-
{% translate "An internal server error occurred." %}
- {% endif %}
-
- {% if log_identifier %}
-
Here's a unique identifier for this error.
-
{{ log_identifier }}
-
{% translate "Please include it if you contact us." %}
- {% endif %}
-
- TODO: Content team to create a "how to contact us" footer for the error pages
+ {% if log_identifier %}
+
Here's a unique identifier for this error.
+
{{ log_identifier }}
+
{% translate "Please include it if you contact us." %}
+ {% endif %}
+
+
+

+
+
{% endblock %}
diff --git a/src/registrar/templates/application_anything_else.html b/src/registrar/templates/application_anything_else.html
index 306b2fb0b..1c598db9a 100644
--- a/src/registrar/templates/application_anything_else.html
+++ b/src/registrar/templates/application_anything_else.html
@@ -11,7 +11,7 @@
{% block form_fields %}
- {% with add_label_class="usa-sr-only" attr_maxlength=500 %}
+ {% with add_label_class="usa-sr-only" attr_maxlength=1000 %}
{% input_with_errors forms.0.anything_else %}
{% endwith %}
{% endblock %}
diff --git a/src/registrar/templates/application_purpose.html b/src/registrar/templates/application_purpose.html
index 54a68998c..a28dc27b3 100644
--- a/src/registrar/templates/application_purpose.html
+++ b/src/registrar/templates/application_purpose.html
@@ -19,7 +19,7 @@ Read about activities that are prohibited on .gov dom
{% block form_fields %}
- {% with attr_maxlength=500 add_label_class="usa-sr-only" %}
+ {% with attr_maxlength=1000 add_label_class="usa-sr-only" %}
{% input_with_errors forms.0.purpose %}
{% endwith %}
{% endblock %}
diff --git a/src/registrar/templates/application_type_of_work.html b/src/registrar/templates/application_type_of_work.html
index 1cacc1535..9ad58936f 100644
--- a/src/registrar/templates/application_type_of_work.html
+++ b/src/registrar/templates/application_type_of_work.html
@@ -3,7 +3,7 @@
{% block form_fields %}
- {% with attr_maxlength=500 %}
+ {% with attr_maxlength=1000 %}
{% input_with_errors forms.0.type_of_work %}
{% input_with_errors forms.0.more_organization_information %}
{% endwith %}
diff --git a/src/registrar/templates/django/forms/widgets/input.html b/src/registrar/templates/django/forms/widgets/input.html
index ba287df3a..f47fc6415 100644
--- a/src/registrar/templates/django/forms/widgets/input.html
+++ b/src/registrar/templates/django/forms/widgets/input.html
@@ -4,5 +4,6 @@
{# hint: spacing in the class string matters #}
class="{{ uswds_input_class }}{% if classes %} {{ classes }}{% endif %}"
{% if widget.value != None %}value="{{ widget.value|stringformat:'s' }}"{% endif %}
+ {% if sublabel_text %}aria-describedby="{{ widget.attrs.id }}__sublabel"{% endif %}
{% include "django/forms/widgets/attrs.html" %}
-/>
\ No newline at end of file
+/>
diff --git a/src/registrar/templates/domain_nameservers.html b/src/registrar/templates/domain_nameservers.html
new file mode 100644
index 000000000..9c261306a
--- /dev/null
+++ b/src/registrar/templates/domain_nameservers.html
@@ -0,0 +1,52 @@
+{% extends "domain_base.html" %}
+{% load static field_helpers%}
+
+{% block title %}Domain name servers | {{ domain.name }} | {% endblock %}
+
+{% block domain_content %}
+ {# this is right after the messages block in the parent template #}
+ {% for form in formset %}
+ {% include "includes/form_errors.html" with form=form %}
+ {% endfor %}
+
+ Domain name servers
+
+ Before your domain can be used we'll need information about your domain
+ name servers.
+
+ Get help with domain servers.
+
+ {% include "includes/required_fields.html" %}
+
+
+
+{% endblock %} {# domain_content #}
diff --git a/src/registrar/templates/domain_sidebar.html b/src/registrar/templates/domain_sidebar.html
index 7f3a66be6..8114f1be6 100644
--- a/src/registrar/templates/domain_sidebar.html
+++ b/src/registrar/templates/domain_sidebar.html
@@ -13,7 +13,7 @@
- {% url 'todo' as url %}
+ {% url 'domain-nameservers' pk=domain.id as url %}
diff --git a/src/registrar/templates/includes/footer.html b/src/registrar/templates/includes/footer.html
index 929a49cce..ea7bd8420 100644
--- a/src/registrar/templates/includes/footer.html
+++ b/src/registrar/templates/includes/footer.html
@@ -52,7 +52,7 @@
src="{% static 'img/CISA_logo.png' %}"
alt="CISA logo"
role="img"
- width="48px"
+ width="56px"
/>
+ {% endif %}
+
{% if field.errors %}
{% for error in field.errors %}
@@ -71,4 +75,4 @@ error messages, if necessary.
-{% endif %}
\ No newline at end of file
+{% endif %}
diff --git a/src/registrar/templatetags/__init__.py b/src/registrar/templatetags/__init__.py
index 013c5a89b..105ccdda1 100644
--- a/src/registrar/templatetags/__init__.py
+++ b/src/registrar/templatetags/__init__.py
@@ -6,3 +6,9 @@ from django.template.defaulttags import register
@register.filter
def get_item(dictionary, key):
return dictionary.get(key)
+
+
+@register.filter
+def concat(arg1, arg2):
+ """concatenate arg1 & arg2"""
+ return str(arg1) + str(arg2)
diff --git a/src/registrar/tests/test_forms.py b/src/registrar/tests/test_forms.py
index 53bed0b5a..94f985fab 100644
--- a/src/registrar/tests/test_forms.py
+++ b/src/registrar/tests/test_forms.py
@@ -11,6 +11,9 @@ from registrar.forms.application_wizard import (
OtherContactsForm,
RequirementsForm,
TribalGovernmentForm,
+ PurposeForm,
+ AnythingElseForm,
+ TypeOfWorkForm,
)
@@ -85,6 +88,125 @@ class TestFormValidation(TestCase):
["Enter an email address in the required format, like name@example.com."],
)
+ def test_purpose_form_character_count_invalid(self):
+ """Response must be less than 1000 characters."""
+ form = PurposeForm(
+ data={
+ "purpose": "Bacon ipsum dolor amet fatback strip steak pastrami"
+ "shankle, drumstick doner chicken landjaeger turkey andouille."
+ "Buffalo biltong chuck pork chop tongue bresaola turkey. Doner"
+ "ground round strip steak, jowl tail chuck ribeye bacon"
+ "beef ribs swine filet ball tip pancetta strip steak sirloin"
+ "mignon ham spare ribs rump. Tail shank biltong beef ribs doner"
+ "buffalo swine bacon. Tongue cow picanha brisket bacon chuck"
+ "leberkas pork loin pork, drumstick capicola. Doner short loin"
+ "ground round fatback turducken chislic shoulder turducken"
+ "spare ribs, burgdoggen kielbasa kevin frankfurter ball tip"
+ "pancetta cupim. Turkey meatball andouille porchetta hamburger"
+ "pork chop corned beef. Brisket short ribs turducken, pork chop"
+ "chislic turkey ball pork chop leberkas rump, rump bacon, jowl"
+ "tip ham. Shankle salami tongue venison short ribs kielbasa"
+ "tri-tip ham hock swine hamburger. Flank meatball corned beef"
+ "cow sausage ball tip kielbasa ham hock. Ball tip cupim meatloaf"
+ "beef ribs rump jowl tenderloin swine sausage biltong"
+ "bacon rump tail boudin meatball boudin meatball boudin."
+ }
+ )
+ self.assertEqual(
+ form.errors["purpose"],
+ ["Response must be less than 1000 characters."],
+ )
+
+ def test_anything_else_form_type_of_work_character_count_invalid(self):
+ """Response must be less than 1000 characters."""
+ form = AnythingElseForm(
+ data={
+ "anything_else": "Bacon ipsum dolor amet fatback strip steak pastrami"
+ "shankle, drumstick doner chicken landjaeger turkey andouille."
+ "Buffalo biltong chuck pork chop tongue bresaola turkey. Doner"
+ "ground round strip steak, jowl tail chuck ribeye bacon"
+ "beef ribs swine filet ball tip pancetta strip steak sirloin"
+ "mignon ham spare ribs rump. Tail shank biltong beef ribs doner"
+ "buffalo swine bacon. Tongue cow picanha brisket bacon chuck"
+ "leberkas pork loin pork, drumstick capicola. Doner short loin"
+ "ground round fatback turducken chislic shoulder turducken"
+ "spare ribs, burgdoggen kielbasa kevin frankfurter ball tip"
+ "pancetta cupim. Turkey meatball andouille porchetta hamburger"
+ "pork chop corned beef. Brisket short ribs turducken, pork chop"
+ "chislic turkey ball pork chop leberkas rump, rump bacon, jowl"
+ "tip ham. Shankle salami tongue venison short ribs kielbasa"
+ "tri-tip ham hock swine hamburger. Flank meatball corned beef"
+ "cow sausage ball tip kielbasa ham hock. Ball tip cupim meatloaf"
+ "beef ribs rump jowl tenderloin swine sausage biltong"
+ "bacon rump tail boudin meatball boudin meatball boudin."
+ }
+ )
+ self.assertEqual(
+ form.errors["anything_else"],
+ ["Response must be less than 1000 characters."],
+ )
+
+ def test_anything_else_form_more_organization_information_character_count_invalid(
+ self,
+ ):
+ """Response must be less than 1000 characters."""
+ form = TypeOfWorkForm(
+ data={
+ "more_organization_information": "Bacon ipsum dolor amet fatback"
+ "shankle, drumstick doner chicken landjaeger turkey andouille."
+ "Buffalo biltong chuck pork chop tongue bresaola turkey. Doner"
+ "ground round strip steak, jowl tail chuck ribeye bacon"
+ "beef ribs swine filet ball tip pancetta strip steak sirloin"
+ "mignon ham spare ribs rump. Tail shank biltong beef ribs doner"
+ "buffalo swine bacon. Tongue cow picanha brisket bacon chuck"
+ "leberkas pork loin pork, drumstick capicola. Doner short loin"
+ "ground round fatback turducken chislic shoulder turducken"
+ "spare ribs, burgdoggen kielbasa kevin frankfurter ball tip"
+ "pancetta cupim. Turkey meatball andouille porchetta hamburger"
+ "pork chop corned beef. Brisket short ribs turducken, pork chop"
+ "chislic turkey ball pork chop leberkas rump, rump bacon, jowl"
+ "tip ham. Shankle salami tongue venison short ribs kielbasa"
+ "tri-tip ham hock swine hamburger. Flank meatball corned beef"
+ "cow sausage ball tip kielbasa ham hock. Ball tip cupim meatloaf"
+ "beef ribs rump jowl tenderloin swine sausage biltong"
+ "bacon rump tail boudin meatball boudin meatball boudin"
+ "strip steak pastrami."
+ }
+ )
+ self.assertEqual(
+ form.errors["more_organization_information"],
+ ["Response must be less than 1000 characters."],
+ )
+
+ def test_anything_else_form_character_count_invalid(self):
+ """Response must be less than 1000 characters."""
+ form = TypeOfWorkForm(
+ data={
+ "type_of_work": "Bacon ipsum dolor amet fatback strip steak pastrami"
+ "shankle, drumstick doner chicken landjaeger turkey andouille."
+ "Buffalo biltong chuck pork chop tongue bresaola turkey. Doner"
+ "ground round strip steak, jowl tail chuck ribeye bacon"
+ "beef ribs swine filet ball tip pancetta strip steak sirloin"
+ "mignon ham spare ribs rump. Tail shank biltong beef ribs doner"
+ "buffalo swine bacon. Tongue cow picanha brisket bacon chuck"
+ "leberkas pork loin pork, drumstick capicola. Doner short loin"
+ "ground round fatback turducken chislic shoulder turducken"
+ "spare ribs, burgdoggen kielbasa kevin frankfurter ball tip"
+ "pancetta cupim. Turkey meatball andouille porchetta hamburger"
+ "pork chop corned beef. Brisket short ribs turducken, pork chop"
+ "chislic turkey ball pork chop leberkas rump, rump bacon, jowl"
+ "tip ham. Shankle salami tongue venison short ribs kielbasa"
+ "tri-tip ham hock swine hamburger. Flank meatball corned beef"
+ "cow sausage ball tip kielbasa ham hock. Ball tip cupim meatloaf"
+ "beef ribs rump jowl tenderloin swine sausage biltong"
+ "bacon rump tail boudin meatball boudin meatball boudin."
+ }
+ )
+ self.assertEqual(
+ form.errors["type_of_work"],
+ ["Response must be less than 1000 characters."],
+ )
+
def test_authorizing_official_phone_invalid(self):
"""Must be a valid phone number."""
form = AuthorizingOfficialForm(data={"phone": "boss@boss"})
diff --git a/src/registrar/tests/test_models.py b/src/registrar/tests/test_models.py
index 784920ec5..2ebca68d7 100644
--- a/src/registrar/tests/test_models.py
+++ b/src/registrar/tests/test_models.py
@@ -4,6 +4,7 @@ from django.db.utils import IntegrityError
from registrar.models import (
Contact,
DomainApplication,
+ DomainInformation,
User,
Website,
Domain,
@@ -63,6 +64,33 @@ class TestDomainApplication(TestCase):
application.other_contacts.add(contact)
application.save()
+ def test_domain_info(self):
+ """Can create domain info with all fields."""
+ user, _ = User.objects.get_or_create()
+ contact = Contact.objects.create()
+ domain, _ = Domain.objects.get_or_create(name="igorville.gov")
+ information = DomainInformation.objects.create(
+ creator=user,
+ organization_type=DomainInformation.OrganizationChoices.FEDERAL,
+ federal_type=DomainInformation.BranchChoices.EXECUTIVE,
+ is_election_board=False,
+ organization_name="Test",
+ address_line1="100 Main St.",
+ address_line2="APT 1A",
+ state_territory="CA",
+ zipcode="12345-6789",
+ authorizing_official=contact,
+ submitter=contact,
+ purpose="Igorville rules!",
+ anything_else="All of Igorville loves the dotgov program.",
+ is_policy_acknowledged=True,
+ domain=domain,
+ )
+ information.other_contacts.add(contact)
+ information.save()
+ self.assertEqual(information.domain.id, domain.id)
+ self.assertEqual(information.id, domain.domain_info.id)
+
def test_status_fsm_submit_fail(self):
user, _ = User.objects.get_or_create()
application = DomainApplication.objects.create(creator=user)
@@ -166,6 +194,24 @@ class TestPermissions(TestCase):
self.assertTrue(UserDomainRole.objects.get(user=user, domain=domain))
+class TestDomainInfo(TestCase):
+
+ """Test creation of Domain Information when approved."""
+
+ def test_approval_creates_info(self):
+ domain, _ = Domain.objects.get_or_create(name="igorville.gov")
+ user, _ = User.objects.get_or_create()
+ application = DomainApplication.objects.create(
+ creator=user, requested_domain=domain
+ )
+ # skip using the submit method
+ application.status = DomainApplication.SUBMITTED
+ application.approve()
+
+ # should be an information present for this domain
+ self.assertTrue(DomainInformation.objects.get(domain=domain))
+
+
class TestInvitations(TestCase):
"""Test the retrieval of invitations."""
diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py
index e64764659..f1dfec73a 100644
--- a/src/registrar/tests/test_views.py
+++ b/src/registrar/tests/test_views.py
@@ -1058,6 +1058,11 @@ class TestDomainPermissions(TestWithDomainPermissions):
)
self.assertEqual(response.status_code, 302)
+ response = self.client.get(
+ reverse("domain-nameservers", kwargs={"pk": self.domain.id})
+ )
+ self.assertEqual(response.status_code, 302)
+
def test_no_domain_role(self):
"""Logged in but no role gets 403 Forbidden."""
self.client.force_login(self.user)
@@ -1079,6 +1084,12 @@ class TestDomainPermissions(TestWithDomainPermissions):
)
self.assertEqual(response.status_code, 403)
+ with less_console_noise():
+ response = self.client.get(
+ reverse("domain-nameservers", kwargs={"pk": self.domain.id})
+ )
+ self.assertEqual(response.status_code, 403)
+
class TestDomainDetail(TestWithDomainPermissions, WebTest):
def setUp(self):
@@ -1222,6 +1233,55 @@ class TestDomainDetail(TestWithDomainPermissions, WebTest):
home_page = self.app.get(reverse("home"))
self.assertContains(home_page, self.domain.name)
+ def test_domain_nameservers(self):
+ """Can load domain's nameservers page."""
+ page = self.client.get(
+ reverse("domain-nameservers", kwargs={"pk": self.domain.id})
+ )
+ self.assertContains(page, "Domain name servers")
+
+ def test_domain_nameservers_form(self):
+ """Can change domain's nameservers.
+
+ Uses self.app WebTest because we need to interact with forms.
+ """
+ nameservers_page = self.app.get(
+ reverse("domain-nameservers", kwargs={"pk": self.domain.id})
+ )
+ session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
+ self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
+ with less_console_noise(): # swallow log warning message
+ result = nameservers_page.form.submit()
+ # form submission was a post, response should be a redirect
+ self.assertEqual(result.status_code, 302)
+ self.assertEqual(
+ result["Location"],
+ reverse("domain-nameservers", kwargs={"pk": self.domain.id}),
+ )
+ self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
+ page = result.follow()
+ self.assertContains(page, "The name servers for this domain have been updated")
+
+ def test_domain_nameservers_form_invalid(self):
+ """Can change domain's nameservers.
+
+ Uses self.app WebTest because we need to interact with forms.
+ """
+ nameservers_page = self.app.get(
+ reverse("domain-nameservers", kwargs={"pk": self.domain.id})
+ )
+ session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
+ self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
+ # first two nameservers are required, so if we empty one out we should
+ # get a form error
+ nameservers_page.form["form-0-server"] = ""
+ with less_console_noise(): # swallow logged warning message
+ result = nameservers_page.form.submit()
+ # form submission was a post with an error, response should be a 200
+ # error text appears twice, once at the top of the page, once around
+ # the field.
+ self.assertContains(result, "This field is required", count=2, status_code=200)
+
class TestApplicationStatus(TestWithUser, WebTest):
def setUp(self):
diff --git a/src/registrar/views/__init__.py b/src/registrar/views/__init__.py
index 0776f70fe..9f7fe139e 100644
--- a/src/registrar/views/__init__.py
+++ b/src/registrar/views/__init__.py
@@ -1,6 +1,7 @@
from .application import *
from .domain import (
DomainView,
+ DomainNameserversView,
DomainUsersView,
DomainAddUserView,
DomainInvitationDeleteView,
diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py
index 5c199066f..8a9095de3 100644
--- a/src/registrar/views/domain.py
+++ b/src/registrar/views/domain.py
@@ -12,7 +12,7 @@ from django.views.generic.edit import DeleteView, FormMixin
from registrar.models import Domain, DomainInvitation, User, UserDomainRole
-from ..forms import DomainAddUserForm
+from ..forms import DomainAddUserForm, NameserverFormset
from ..utility.email import send_templated_email, EmailSendingError
from .utility import DomainPermission
@@ -29,6 +29,73 @@ class DomainView(DomainPermission, DetailView):
context_object_name = "domain"
+class DomainNameserversView(DomainPermission, FormMixin, DetailView):
+
+ """Domain nameserver editing view."""
+
+ model = Domain
+ template_name = "domain_nameservers.html"
+ context_object_name = "domain"
+ form_class = NameserverFormset
+
+ def get_initial(self):
+ """The initial value for the form (which is a formset here)."""
+ domain = self.get_object()
+ return [{"server": server} for server in domain.nameservers()]
+
+ def get_success_url(self):
+ """Redirect to the overview page for the domain."""
+ return reverse("domain-nameservers", kwargs={"pk": self.object.pk})
+
+ def get_context_data(self, **kwargs):
+ """Adjust context from FormMixin for formsets."""
+ context = super().get_context_data(**kwargs)
+ # use "formset" instead of "form" for the key
+ context["formset"] = context.pop("form")
+ return context
+
+ def get_form(self, **kwargs):
+ """Override the labels and required fields every time we get a formset."""
+ formset = super().get_form(**kwargs)
+ for i, form in enumerate(formset):
+ form.fields["server"].label += f" {i+1}"
+ if i < 2:
+ form.fields["server"].required = True
+ else:
+ form.fields["server"].required = False
+ return formset
+
+ def post(self, request, *args, **kwargs):
+ """Formset submission posts to this view."""
+ self.object = self.get_object()
+ formset = self.get_form()
+
+ if formset.is_valid():
+ return self.form_valid(formset)
+ else:
+ return self.form_invalid(formset)
+
+ def form_valid(self, formset):
+ """The formset is valid, perform something with it."""
+
+ # Set the nameservers from the formset
+ nameservers = []
+ for form in formset:
+ try:
+ nameservers.append(form.cleaned_data["server"])
+ except KeyError:
+ # no server information in this field, skip it
+ pass
+ domain = self.get_object()
+ domain.set_nameservers(nameservers)
+
+ messages.success(
+ self.request, "The name servers for this domain have been updated"
+ )
+ # superclass has the redirect
+ return super().form_valid(formset)
+
+
class DomainUsersView(DomainPermission, DetailView):
"""User management page in the domain details."""
diff --git a/src/zap.conf b/src/zap.conf
index 293582826..3658bfe6c 100644
--- a/src/zap.conf
+++ b/src/zap.conf
@@ -51,6 +51,7 @@
10038 OUTOFSCOPE http://app:8080/(robots.txt|sitemap.xml|TODO|edit/)
10038 OUTOFSCOPE http://app:8080/users
10038 OUTOFSCOPE http://app:8080/users/add
+10038 OUTOFSCOPE http://app:8080/nameservers
10038 OUTOFSCOPE http://app:8080/delete
10038 OUTOFSCOPE http://app:8080/withdraw
10038 OUTOFSCOPE http://app:8080/withdrawconfirmed