mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-05-19 02:49:21 +02:00
Merge branch 'main' into nmb/1294-slow-roll
This commit is contained in:
commit
f873bb3804
113 changed files with 942 additions and 1854 deletions
|
@ -0,0 +1,30 @@
|
||||||
|
# 24. Production Release Cadence
|
||||||
|
|
||||||
|
Date: 2023-11-02
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
In Review
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
Going into our first production launch we need a plan describing what our release cadence for both our staging and stable envirornments will be. Currently, we release to staging whenever there are significant changes made, but we have not been making releases to stable with the same frequency.
|
||||||
|
|
||||||
|
## Considered Options
|
||||||
|
|
||||||
|
**Option 1:** Releasing to stable/staging once a sprint
|
||||||
|
Releasing once a sprint would mean that we release the past sprint's work to stable at the end of the current sprint. At the same point, the current sprint's work would be pushed to staging, thus making staging a full sprint ahead of stable. While this is more straight forward, it means our users would have to wait longer to see changes that weren't deemed critical.
|
||||||
|
**Option 2:** Releasing to stable/staging once a week
|
||||||
|
Releasing once a week would follow the same flow but with code being released to staging one week before the same code is released to stable. This would make stable only one week behind staging and would allow us to roll out minor bug fixes and faster with greater speed. The negative side is that we have less time to see if errors occur on staging
|
||||||
|
|
||||||
|
In both of the above scenarios the release date would fall on the same day of the week that the sprint starts, which is currently a Wednesday. Additionally, in both scenarios the release commits would eventually be tagged with both a staging and stable tag. Furthermore, critical bugs or features would be exempt from these restrictions based on the product owner's discretion.
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
We decided to go with option 2 and release once a week once in production. This will allow us to give users features and bug fixes faster while still allowing enough time on staging for quality to be maintained.
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
Work not completed by end of the sprint will have to wait to be added to stable. Also, making quick fixes for bugs that are found on stable will be a little more complicated to fix.
|
||||||
|
|
||||||
|
When first going into production, staging and stable will start with the same code base. The following week a new release will be made to staging, but not stable as no code will have been on staging long enough to warrant another release. Thus just at the start of launch stable will be essentially frozen for 2 weeks, not one.
|
|
@ -136,7 +136,7 @@ class DomainInvitation {
|
||||||
--
|
--
|
||||||
}
|
}
|
||||||
DomainInvitation -- Domain
|
DomainInvitation -- Domain
|
||||||
DomainInvitation .[#green].> UserDomainRole : User.first_login()
|
DomainInvitation .[#green].> UserDomainRole : User.on_each_login()
|
||||||
|
|
||||||
actor applicant #Red
|
actor applicant #Red
|
||||||
applicant -d-> DomainApplication : **/register**
|
applicant -d-> DomainApplication : **/register**
|
||||||
|
|
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
@ -43,6 +43,21 @@ For ease of use, you can run the `deploy.sh <sandbox name>` script in the `/src`
|
||||||
|
|
||||||
Your sandbox space should've been setup as part of the onboarding process. If this was not the case, please have an admin follow the instructions [here](../../.github/ISSUE_TEMPLATE/developer-onboarding.md#setting-up-developer-sandbox).
|
Your sandbox space should've been setup as part of the onboarding process. If this was not the case, please have an admin follow the instructions [here](../../.github/ISSUE_TEMPLATE/developer-onboarding.md#setting-up-developer-sandbox).
|
||||||
|
|
||||||
|
## Stable and Staging Release Rules
|
||||||
|
|
||||||
|
Releases will be made for staging and stable every week starting on the first day of the sprint (Wednesday), with the second release of the sprint occuring halfway through the sprint. With the exception of first time going into production, these releases will NOT have the same code. The release to stable will be the same commit that was tagged for staging one week prior, making stable one week behind staging. Further, this means staging can be up to a week behind the main branch of code.
|
||||||
|
|
||||||
|
If a bug fix or feature needs to be made to stable out of the normal cycle, this can only be done at the product owner's request.
|
||||||
|
|
||||||
|
## Making bug fixes on stable during production
|
||||||
|
|
||||||
|
In the case where a bug fix or feature needs to be added outside of the normal cycle, the code-fix branch and release will be handled differently than normal:
|
||||||
|
|
||||||
|
1. Code will need to be branched NOT off of main, but off of the same commit as the most recent stable commit. This should be the one tagged with the most recent vX.XX.XX value.
|
||||||
|
2. After making the bug fix, the approved PR will branch will be tagged with a new release tag, incrementing the patch value from the last commit number.
|
||||||
|
3. This branch then needs to be merged to main per the usual process.
|
||||||
|
4. This same branch should be merged into staging.
|
||||||
|
|
||||||
## Serving static assets
|
## Serving static assets
|
||||||
We are using [WhiteNoise](http://whitenoise.evans.io/en/stable/index.html) plugin to serve our static assets on cloud.gov. This plugin is added to the `MIDDLEWARE` list in our apps `settings.py`.
|
We are using [WhiteNoise](http://whitenoise.evans.io/en/stable/index.html) plugin to serve our static assets on cloud.gov. This plugin is added to the `MIDDLEWARE` list in our apps `settings.py`.
|
||||||
|
|
||||||
|
@ -159,3 +174,27 @@ it with the latest model schema. Once launched, this should never be used on
|
||||||
the `stable` environment, but during development, it may be useful on the
|
the `stable` environment, but during development, it may be useful on the
|
||||||
various sandbox environments. After launch, some schema changes may take the
|
various sandbox environments. After launch, some schema changes may take the
|
||||||
involvement of a skilled DBA to fix problems like this.
|
involvement of a skilled DBA to fix problems like this.
|
||||||
|
|
||||||
|
# Bug triage
|
||||||
|
|
||||||
|
Bugs on production software need to be documented quickly and triaged to determine if fixes need to be made outside of the normal release cadence. Triage levels will be Critical, High, Medium, and Low to indicate the level of priority for fix, not neccessarily the level of severity. See below for more details
|
||||||
|
|
||||||
|
**Critical**- should only be determined by the product owner and means the fix for this critical bug needs to have a quick fix for it created ASAP. This is the only case where a bug fix can be added outside of the normal release cycle and directly onto the stable release.
|
||||||
|
**High**- Can be determined by product owner or other team member, and indicates this bug is critical enough to warrant being added into the current sprint.
|
||||||
|
**Medium**- Should be added to a sprint coming up but is not blocking users, or enough users to warrant rushing it into a sprint
|
||||||
|
**Low**- A minor bug, that could even wait until after the next big launch date to be implemented.
|
||||||
|
|
||||||
|
## Steps for Triaging
|
||||||
|
|
||||||
|
1. When a bug is found, whether by a developer/designer or from feedback from an end user, a ticket should be made immediately. The actual maker of the ticket can be a member of the product team as needed.
|
||||||
|
2. This bug ticket immediately gets a priority added Critical/High/Medium/Low, with Critical requiring the product owner's consent.
|
||||||
|
3. Anything marked as `critical` should be refined immediately and engineering should be notified in our Slack dev channel that a Critical ticket has been created (if not already notified)
|
||||||
|
4. All items not marked as `critical` by the product owner can wait until refinement to be refined and may have their prioirty level changed during that meeting.
|
||||||
|
|
||||||
|
## Steps for dealing with Critical Bugs
|
||||||
|
|
||||||
|
1. Once the critical bug ticket is refined and the bug is clear, an engineer should be assigned to work on it. (No ticket, no work)
|
||||||
|
2. At the same point, two other engineers should be assigned to review the PR once it's made. One of the reviewing engineers can be subsititued for a designer if this is a design/content/other user facing bug fix.
|
||||||
|
3. In the case where the engineering lead is is unresponsive or unavailable to assign the ticket immediately, the product team will make sure an engineer volunteers or is assigned to the ticket/PR review ASAP.
|
||||||
|
4. Once done, the developer must make a PR and should tag the assigned PR reviewers in our Slack dev channel stating that the PR is now waiting on their review. These reviewers should drop other tasks in order to review this promptly.
|
||||||
|
5. See the the section above on [Making bug fixes on stable](#making-bug-fixes-on-stable-during-production) for how to push changes to stable once the PR is approved
|
||||||
|
|
|
@ -23,6 +23,8 @@ applications:
|
||||||
DJANGO_LOG_LEVEL: INFO
|
DJANGO_LOG_LEVEL: INFO
|
||||||
# default public site location
|
# default public site location
|
||||||
GETGOV_PUBLIC_SITE_URL: https://beta.get.gov
|
GETGOV_PUBLIC_SITE_URL: https://beta.get.gov
|
||||||
|
# Flag to disable/enable features in prod environments
|
||||||
|
IS_PRODUCTION: False
|
||||||
routes:
|
routes:
|
||||||
- route: getgov-ab.app.cloud.gov
|
- route: getgov-ab.app.cloud.gov
|
||||||
services:
|
services:
|
||||||
|
|
|
@ -23,6 +23,8 @@ applications:
|
||||||
DJANGO_LOG_LEVEL: INFO
|
DJANGO_LOG_LEVEL: INFO
|
||||||
# default public site location
|
# default public site location
|
||||||
GETGOV_PUBLIC_SITE_URL: https://beta.get.gov
|
GETGOV_PUBLIC_SITE_URL: https://beta.get.gov
|
||||||
|
# Flag to disable/enable features in prod environments
|
||||||
|
IS_PRODUCTION: False
|
||||||
routes:
|
routes:
|
||||||
- route: getgov-bl.app.cloud.gov
|
- route: getgov-bl.app.cloud.gov
|
||||||
services:
|
services:
|
||||||
|
|
|
@ -23,6 +23,8 @@ applications:
|
||||||
DJANGO_LOG_LEVEL: INFO
|
DJANGO_LOG_LEVEL: INFO
|
||||||
# default public site location
|
# default public site location
|
||||||
GETGOV_PUBLIC_SITE_URL: https://beta.get.gov
|
GETGOV_PUBLIC_SITE_URL: https://beta.get.gov
|
||||||
|
# Flag to disable/enable features in prod environments
|
||||||
|
IS_PRODUCTION: False
|
||||||
routes:
|
routes:
|
||||||
- route: getgov-dk.app.cloud.gov
|
- route: getgov-dk.app.cloud.gov
|
||||||
services:
|
services:
|
||||||
|
|
|
@ -23,6 +23,8 @@ applications:
|
||||||
DJANGO_LOG_LEVEL: INFO
|
DJANGO_LOG_LEVEL: INFO
|
||||||
# default public site location
|
# default public site location
|
||||||
GETGOV_PUBLIC_SITE_URL: https://beta.get.gov
|
GETGOV_PUBLIC_SITE_URL: https://beta.get.gov
|
||||||
|
# Flag to disable/enable features in prod environments
|
||||||
|
IS_PRODUCTION: False
|
||||||
routes:
|
routes:
|
||||||
- route: getgov-es.app.cloud.gov
|
- route: getgov-es.app.cloud.gov
|
||||||
services:
|
services:
|
||||||
|
|
|
@ -23,6 +23,8 @@ applications:
|
||||||
DJANGO_LOG_LEVEL: INFO
|
DJANGO_LOG_LEVEL: INFO
|
||||||
# default public site location
|
# default public site location
|
||||||
GETGOV_PUBLIC_SITE_URL: https://beta.get.gov
|
GETGOV_PUBLIC_SITE_URL: https://beta.get.gov
|
||||||
|
# Flag to disable/enable features in prod environments
|
||||||
|
IS_PRODUCTION: False
|
||||||
routes:
|
routes:
|
||||||
- route: getgov-gd.app.cloud.gov
|
- route: getgov-gd.app.cloud.gov
|
||||||
services:
|
services:
|
||||||
|
|
|
@ -23,6 +23,8 @@ applications:
|
||||||
DJANGO_LOG_LEVEL: INFO
|
DJANGO_LOG_LEVEL: INFO
|
||||||
# default public site location
|
# default public site location
|
||||||
GETGOV_PUBLIC_SITE_URL: https://beta.get.gov
|
GETGOV_PUBLIC_SITE_URL: https://beta.get.gov
|
||||||
|
# Flag to disable/enable features in prod environments
|
||||||
|
IS_PRODUCTION: False
|
||||||
routes:
|
routes:
|
||||||
- route: getgov-ko.app.cloud.gov
|
- route: getgov-ko.app.cloud.gov
|
||||||
services:
|
services:
|
||||||
|
|
|
@ -23,6 +23,8 @@ applications:
|
||||||
DJANGO_LOG_LEVEL: INFO
|
DJANGO_LOG_LEVEL: INFO
|
||||||
# default public site location
|
# default public site location
|
||||||
GETGOV_PUBLIC_SITE_URL: https://beta.get.gov
|
GETGOV_PUBLIC_SITE_URL: https://beta.get.gov
|
||||||
|
# Flag to disable/enable features in prod environments
|
||||||
|
IS_PRODUCTION: False
|
||||||
routes:
|
routes:
|
||||||
- route: getgov-nl.app.cloud.gov
|
- route: getgov-nl.app.cloud.gov
|
||||||
services:
|
services:
|
||||||
|
|
|
@ -23,6 +23,8 @@ applications:
|
||||||
DJANGO_LOG_LEVEL: INFO
|
DJANGO_LOG_LEVEL: INFO
|
||||||
# default public site location
|
# default public site location
|
||||||
GETGOV_PUBLIC_SITE_URL: https://beta.get.gov
|
GETGOV_PUBLIC_SITE_URL: https://beta.get.gov
|
||||||
|
# Flag to disable/enable features in prod environments
|
||||||
|
IS_PRODUCTION: False
|
||||||
routes:
|
routes:
|
||||||
- route: getgov-rb.app.cloud.gov
|
- route: getgov-rb.app.cloud.gov
|
||||||
services:
|
services:
|
||||||
|
|
|
@ -23,6 +23,8 @@ applications:
|
||||||
DJANGO_LOG_LEVEL: INFO
|
DJANGO_LOG_LEVEL: INFO
|
||||||
# default public site location
|
# default public site location
|
||||||
GETGOV_PUBLIC_SITE_URL: https://beta.get.gov
|
GETGOV_PUBLIC_SITE_URL: https://beta.get.gov
|
||||||
|
# Flag to disable/enable features in prod environments
|
||||||
|
IS_PRODUCTION: False
|
||||||
routes:
|
routes:
|
||||||
- route: getgov-rh.app.cloud.gov
|
- route: getgov-rh.app.cloud.gov
|
||||||
services:
|
services:
|
||||||
|
|
|
@ -23,6 +23,8 @@ applications:
|
||||||
DJANGO_LOG_LEVEL: INFO
|
DJANGO_LOG_LEVEL: INFO
|
||||||
# default public site location
|
# default public site location
|
||||||
GETGOV_PUBLIC_SITE_URL: https://beta.get.gov
|
GETGOV_PUBLIC_SITE_URL: https://beta.get.gov
|
||||||
|
# Flag to disable/enable features in prod environments
|
||||||
|
IS_PRODUCTION: False
|
||||||
routes:
|
routes:
|
||||||
- route: getgov-rjm.app.cloud.gov
|
- route: getgov-rjm.app.cloud.gov
|
||||||
services:
|
services:
|
||||||
|
|
|
@ -25,6 +25,8 @@ applications:
|
||||||
GETGOV_PUBLIC_SITE_URL: https://beta.get.gov
|
GETGOV_PUBLIC_SITE_URL: https://beta.get.gov
|
||||||
# Which OIDC provider to use
|
# Which OIDC provider to use
|
||||||
OIDC_ACTIVE_PROVIDER: login.gov production
|
OIDC_ACTIVE_PROVIDER: login.gov production
|
||||||
|
# Flag to disable/enable features in prod environments
|
||||||
|
IS_PRODUCTION: True
|
||||||
routes:
|
routes:
|
||||||
- route: getgov-stable.app.cloud.gov
|
- route: getgov-stable.app.cloud.gov
|
||||||
services:
|
services:
|
||||||
|
|
|
@ -23,6 +23,8 @@ applications:
|
||||||
DJANGO_LOG_LEVEL: INFO
|
DJANGO_LOG_LEVEL: INFO
|
||||||
# default public site location
|
# default public site location
|
||||||
GETGOV_PUBLIC_SITE_URL: https://beta.get.gov
|
GETGOV_PUBLIC_SITE_URL: https://beta.get.gov
|
||||||
|
# Flag to disable/enable features in prod environments
|
||||||
|
IS_PRODUCTION: False
|
||||||
routes:
|
routes:
|
||||||
- route: getgov-staging.app.cloud.gov
|
- route: getgov-staging.app.cloud.gov
|
||||||
services:
|
services:
|
||||||
|
|
|
@ -23,6 +23,8 @@ applications:
|
||||||
DJANGO_LOG_LEVEL: INFO
|
DJANGO_LOG_LEVEL: INFO
|
||||||
# default public site location
|
# default public site location
|
||||||
GETGOV_PUBLIC_SITE_URL: https://beta.get.gov
|
GETGOV_PUBLIC_SITE_URL: https://beta.get.gov
|
||||||
|
# Flag to disable/enable features in prod environments
|
||||||
|
IS_PRODUCTION: False
|
||||||
routes:
|
routes:
|
||||||
- route: getgov-za.app.cloud.gov
|
- route: getgov-za.app.cloud.gov
|
||||||
services:
|
services:
|
||||||
|
|
|
@ -23,6 +23,8 @@ applications:
|
||||||
DJANGO_LOG_LEVEL: INFO
|
DJANGO_LOG_LEVEL: INFO
|
||||||
# default public site location
|
# default public site location
|
||||||
GETGOV_PUBLIC_SITE_URL: https://beta.get.gov
|
GETGOV_PUBLIC_SITE_URL: https://beta.get.gov
|
||||||
|
# Flag to disable/enable features in prod environments
|
||||||
|
IS_PRODUCTION: False
|
||||||
routes:
|
routes:
|
||||||
- route: getgov-ENVIRONMENT.app.cloud.gov
|
- route: getgov-ENVIRONMENT.app.cloud.gov
|
||||||
services:
|
services:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
[flake8]
|
[flake8]
|
||||||
max-line-length = 88
|
max-line-length = 120
|
||||||
max-complexity = 10
|
max-complexity = 10
|
||||||
extend-ignore = E203
|
extend-ignore = E203
|
||||||
per-file-ignores = __init__.py:F401,F403,E402
|
per-file-ignores = __init__.py:F401,F403,E402
|
||||||
|
|
44
src/Pipfile.lock
generated
44
src/Pipfile.lock
generated
|
@ -32,20 +32,20 @@
|
||||||
},
|
},
|
||||||
"boto3": {
|
"boto3": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:85e2fa361ad3210d30800bad311688261f2673a9b301e0edab56463d89609761",
|
"sha256:02ce7dcad2d3b054cd99e7ca6df7a708e016a31b1c98b46d8df3b3891070c121",
|
||||||
"sha256:d18688bc5d688decf3cc404430a3ac3ec317be653cdcfbc51104c01f38a66434"
|
"sha256:b8acb57a124434284d6ab69c61d32d70e84e13e2c27c33b4ad3c32f15ad407d3"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"markers": "python_version >= '3.7'",
|
"markers": "python_version >= '3.7'",
|
||||||
"version": "==1.28.76"
|
"version": "==1.28.79"
|
||||||
},
|
},
|
||||||
"botocore": {
|
"botocore": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:479abb5a1ee03eb00faa1ea176bc595b2f46f7494777807681a9df45ed99ea18",
|
"sha256:07ecb93833475dde68e5c0e02a7ccf8ca22caf68cdc892651c300529894133e1",
|
||||||
"sha256:74e0a4515d61b2860b24dc208ca89a68d79dc00147125d531746d3ba808822ad"
|
"sha256:6f1fc49e9e12f9772b4fef577837670bc84d772a7c946b4d08fe2890e34a4305"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.7'",
|
"markers": "python_version >= '3.7'",
|
||||||
"version": "==1.31.76"
|
"version": "==1.31.79"
|
||||||
},
|
},
|
||||||
"cachetools": {
|
"cachetools": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -448,7 +448,7 @@
|
||||||
},
|
},
|
||||||
"geventconnpool": {
|
"geventconnpool": {
|
||||||
"git": "https://github.com/rasky/geventconnpool.git",
|
"git": "https://github.com/rasky/geventconnpool.git",
|
||||||
"ref": null
|
"ref": "1bbb93a714a331a069adf27265fe582d9ba7ecd4"
|
||||||
},
|
},
|
||||||
"greenlet": {
|
"greenlet": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -1205,12 +1205,12 @@
|
||||||
},
|
},
|
||||||
"boto3": {
|
"boto3": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:85e2fa361ad3210d30800bad311688261f2673a9b301e0edab56463d89609761",
|
"sha256:02ce7dcad2d3b054cd99e7ca6df7a708e016a31b1c98b46d8df3b3891070c121",
|
||||||
"sha256:d18688bc5d688decf3cc404430a3ac3ec317be653cdcfbc51104c01f38a66434"
|
"sha256:b8acb57a124434284d6ab69c61d32d70e84e13e2c27c33b4ad3c32f15ad407d3"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"markers": "python_version >= '3.7'",
|
"markers": "python_version >= '3.7'",
|
||||||
"version": "==1.28.76"
|
"version": "==1.28.79"
|
||||||
},
|
},
|
||||||
"boto3-mocking": {
|
"boto3-mocking": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -1223,28 +1223,28 @@
|
||||||
},
|
},
|
||||||
"boto3-stubs": {
|
"boto3-stubs": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:d89c3546e9e500f81ebfe78c71627e74085d3f77cd7e62830b5e48a67bce9b75",
|
"sha256:621e229ef9b394cd1f6cd5caa58a17347440b14423b01435d9f2a50031a427fc",
|
||||||
"sha256:fc57fc32d9a0c4bdd02676c37dbaa911b3e6c3857e417a229d236938d31299fe"
|
"sha256:f5986d1b09d516f58780100a3a86bfa75114370dd5dd0bdea67bfe8cda255723"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"markers": "python_version >= '3.7'",
|
"markers": "python_version >= '3.7'",
|
||||||
"version": "==1.28.76"
|
"version": "==1.28.79"
|
||||||
},
|
},
|
||||||
"botocore": {
|
"botocore": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:479abb5a1ee03eb00faa1ea176bc595b2f46f7494777807681a9df45ed99ea18",
|
"sha256:07ecb93833475dde68e5c0e02a7ccf8ca22caf68cdc892651c300529894133e1",
|
||||||
"sha256:74e0a4515d61b2860b24dc208ca89a68d79dc00147125d531746d3ba808822ad"
|
"sha256:6f1fc49e9e12f9772b4fef577837670bc84d772a7c946b4d08fe2890e34a4305"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.7'",
|
"markers": "python_version >= '3.7'",
|
||||||
"version": "==1.31.76"
|
"version": "==1.31.79"
|
||||||
},
|
},
|
||||||
"botocore-stubs": {
|
"botocore-stubs": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:778c6e014ae1103d546d59dffb000b0a659c9b9bbfa11050ff4a62c5adeec3a4",
|
"sha256:64488b9f38905f8a60041998f9dc945754222d900a3345b449059667890c2c17",
|
||||||
"sha256:9fd9447a28642efa35a1c5590fc35132cf0173cd12055ba9044511cb6b24dd6f"
|
"sha256:e4d8e782d774f45dbfc36d922a0a0edfffbacca2ce66bccaba02a893a38359f2"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.7' and python_version < '4.0'",
|
"markers": "python_version >= '3.7' and python_version < '4.0'",
|
||||||
"version": "==1.31.76"
|
"version": "==1.31.79"
|
||||||
},
|
},
|
||||||
"click": {
|
"click": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -1429,11 +1429,11 @@
|
||||||
},
|
},
|
||||||
"pbr": {
|
"pbr": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:567f09558bae2b3ab53cb3c1e2e33e726ff3338e7bae3db5dc954b3a44eef12b",
|
"sha256:4a7317d5e3b17a3dccb6a8cfe67dab65b20551404c52c8ed41279fa4f0cb4cda",
|
||||||
"sha256:aefc51675b0b533d56bb5fd1c8c6c0522fe31896679882e1c4c63d5e4a0fccb3"
|
"sha256:d1377122a5a00e2f940ee482999518efe16d745d423a670c27773dfbc3c9a7d9"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '2.6'",
|
"markers": "python_version >= '2.6'",
|
||||||
"version": "==5.11.1"
|
"version": "==6.0.0"
|
||||||
},
|
},
|
||||||
"platformdirs": {
|
"platformdirs": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
|
|
@ -10,9 +10,7 @@ from login_required import login_not_required
|
||||||
from cachetools.func import ttl_cache
|
from cachetools.func import ttl_cache
|
||||||
|
|
||||||
|
|
||||||
DOMAIN_FILE_URL = (
|
DOMAIN_FILE_URL = "https://raw.githubusercontent.com/cisagov/dotgov-data/main/current-full.csv"
|
||||||
"https://raw.githubusercontent.com/cisagov/dotgov-data/main/current-full.csv"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
DOMAIN_API_MESSAGES = {
|
DOMAIN_API_MESSAGES = {
|
||||||
|
@ -22,8 +20,7 @@ DOMAIN_API_MESSAGES = {
|
||||||
"extra_dots": "Enter the .gov domain you want without any periods.",
|
"extra_dots": "Enter the .gov domain you want without any periods.",
|
||||||
"unavailable": "That domain isn’t available. Try entering another one."
|
"unavailable": "That domain isn’t available. Try entering another one."
|
||||||
" Contact us if you need help coming up with a domain.",
|
" Contact us if you need help coming up with a domain.",
|
||||||
"invalid": "Enter a domain using only letters,"
|
"invalid": "Enter a domain using only letters, numbers, or hyphens (though we don't recommend using hyphens).",
|
||||||
" numbers, or hyphens (though we don't recommend using hyphens).",
|
|
||||||
"success": "That domain is available!",
|
"success": "That domain is available!",
|
||||||
"error": "Error finding domain availability.",
|
"error": "Error finding domain availability.",
|
||||||
}
|
}
|
||||||
|
@ -82,24 +79,13 @@ def available(request, domain=""):
|
||||||
DraftDomain = apps.get_model("registrar.DraftDomain")
|
DraftDomain = apps.get_model("registrar.DraftDomain")
|
||||||
# validate that the given domain could be a domain name and fail early if
|
# validate that the given domain could be a domain name and fail early if
|
||||||
# not.
|
# not.
|
||||||
if not (
|
if not (DraftDomain.string_could_be_domain(domain) or DraftDomain.string_could_be_domain(domain + ".gov")):
|
||||||
DraftDomain.string_could_be_domain(domain)
|
return JsonResponse({"available": False, "message": DOMAIN_API_MESSAGES["invalid"]})
|
||||||
or DraftDomain.string_could_be_domain(domain + ".gov")
|
|
||||||
):
|
|
||||||
return JsonResponse(
|
|
||||||
{"available": False, "message": DOMAIN_API_MESSAGES["invalid"]}
|
|
||||||
)
|
|
||||||
# a domain is available if it is NOT in the list of current domains
|
# a domain is available if it is NOT in the list of current domains
|
||||||
try:
|
try:
|
||||||
if check_domain_available(domain):
|
if check_domain_available(domain):
|
||||||
return JsonResponse(
|
return JsonResponse({"available": True, "message": DOMAIN_API_MESSAGES["success"]})
|
||||||
{"available": True, "message": DOMAIN_API_MESSAGES["success"]}
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
return JsonResponse(
|
return JsonResponse({"available": False, "message": DOMAIN_API_MESSAGES["unavailable"]})
|
||||||
{"available": False, "message": DOMAIN_API_MESSAGES["unavailable"]}
|
|
||||||
)
|
|
||||||
except Exception:
|
except Exception:
|
||||||
return JsonResponse(
|
return JsonResponse({"available": False, "message": DOMAIN_API_MESSAGES["error"]})
|
||||||
{"available": False, "message": DOMAIN_API_MESSAGES["error"]}
|
|
||||||
)
|
|
||||||
|
|
|
@ -49,13 +49,13 @@ class OpenIdConnectBackend(ModelBackend):
|
||||||
user, created = UserModel.objects.update_or_create(**args)
|
user, created = UserModel.objects.update_or_create(**args)
|
||||||
if created:
|
if created:
|
||||||
user = self.configure_user(user, **kwargs)
|
user = self.configure_user(user, **kwargs)
|
||||||
# run a newly created user's callback for a first-time login
|
|
||||||
user.first_login()
|
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
user = UserModel.objects.get_by_natural_key(username)
|
user = UserModel.objects.get_by_natural_key(username)
|
||||||
except UserModel.DoesNotExist:
|
except UserModel.DoesNotExist:
|
||||||
return None
|
return None
|
||||||
|
# run this callback for a each login
|
||||||
|
user.on_each_login()
|
||||||
return user
|
return user
|
||||||
|
|
||||||
def clean_username(self, username):
|
def clean_username(self, username):
|
||||||
|
|
|
@ -72,9 +72,7 @@ class Client(oic.Client):
|
||||||
try:
|
try:
|
||||||
# discover and store the provider (OP) urls, etc
|
# discover and store the provider (OP) urls, etc
|
||||||
self.provider_config(provider["srv_discovery_url"])
|
self.provider_config(provider["srv_discovery_url"])
|
||||||
self.store_registration_info(
|
self.store_registration_info(RegistrationResponse(**provider["client_registration"]))
|
||||||
RegistrationResponse(**provider["client_registration"])
|
|
||||||
)
|
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
logger.error(err)
|
logger.error(err)
|
||||||
logger.error(
|
logger.error(
|
||||||
|
@ -169,9 +167,7 @@ class Client(oic.Client):
|
||||||
if isinstance(authn_response, ErrorResponse):
|
if isinstance(authn_response, ErrorResponse):
|
||||||
error = authn_response.get("error", "")
|
error = authn_response.get("error", "")
|
||||||
if error == "login_required":
|
if error == "login_required":
|
||||||
logger.warning(
|
logger.warning("User was not logged in (%s), trying again for %s" % (error, state))
|
||||||
"User was not logged in (%s), trying again for %s" % (error, state)
|
|
||||||
)
|
|
||||||
return self.create_authn_request(session)
|
return self.create_authn_request(session)
|
||||||
else:
|
else:
|
||||||
logger.error("Unable to process response %s for %s" % (error, state))
|
logger.error("Unable to process response %s for %s" % (error, state))
|
||||||
|
@ -190,9 +186,7 @@ class Client(oic.Client):
|
||||||
|
|
||||||
if self.behaviour.get("response_type") == "code":
|
if self.behaviour.get("response_type") == "code":
|
||||||
# need an access token to get user info (and to log the user out later)
|
# need an access token to get user info (and to log the user out later)
|
||||||
self._request_token(
|
self._request_token(authn_response["state"], authn_response["code"], session)
|
||||||
authn_response["state"], authn_response["code"], session
|
|
||||||
)
|
|
||||||
|
|
||||||
user_info = self._get_user_info(state, session)
|
user_info = self._get_user_info(state, session)
|
||||||
|
|
||||||
|
@ -216,10 +210,7 @@ class Client(oic.Client):
|
||||||
|
|
||||||
# ErrorResponse is not raised, it is passed back...
|
# ErrorResponse is not raised, it is passed back...
|
||||||
if isinstance(info_response, ErrorResponse):
|
if isinstance(info_response, ErrorResponse):
|
||||||
logger.error(
|
logger.error("Unable to get user info (%s) for %s" % (info_response.get("error", ""), state))
|
||||||
"Unable to get user info (%s) for %s"
|
|
||||||
% (info_response.get("error", ""), state)
|
|
||||||
)
|
|
||||||
raise o_e.AuthenticationFailed(locator=state)
|
raise o_e.AuthenticationFailed(locator=state)
|
||||||
|
|
||||||
logger.debug("user info: %s" % info_response)
|
logger.debug("user info: %s" % info_response)
|
||||||
|
@ -249,10 +240,7 @@ class Client(oic.Client):
|
||||||
|
|
||||||
# ErrorResponse is not raised, it is passed back...
|
# ErrorResponse is not raised, it is passed back...
|
||||||
if isinstance(token_response, ErrorResponse):
|
if isinstance(token_response, ErrorResponse):
|
||||||
logger.error(
|
logger.error("Unable to get token (%s) for %s" % (token_response.get("error", ""), state))
|
||||||
"Unable to get token (%s) for %s"
|
|
||||||
% (token_response.get("error", ""), state)
|
|
||||||
)
|
|
||||||
raise o_e.AuthenticationFailed(locator=state)
|
raise o_e.AuthenticationFailed(locator=state)
|
||||||
|
|
||||||
logger.debug("token response %s" % token_response)
|
logger.debug("token response %s" % token_response)
|
||||||
|
|
|
@ -85,12 +85,8 @@ class ViewsTest(TestCase):
|
||||||
session.save()
|
session.save()
|
||||||
# mock
|
# mock
|
||||||
mock_client.callback.side_effect = self.user_info
|
mock_client.callback.side_effect = self.user_info
|
||||||
mock_client.registration_response = {
|
mock_client.registration_response = {"post_logout_redirect_uris": ["http://example.com/back"]}
|
||||||
"post_logout_redirect_uris": ["http://example.com/back"]
|
mock_client.provider_info = {"end_session_endpoint": "http://example.com/log_me_out"}
|
||||||
}
|
|
||||||
mock_client.provider_info = {
|
|
||||||
"end_session_endpoint": "http://example.com/log_me_out"
|
|
||||||
}
|
|
||||||
mock_client.client_id = "TEST"
|
mock_client.client_id = "TEST"
|
||||||
# test
|
# test
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
|
|
|
@ -92,11 +92,7 @@ def logout(request, next_page=None):
|
||||||
and len(CLIENT.registration_response["post_logout_redirect_uris"]) > 0
|
and len(CLIENT.registration_response["post_logout_redirect_uris"]) > 0
|
||||||
):
|
):
|
||||||
request_args.update(
|
request_args.update(
|
||||||
{
|
{"post_logout_redirect_uri": CLIENT.registration_response["post_logout_redirect_uris"][0]}
|
||||||
"post_logout_redirect_uri": CLIENT.registration_response[
|
|
||||||
"post_logout_redirect_uris"
|
|
||||||
][0]
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
url = CLIENT.provider_info["end_session_endpoint"]
|
url = CLIENT.provider_info["end_session_endpoint"]
|
||||||
|
|
|
@ -25,8 +25,12 @@ services:
|
||||||
- DJANGO_SECRET_KEY=really-long-random-string-BNPecI7+s8jMahQcGHZ3XQ5yUfRrSibdapVLIz0UemdktVPofDKcoy
|
- DJANGO_SECRET_KEY=really-long-random-string-BNPecI7+s8jMahQcGHZ3XQ5yUfRrSibdapVLIz0UemdktVPofDKcoy
|
||||||
# Run Django in debug mode on local
|
# Run Django in debug mode on local
|
||||||
- DJANGO_DEBUG=True
|
- DJANGO_DEBUG=True
|
||||||
|
# Run Django without production flags
|
||||||
|
- IS_PRODUCTION=False
|
||||||
# Tell Django where it is being hosted
|
# Tell Django where it is being hosted
|
||||||
- DJANGO_BASE_URL=http://localhost:8080
|
- DJANGO_BASE_URL=http://localhost:8080
|
||||||
|
# Is this a production environment
|
||||||
|
- IS_PRODUCTION
|
||||||
# Public site URL link
|
# Public site URL link
|
||||||
- GETGOV_PUBLIC_SITE_URL=https://beta.get.gov
|
- GETGOV_PUBLIC_SITE_URL=https://beta.get.gov
|
||||||
# Set a username for accessing the registry
|
# Set a username for accessing the registry
|
||||||
|
|
|
@ -95,9 +95,7 @@ class EPPLibWrapper:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if not self.pool_status.connection_success:
|
if not self.pool_status.connection_success:
|
||||||
raise LoginError(
|
raise LoginError("Couldn't connect to the registry after three attempts")
|
||||||
"Couldn't connect to the registry after three attempts"
|
|
||||||
)
|
|
||||||
with self._pool.get() as connection:
|
with self._pool.get() as connection:
|
||||||
response = connection.send(command)
|
response = connection.send(command)
|
||||||
except Timeout as t:
|
except Timeout as t:
|
||||||
|
@ -239,6 +237,4 @@ try:
|
||||||
logger.info("registry client initialized")
|
logger.info("registry client initialized")
|
||||||
except Exception:
|
except Exception:
|
||||||
CLIENT = None # type: ignore
|
CLIENT = None # type: ignore
|
||||||
logger.warning(
|
logger.warning("Unable to configure epplib. Registrar cannot contact registry.", exc_info=True)
|
||||||
"Unable to configure epplib. Registrar cannot contact registry.", exc_info=True
|
|
||||||
)
|
|
||||||
|
|
|
@ -103,9 +103,7 @@ class TestConnectionPool(TestCase):
|
||||||
],
|
],
|
||||||
cl_id="gov2023-ote",
|
cl_id="gov2023-ote",
|
||||||
cr_id="gov2023-ote",
|
cr_id="gov2023-ote",
|
||||||
cr_date=datetime.datetime(
|
cr_date=datetime.datetime(2023, 8, 15, 23, 56, 36, tzinfo=tzlocal()),
|
||||||
2023, 8, 15, 23, 56, 36, tzinfo=tzlocal()
|
|
||||||
),
|
|
||||||
up_id="gov2023-ote",
|
up_id="gov2023-ote",
|
||||||
up_date=datetime.datetime(2023, 8, 17, 2, 3, 19, tzinfo=tzlocal()),
|
up_date=datetime.datetime(2023, 8, 17, 2, 3, 19, tzinfo=tzlocal()),
|
||||||
tr_date=None,
|
tr_date=None,
|
||||||
|
@ -129,9 +127,7 @@ class TestConnectionPool(TestCase):
|
||||||
|
|
||||||
# Mock what happens inside the "with"
|
# Mock what happens inside the "with"
|
||||||
with ExitStack() as stack:
|
with ExitStack() as stack:
|
||||||
stack.enter_context(
|
stack.enter_context(patch.object(EPPConnectionPool, "_create_socket", self.fake_socket))
|
||||||
patch.object(EPPConnectionPool, "_create_socket", self.fake_socket)
|
|
||||||
)
|
|
||||||
stack.enter_context(patch.object(Socket, "connect", self.fake_client))
|
stack.enter_context(patch.object(Socket, "connect", self.fake_client))
|
||||||
stack.enter_context(patch.object(SocketTransport, "send", self.fake_send))
|
stack.enter_context(patch.object(SocketTransport, "send", self.fake_send))
|
||||||
stack.enter_context(patch.object(SocketTransport, "receive", fake_receive))
|
stack.enter_context(patch.object(SocketTransport, "receive", fake_receive))
|
||||||
|
@ -176,9 +172,7 @@ class TestConnectionPool(TestCase):
|
||||||
],
|
],
|
||||||
cl_id="gov2023-ote",
|
cl_id="gov2023-ote",
|
||||||
cr_id="gov2023-ote",
|
cr_id="gov2023-ote",
|
||||||
cr_date=datetime.datetime(
|
cr_date=datetime.datetime(2023, 8, 15, 23, 56, 36, tzinfo=tzlocal()),
|
||||||
2023, 8, 15, 23, 56, 36, tzinfo=tzlocal()
|
|
||||||
),
|
|
||||||
up_id="gov2023-ote",
|
up_id="gov2023-ote",
|
||||||
up_date=datetime.datetime(2023, 8, 17, 2, 3, 19, tzinfo=tzlocal()),
|
up_date=datetime.datetime(2023, 8, 17, 2, 3, 19, tzinfo=tzlocal()),
|
||||||
tr_date=None,
|
tr_date=None,
|
||||||
|
@ -202,9 +196,7 @@ class TestConnectionPool(TestCase):
|
||||||
|
|
||||||
# Mock what happens inside the "with"
|
# Mock what happens inside the "with"
|
||||||
with ExitStack() as stack:
|
with ExitStack() as stack:
|
||||||
stack.enter_context(
|
stack.enter_context(patch.object(EPPConnectionPool, "_create_socket", self.fake_socket))
|
||||||
patch.object(EPPConnectionPool, "_create_socket", self.fake_socket)
|
|
||||||
)
|
|
||||||
stack.enter_context(patch.object(Socket, "connect", self.fake_client))
|
stack.enter_context(patch.object(Socket, "connect", self.fake_client))
|
||||||
stack.enter_context(patch.object(SocketTransport, "send", self.fake_send))
|
stack.enter_context(patch.object(SocketTransport, "send", self.fake_send))
|
||||||
stack.enter_context(patch.object(SocketTransport, "receive", fake_receive))
|
stack.enter_context(patch.object(SocketTransport, "receive", fake_receive))
|
||||||
|
@ -218,9 +210,7 @@ class TestConnectionPool(TestCase):
|
||||||
# that they cannot connect to EPP
|
# that they cannot connect to EPP
|
||||||
with self.assertRaises(RegistryError):
|
with self.assertRaises(RegistryError):
|
||||||
expected = "InfoDomain failed to execute due to a connection error."
|
expected = "InfoDomain failed to execute due to a connection error."
|
||||||
result = registry.send(
|
result = registry.send(commands.InfoDomain(name="test.gov"), cleaned=True)
|
||||||
commands.InfoDomain(name="test.gov"), cleaned=True
|
|
||||||
)
|
|
||||||
self.assertEqual(result, expected)
|
self.assertEqual(result, expected)
|
||||||
|
|
||||||
# A subsequent command should be successful, as the pool restarts
|
# A subsequent command should be successful, as the pool restarts
|
||||||
|
@ -240,9 +230,7 @@ class TestConnectionPool(TestCase):
|
||||||
right as we send a command."""
|
right as we send a command."""
|
||||||
|
|
||||||
with ExitStack() as stack:
|
with ExitStack() as stack:
|
||||||
stack.enter_context(
|
stack.enter_context(patch.object(EPPConnectionPool, "_create_socket", self.fake_socket))
|
||||||
patch.object(EPPConnectionPool, "_create_socket", self.fake_socket)
|
|
||||||
)
|
|
||||||
stack.enter_context(patch.object(Socket, "connect", self.fake_client))
|
stack.enter_context(patch.object(Socket, "connect", self.fake_client))
|
||||||
|
|
||||||
# Pool should be running
|
# Pool should be running
|
||||||
|
@ -252,7 +240,5 @@ class TestConnectionPool(TestCase):
|
||||||
# Try to send a command out - should fail
|
# Try to send a command out - should fail
|
||||||
with self.assertRaises(RegistryError):
|
with self.assertRaises(RegistryError):
|
||||||
expected = "InfoDomain failed to execute due to a connection error."
|
expected = "InfoDomain failed to execute due to a connection error."
|
||||||
result = registry.send(
|
result = registry.send(commands.InfoDomain(name="test.gov"), cleaned=True)
|
||||||
commands.InfoDomain(name="test.gov"), cleaned=True
|
|
||||||
)
|
|
||||||
self.assertEqual(result, expected)
|
self.assertEqual(result, expected)
|
||||||
|
|
|
@ -125,9 +125,7 @@ class EPPConnectionPool(ConnectionPool):
|
||||||
|
|
||||||
# Open multiple connections
|
# Open multiple connections
|
||||||
for i in range(self.size):
|
for i in range(self.size):
|
||||||
self.greenlets.append(
|
self.greenlets.append(gevent.spawn_later(self.spawn_frequency * i, self._addOne))
|
||||||
gevent.spawn_later(self.spawn_frequency * i, self._addOne)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Open a "keepalive" thread if we want to ping open connections
|
# Open a "keepalive" thread if we want to ping open connections
|
||||||
if self.keepalive:
|
if self.keepalive:
|
||||||
|
|
|
@ -28,14 +28,8 @@ class PoolError(Exception):
|
||||||
|
|
||||||
# Used variables due to linter requirements
|
# Used variables due to linter requirements
|
||||||
kill_failed = "Could not kill all connections. Are multiple pools running?"
|
kill_failed = "Could not kill all connections. Are multiple pools running?"
|
||||||
conn_failed = (
|
conn_failed = "Failed to execute due to a registry error. See previous logs to determine the cause of the error."
|
||||||
"Failed to execute due to a registry error."
|
alive_failed = "Failed to keep the connection alive. It is likely that the registry returned a LoginError."
|
||||||
" See previous logs to determine the cause of the error."
|
|
||||||
)
|
|
||||||
alive_failed = (
|
|
||||||
"Failed to keep the connection alive. "
|
|
||||||
"It is likely that the registry returned a LoginError."
|
|
||||||
)
|
|
||||||
_error_mapping = {
|
_error_mapping = {
|
||||||
PoolErrorCodes.KILL_ALL_FAILED: kill_failed,
|
PoolErrorCodes.KILL_ALL_FAILED: kill_failed,
|
||||||
PoolErrorCodes.NEW_CONNECTION_FAILED: conn_failed,
|
PoolErrorCodes.NEW_CONNECTION_FAILED: conn_failed,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
[tool.black]
|
[tool.black]
|
||||||
line-length=88
|
line-length=120
|
||||||
|
|
||||||
[tool.mypy]
|
[tool.mypy]
|
||||||
ignore_missing_imports = true
|
ignore_missing_imports = true
|
||||||
|
|
|
@ -73,9 +73,7 @@ class ListHeaderAdmin(AuditedAdmin):
|
||||||
filters = self.get_filters(request)
|
filters = self.get_filters(request)
|
||||||
# Pass the filtered values to the template context
|
# Pass the filtered values to the template context
|
||||||
extra_context["filters"] = filters
|
extra_context["filters"] = filters
|
||||||
extra_context["search_query"] = request.GET.get(
|
extra_context["search_query"] = request.GET.get("q", "") # Assuming the search query parameter is 'q'
|
||||||
"q", ""
|
|
||||||
) # Assuming the search query parameter is 'q'
|
|
||||||
return super().changelist_view(request, extra_context=extra_context)
|
return super().changelist_view(request, extra_context=extra_context)
|
||||||
|
|
||||||
def get_filters(self, request):
|
def get_filters(self, request):
|
||||||
|
@ -91,11 +89,7 @@ class ListHeaderAdmin(AuditedAdmin):
|
||||||
for param in request.GET.keys():
|
for param in request.GET.keys():
|
||||||
# Exclude the default search parameter 'q'
|
# Exclude the default search parameter 'q'
|
||||||
if param != "q" and param != "o":
|
if param != "q" and param != "o":
|
||||||
parameter_name = (
|
parameter_name = param.replace("__exact", "").replace("_type", "").replace("__id", " id")
|
||||||
param.replace("__exact", "")
|
|
||||||
.replace("_type", "")
|
|
||||||
.replace("__id", " id")
|
|
||||||
)
|
|
||||||
|
|
||||||
if parameter_name == "investigator id":
|
if parameter_name == "investigator id":
|
||||||
# Retrieves the corresponding contact from Users
|
# Retrieves the corresponding contact from Users
|
||||||
|
@ -613,8 +607,7 @@ class DomainApplicationAdmin(ListHeaderAdmin):
|
||||||
|
|
||||||
messages.error(
|
messages.error(
|
||||||
request,
|
request,
|
||||||
"This action is not permitted. The domain "
|
"This action is not permitted. The domain is already active.",
|
||||||
+ "is already active.",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
@ -627,9 +620,7 @@ class DomainApplicationAdmin(ListHeaderAdmin):
|
||||||
models.DomainApplication.APPROVED: obj.approve,
|
models.DomainApplication.APPROVED: obj.approve,
|
||||||
models.DomainApplication.WITHDRAWN: obj.withdraw,
|
models.DomainApplication.WITHDRAWN: obj.withdraw,
|
||||||
models.DomainApplication.REJECTED: obj.reject,
|
models.DomainApplication.REJECTED: obj.reject,
|
||||||
models.DomainApplication.INELIGIBLE: (
|
models.DomainApplication.INELIGIBLE: (obj.reject_with_prejudice),
|
||||||
obj.reject_with_prejudice
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
selected_method = status_method_mapping.get(obj.status)
|
selected_method = status_method_mapping.get(obj.status)
|
||||||
if selected_method is None:
|
if selected_method is None:
|
||||||
|
@ -649,8 +640,7 @@ class DomainApplicationAdmin(ListHeaderAdmin):
|
||||||
|
|
||||||
messages.error(
|
messages.error(
|
||||||
request,
|
request,
|
||||||
"This action is not permitted for applications "
|
"This action is not permitted for applications with a restricted creator.",
|
||||||
+ "with a restricted creator.",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_readonly_fields(self, request, obj=None):
|
def get_readonly_fields(self, request, obj=None):
|
||||||
|
@ -669,9 +659,7 @@ class DomainApplicationAdmin(ListHeaderAdmin):
|
||||||
readonly_fields.extend([field.name for field in self.model._meta.fields])
|
readonly_fields.extend([field.name for field in self.model._meta.fields])
|
||||||
# Add the multi-select fields to readonly_fields:
|
# Add the multi-select fields to readonly_fields:
|
||||||
# Complex fields like ManyToManyField require special handling
|
# Complex fields like ManyToManyField require special handling
|
||||||
readonly_fields.extend(
|
readonly_fields.extend(["current_websites", "other_contacts", "alternative_domains"])
|
||||||
["current_websites", "other_contacts", "alternative_domains"]
|
|
||||||
)
|
|
||||||
|
|
||||||
if request.user.has_perm("registrar.full_access_permission"):
|
if request.user.has_perm("registrar.full_access_permission"):
|
||||||
return readonly_fields
|
return readonly_fields
|
||||||
|
@ -739,9 +727,7 @@ class DomainAdmin(ListHeaderAdmin):
|
||||||
def organization_type(self, obj):
|
def organization_type(self, obj):
|
||||||
return obj.domain_info.get_organization_type_display()
|
return obj.domain_info.get_organization_type_display()
|
||||||
|
|
||||||
organization_type.admin_order_field = ( # type: ignore
|
organization_type.admin_order_field = "domain_info__organization_type" # type: ignore
|
||||||
"domain_info__organization_type"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Filters
|
# Filters
|
||||||
list_filter = ["domain_info__organization_type", "state"]
|
list_filter = ["domain_info__organization_type", "state"]
|
||||||
|
@ -846,9 +832,7 @@ class DomainAdmin(ListHeaderAdmin):
|
||||||
if not err.is_connection_error():
|
if not err.is_connection_error():
|
||||||
# If nothing is found, will default to returned err
|
# If nothing is found, will default to returned err
|
||||||
message = error_messages.get(err.code, err)
|
message = error_messages.get(err.code, err)
|
||||||
self.message_user(
|
self.message_user(request, f"Error deleting this Domain: {message}", messages.ERROR)
|
||||||
request, f"Error deleting this Domain: {message}", messages.ERROR
|
|
||||||
)
|
|
||||||
except TransitionNotAllowed:
|
except TransitionNotAllowed:
|
||||||
if obj.state == Domain.State.DELETED:
|
if obj.state == Domain.State.DELETED:
|
||||||
self.message_user(
|
self.message_user(
|
||||||
|
@ -886,8 +870,7 @@ class DomainAdmin(ListHeaderAdmin):
|
||||||
else:
|
else:
|
||||||
self.message_user(
|
self.message_user(
|
||||||
request,
|
request,
|
||||||
f"The registry statuses are {statuses}. "
|
f"The registry statuses are {statuses}. These statuses are from the provider of the .gov registry.",
|
||||||
"These statuses are from the provider of the .gov registry.",
|
|
||||||
)
|
)
|
||||||
return HttpResponseRedirect(".")
|
return HttpResponseRedirect(".")
|
||||||
|
|
||||||
|
@ -916,11 +899,7 @@ class DomainAdmin(ListHeaderAdmin):
|
||||||
else:
|
else:
|
||||||
self.message_user(
|
self.message_user(
|
||||||
request,
|
request,
|
||||||
(
|
("%s is in client hold. This domain is no longer accessible on the public internet.") % obj.name,
|
||||||
"%s is in client hold. This domain is no longer accessible on"
|
|
||||||
" the public internet."
|
|
||||||
)
|
|
||||||
% obj.name,
|
|
||||||
)
|
)
|
||||||
return HttpResponseRedirect(".")
|
return HttpResponseRedirect(".")
|
||||||
|
|
||||||
|
@ -949,8 +928,7 @@ class DomainAdmin(ListHeaderAdmin):
|
||||||
else:
|
else:
|
||||||
self.message_user(
|
self.message_user(
|
||||||
request,
|
request,
|
||||||
("%s is ready. This domain is accessible on the public internet.")
|
("%s is ready. This domain is accessible on the public internet.") % obj.name,
|
||||||
% obj.name,
|
|
||||||
)
|
)
|
||||||
return HttpResponseRedirect(".")
|
return HttpResponseRedirect(".")
|
||||||
|
|
||||||
|
@ -973,9 +951,9 @@ class DomainAdmin(ListHeaderAdmin):
|
||||||
# Fixes a bug wherein users which are only is_staff
|
# Fixes a bug wherein users which are only is_staff
|
||||||
# can access 'change' when GET,
|
# can access 'change' when GET,
|
||||||
# but cannot access this page when it is a request of type POST.
|
# but cannot access this page when it is a request of type POST.
|
||||||
if request.user.has_perm(
|
if request.user.has_perm("registrar.full_access_permission") or request.user.has_perm(
|
||||||
"registrar.full_access_permission"
|
"registrar.analyst_access_permission"
|
||||||
) or request.user.has_perm("registrar.analyst_access_permission"):
|
):
|
||||||
return True
|
return True
|
||||||
return super().has_change_permission(request, obj)
|
return super().has_change_permission(request, obj)
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.usa-alert__text.measure-none {
|
||||||
|
max-width: measure(none);
|
||||||
|
}
|
||||||
|
|
||||||
// The icon was off center for some reason
|
// The icon was off center for some reason
|
||||||
// Fixes that issue
|
// Fixes that issue
|
||||||
@media (min-width: 64em){
|
@media (min-width: 64em){
|
||||||
|
|
|
@ -26,6 +26,24 @@ a.usa-button {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a.usa-button.disabled-link {
|
||||||
|
background-color: #ccc !important;
|
||||||
|
color: #454545 !important
|
||||||
|
}
|
||||||
|
|
||||||
|
a.usa-button.disabled-link:hover {
|
||||||
|
background-color: #ccc !important;
|
||||||
|
cursor: not-allowed !important;
|
||||||
|
color: #454545 !important
|
||||||
|
}
|
||||||
|
|
||||||
|
a.usa-button.disabled-link:focus {
|
||||||
|
background-color: #ccc !important;
|
||||||
|
cursor: not-allowed !important;
|
||||||
|
outline: none !important;
|
||||||
|
color: #454545 !important
|
||||||
|
}
|
||||||
|
|
||||||
a.usa-button:not(.usa-button--unstyled, .usa-button--outline) {
|
a.usa-button:not(.usa-button--unstyled, .usa-button--outline) {
|
||||||
color: color('white');
|
color: color('white');
|
||||||
}
|
}
|
||||||
|
|
|
@ -132,4 +132,10 @@ in the form $setting: value,
|
||||||
$theme-table-sorted-background-color: "accent-cool-lightest",
|
$theme-table-sorted-background-color: "accent-cool-lightest",
|
||||||
$theme-table-sorted-icon-color: "primary-darker",
|
$theme-table-sorted-icon-color: "primary-darker",
|
||||||
$theme-table-unsorted-icon-color: "primary",
|
$theme-table-unsorted-icon-color: "primary",
|
||||||
|
|
||||||
|
/*----------------------------
|
||||||
|
# Tooltip Settings
|
||||||
|
-----------------------------*/
|
||||||
|
$theme-tooltip-background-color: "accent-cool-lightest",
|
||||||
|
$theme-tooltip-font-color: "black"
|
||||||
);
|
);
|
||||||
|
|
|
@ -46,6 +46,7 @@ path = Path(__file__)
|
||||||
|
|
||||||
env_db_url = env.dj_db_url("DATABASE_URL")
|
env_db_url = env.dj_db_url("DATABASE_URL")
|
||||||
env_debug = env.bool("DJANGO_DEBUG", default=False)
|
env_debug = env.bool("DJANGO_DEBUG", default=False)
|
||||||
|
env_is_production = env.bool("IS_PRODUCTION", default=False)
|
||||||
env_log_level = env.str("DJANGO_LOG_LEVEL", "DEBUG")
|
env_log_level = env.str("DJANGO_LOG_LEVEL", "DEBUG")
|
||||||
env_base_url = env.str("DJANGO_BASE_URL")
|
env_base_url = env.str("DJANGO_BASE_URL")
|
||||||
env_getgov_public_site_url = env.str("GETGOV_PUBLIC_SITE_URL", "")
|
env_getgov_public_site_url = env.str("GETGOV_PUBLIC_SITE_URL", "")
|
||||||
|
@ -73,6 +74,8 @@ BASE_DIR = path.resolve().parent.parent.parent
|
||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
DEBUG = env_debug
|
DEBUG = env_debug
|
||||||
|
|
||||||
|
# Controls production specific feature toggles
|
||||||
|
IS_PRODUCTION = env_is_production
|
||||||
|
|
||||||
# Applications are modular pieces of code.
|
# Applications are modular pieces of code.
|
||||||
# They are provided by Django, by third-parties, or by yourself.
|
# They are provided by Django, by third-parties, or by yourself.
|
||||||
|
@ -207,6 +210,7 @@ TEMPLATES = [
|
||||||
"registrar.context_processors.language_code",
|
"registrar.context_processors.language_code",
|
||||||
"registrar.context_processors.canonical_path",
|
"registrar.context_processors.canonical_path",
|
||||||
"registrar.context_processors.is_demo_site",
|
"registrar.context_processors.is_demo_site",
|
||||||
|
"registrar.context_processors.is_production",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -300,7 +304,7 @@ CSP_FORM_ACTION = allowed_sources
|
||||||
# Sets clients that allow access control to manage.get.gov
|
# Sets clients that allow access control to manage.get.gov
|
||||||
# TODO: remove :8080 to see if we can have all localhost access
|
# TODO: remove :8080 to see if we can have all localhost access
|
||||||
CORS_ALLOWED_ORIGINS = ["http://localhost:8080", "https://beta.get.gov"]
|
CORS_ALLOWED_ORIGINS = ["http://localhost:8080", "https://beta.get.gov"]
|
||||||
|
CORS_ALLOWED_ORIGIN_REGEXES = [r"https://[\w-]+\.sites\.pages\.cloud\.gov"]
|
||||||
|
|
||||||
# Content-Length header is set by django.middleware.common.CommonMiddleware
|
# Content-Length header is set by django.middleware.common.CommonMiddleware
|
||||||
|
|
||||||
|
@ -523,9 +527,7 @@ OIDC_PROVIDERS = {
|
||||||
"acr_value": "http://idmanagement.gov/ns/assurance/ial/2",
|
"acr_value": "http://idmanagement.gov/ns/assurance/ial/2",
|
||||||
},
|
},
|
||||||
"client_registration": {
|
"client_registration": {
|
||||||
"client_id": (
|
"client_id": ("urn:gov:cisa:openidconnect.profiles:sp:sso:cisa:dotgov_registrar"),
|
||||||
"urn:gov:cisa:openidconnect.profiles:sp:sso:cisa:dotgov_registrar"
|
|
||||||
),
|
|
||||||
"redirect_uris": [f"{env_base_url}/openid/callback/login/"],
|
"redirect_uris": [f"{env_base_url}/openid/callback/login/"],
|
||||||
"post_logout_redirect_uris": [f"{env_base_url}/openid/callback/logout/"],
|
"post_logout_redirect_uris": [f"{env_base_url}/openid/callback/logout/"],
|
||||||
"token_endpoint_auth_method": ["private_key_jwt"],
|
"token_endpoint_auth_method": ["private_key_jwt"],
|
||||||
|
|
|
@ -31,3 +31,8 @@ def is_demo_site(request):
|
||||||
should not appear.
|
should not appear.
|
||||||
"""
|
"""
|
||||||
return {"IS_DEMO_SITE": settings.IS_DEMO_SITE}
|
return {"IS_DEMO_SITE": settings.IS_DEMO_SITE}
|
||||||
|
|
||||||
|
|
||||||
|
def is_production(request):
|
||||||
|
"""Add a boolean if this is our production site."""
|
||||||
|
return {"IS_PRODUCTION": settings.IS_PRODUCTION}
|
||||||
|
|
|
@ -97,9 +97,7 @@ class DomainApplicationFixture:
|
||||||
def _set_non_foreign_key_fields(cls, da: DomainApplication, app: dict):
|
def _set_non_foreign_key_fields(cls, da: DomainApplication, app: dict):
|
||||||
"""Helper method used by `load`."""
|
"""Helper method used by `load`."""
|
||||||
da.status = app["status"] if "status" in app else "started"
|
da.status = app["status"] if "status" in app else "started"
|
||||||
da.organization_type = (
|
da.organization_type = app["organization_type"] if "organization_type" in app else "federal"
|
||||||
app["organization_type"] if "organization_type" in app else "federal"
|
|
||||||
)
|
|
||||||
da.federal_agency = (
|
da.federal_agency = (
|
||||||
app["federal_agency"]
|
app["federal_agency"]
|
||||||
if "federal_agency" in app
|
if "federal_agency" in app
|
||||||
|
@ -112,40 +110,25 @@ class DomainApplicationFixture:
|
||||||
if "federal_type" in app
|
if "federal_type" in app
|
||||||
else random.choice(["executive", "judicial", "legislative"]) # nosec
|
else random.choice(["executive", "judicial", "legislative"]) # nosec
|
||||||
)
|
)
|
||||||
da.address_line1 = (
|
da.address_line1 = app["address_line1"] if "address_line1" in app else fake.street_address()
|
||||||
app["address_line1"] if "address_line1" in app else fake.street_address()
|
|
||||||
)
|
|
||||||
da.address_line2 = app["address_line2"] if "address_line2" in app else None
|
da.address_line2 = app["address_line2"] if "address_line2" in app else None
|
||||||
da.city = app["city"] if "city" in app else fake.city()
|
da.city = app["city"] if "city" in app else fake.city()
|
||||||
da.state_territory = (
|
da.state_territory = app["state_territory"] if "state_territory" in app else fake.state_abbr()
|
||||||
app["state_territory"] if "state_territory" in app else fake.state_abbr()
|
|
||||||
)
|
|
||||||
da.zipcode = app["zipcode"] if "zipcode" in app else fake.postalcode()
|
da.zipcode = app["zipcode"] if "zipcode" in app else fake.postalcode()
|
||||||
da.urbanization = app["urbanization"] if "urbanization" in app else None
|
da.urbanization = app["urbanization"] if "urbanization" in app else None
|
||||||
da.purpose = app["purpose"] if "purpose" in app else fake.paragraph()
|
da.purpose = app["purpose"] if "purpose" in app else fake.paragraph()
|
||||||
da.anything_else = app["anything_else"] if "anything_else" in app else None
|
da.anything_else = app["anything_else"] if "anything_else" in app else None
|
||||||
da.is_policy_acknowledged = (
|
da.is_policy_acknowledged = app["is_policy_acknowledged"] if "is_policy_acknowledged" in app else True
|
||||||
app["is_policy_acknowledged"] if "is_policy_acknowledged" in app else True
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _set_foreign_key_fields(cls, da: DomainApplication, app: dict, user: User):
|
def _set_foreign_key_fields(cls, da: DomainApplication, app: dict, user: User):
|
||||||
"""Helper method used by `load`."""
|
"""Helper method used by `load`."""
|
||||||
if not da.investigator:
|
if not da.investigator:
|
||||||
da.investigator = (
|
da.investigator = User.objects.get(username=user.username) if "investigator" in app else None
|
||||||
User.objects.get(username=user.username)
|
|
||||||
if "investigator" in app
|
|
||||||
else None
|
|
||||||
)
|
|
||||||
|
|
||||||
if not da.authorizing_official:
|
if not da.authorizing_official:
|
||||||
if (
|
if "authorizing_official" in app and app["authorizing_official"] is not None:
|
||||||
"authorizing_official" in app
|
da.authorizing_official, _ = Contact.objects.get_or_create(**app["authorizing_official"])
|
||||||
and app["authorizing_official"] is not None
|
|
||||||
):
|
|
||||||
da.authorizing_official, _ = Contact.objects.get_or_create(
|
|
||||||
**app["authorizing_official"]
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
da.authorizing_official = Contact.objects.create(**cls.fake_contact())
|
da.authorizing_official = Contact.objects.create(**cls.fake_contact())
|
||||||
|
|
||||||
|
@ -157,13 +140,9 @@ class DomainApplicationFixture:
|
||||||
|
|
||||||
if not da.requested_domain:
|
if not da.requested_domain:
|
||||||
if "requested_domain" in app and app["requested_domain"] is not None:
|
if "requested_domain" in app and app["requested_domain"] is not None:
|
||||||
da.requested_domain, _ = DraftDomain.objects.get_or_create(
|
da.requested_domain, _ = DraftDomain.objects.get_or_create(name=app["requested_domain"])
|
||||||
name=app["requested_domain"]
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
da.requested_domain = DraftDomain.objects.create(
|
da.requested_domain = DraftDomain.objects.create(name=cls.fake_dot_gov())
|
||||||
name=cls.fake_dot_gov()
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _set_many_to_many_relations(cls, da: DomainApplication, app: dict):
|
def _set_many_to_many_relations(cls, da: DomainApplication, app: dict):
|
||||||
|
@ -173,32 +152,25 @@ class DomainApplicationFixture:
|
||||||
da.other_contacts.add(Contact.objects.get_or_create(**contact)[0])
|
da.other_contacts.add(Contact.objects.get_or_create(**contact)[0])
|
||||||
elif not da.other_contacts.exists():
|
elif not da.other_contacts.exists():
|
||||||
other_contacts = [
|
other_contacts = [
|
||||||
Contact.objects.create(**cls.fake_contact())
|
Contact.objects.create(**cls.fake_contact()) for _ in range(random.randint(0, 3)) # nosec
|
||||||
for _ in range(random.randint(0, 3)) # nosec
|
|
||||||
]
|
]
|
||||||
da.other_contacts.add(*other_contacts)
|
da.other_contacts.add(*other_contacts)
|
||||||
|
|
||||||
if "current_websites" in app:
|
if "current_websites" in app:
|
||||||
for website in app["current_websites"]:
|
for website in app["current_websites"]:
|
||||||
da.current_websites.add(
|
da.current_websites.add(Website.objects.get_or_create(website=website)[0])
|
||||||
Website.objects.get_or_create(website=website)[0]
|
|
||||||
)
|
|
||||||
elif not da.current_websites.exists():
|
elif not da.current_websites.exists():
|
||||||
current_websites = [
|
current_websites = [
|
||||||
Website.objects.create(website=fake.uri())
|
Website.objects.create(website=fake.uri()) for _ in range(random.randint(0, 3)) # nosec
|
||||||
for _ in range(random.randint(0, 3)) # nosec
|
|
||||||
]
|
]
|
||||||
da.current_websites.add(*current_websites)
|
da.current_websites.add(*current_websites)
|
||||||
|
|
||||||
if "alternative_domains" in app:
|
if "alternative_domains" in app:
|
||||||
for domain in app["alternative_domains"]:
|
for domain in app["alternative_domains"]:
|
||||||
da.alternative_domains.add(
|
da.alternative_domains.add(Website.objects.get_or_create(website=domain)[0])
|
||||||
Website.objects.get_or_create(website=domain)[0]
|
|
||||||
)
|
|
||||||
elif not da.alternative_domains.exists():
|
elif not da.alternative_domains.exists():
|
||||||
alternative_domains = [
|
alternative_domains = [
|
||||||
Website.objects.create(website=cls.fake_dot_gov())
|
Website.objects.create(website=cls.fake_dot_gov()) for _ in range(random.randint(0, 3)) # nosec
|
||||||
for _ in range(random.randint(0, 3)) # nosec
|
|
||||||
]
|
]
|
||||||
da.alternative_domains.add(*alternative_domains)
|
da.alternative_domains.add(*alternative_domains)
|
||||||
|
|
||||||
|
@ -242,9 +214,7 @@ class DomainFixture(DomainApplicationFixture):
|
||||||
|
|
||||||
for user in users:
|
for user in users:
|
||||||
# approve one of each users in review status domains
|
# approve one of each users in review status domains
|
||||||
application = DomainApplication.objects.filter(
|
application = DomainApplication.objects.filter(creator=user, status=DomainApplication.IN_REVIEW).last()
|
||||||
creator=user, status=DomainApplication.IN_REVIEW
|
|
||||||
).last()
|
|
||||||
logger.debug(f"Approving {application} for {user}")
|
logger.debug(f"Approving {application} for {user}")
|
||||||
application.approve()
|
application.approve()
|
||||||
application.save()
|
application.save()
|
||||||
|
|
|
@ -50,9 +50,7 @@ class RegistrarForm(forms.Form):
|
||||||
"""Returns a dict of form field values gotten from `obj`."""
|
"""Returns a dict of form field values gotten from `obj`."""
|
||||||
if obj is None:
|
if obj is None:
|
||||||
return {}
|
return {}
|
||||||
return {
|
return {name: getattr(obj, name) for name in cls.declared_fields.keys()} # type: ignore
|
||||||
name: getattr(obj, name) for name in cls.declared_fields.keys()
|
|
||||||
} # type: ignore
|
|
||||||
|
|
||||||
|
|
||||||
class RegistrarFormSet(forms.BaseFormSet):
|
class RegistrarFormSet(forms.BaseFormSet):
|
||||||
|
@ -178,10 +176,7 @@ class TribalGovernmentForm(RegistrarForm):
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
"""Needs to be either state or federally recognized."""
|
"""Needs to be either state or federally recognized."""
|
||||||
if not (
|
if not (self.cleaned_data["federally_recognized_tribe"] or self.cleaned_data["state_recognized_tribe"]):
|
||||||
self.cleaned_data["federally_recognized_tribe"]
|
|
||||||
or self.cleaned_data["state_recognized_tribe"]
|
|
||||||
):
|
|
||||||
raise forms.ValidationError(
|
raise forms.ValidationError(
|
||||||
# no sec because we are using it to include an internal URL
|
# no sec because we are using it to include an internal URL
|
||||||
# into a link. There should be no user-facing input in the
|
# into a link. There should be no user-facing input in the
|
||||||
|
@ -203,11 +198,7 @@ class OrganizationFederalForm(RegistrarForm):
|
||||||
federal_type = forms.ChoiceField(
|
federal_type = forms.ChoiceField(
|
||||||
choices=DomainApplication.BranchChoices.choices,
|
choices=DomainApplication.BranchChoices.choices,
|
||||||
widget=forms.RadioSelect,
|
widget=forms.RadioSelect,
|
||||||
error_messages={
|
error_messages={"required": ("Select the part of the federal government your organization is in.")},
|
||||||
"required": (
|
|
||||||
"Select the part of the federal government your organization is in."
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -227,10 +218,7 @@ class OrganizationElectionForm(RegistrarForm):
|
||||||
is_election_board = self.cleaned_data["is_election_board"]
|
is_election_board = self.cleaned_data["is_election_board"]
|
||||||
if is_election_board is None:
|
if is_election_board is None:
|
||||||
raise forms.ValidationError(
|
raise forms.ValidationError(
|
||||||
(
|
("Select “Yes” if you represent an election office. Select “No” if you don’t."),
|
||||||
"Select “Yes” if you represent an election office. Select “No” if"
|
|
||||||
" you don’t."
|
|
||||||
),
|
|
||||||
code="required",
|
code="required",
|
||||||
)
|
)
|
||||||
return is_election_board
|
return is_election_board
|
||||||
|
@ -260,18 +248,13 @@ class OrganizationContactForm(RegistrarForm):
|
||||||
)
|
)
|
||||||
city = forms.CharField(
|
city = forms.CharField(
|
||||||
label="City",
|
label="City",
|
||||||
error_messages={
|
error_messages={"required": "Enter the city where your organization is located."},
|
||||||
"required": "Enter the city where your organization is located."
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
state_territory = forms.ChoiceField(
|
state_territory = forms.ChoiceField(
|
||||||
label="State, territory, or military post",
|
label="State, territory, or military post",
|
||||||
choices=[("", "--Select--")] + DomainApplication.StateTerritoryChoices.choices,
|
choices=[("", "--Select--")] + DomainApplication.StateTerritoryChoices.choices,
|
||||||
error_messages={
|
error_messages={
|
||||||
"required": (
|
"required": ("Select the state, territory, or military post where your organization is located.")
|
||||||
"Select the state, territory, or military post where your organization"
|
|
||||||
" is located."
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
zipcode = forms.CharField(
|
zipcode = forms.CharField(
|
||||||
|
@ -320,9 +303,7 @@ class AboutYourOrganizationForm(RegistrarForm):
|
||||||
message="Response must be less than 1000 characters.",
|
message="Response must be less than 1000 characters.",
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
error_messages={
|
error_messages={"required": ("Enter more information about your organization.")},
|
||||||
"required": ("Enter more information about your organization.")
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -346,11 +327,7 @@ class AuthorizingOfficialForm(RegistrarForm):
|
||||||
|
|
||||||
first_name = forms.CharField(
|
first_name = forms.CharField(
|
||||||
label="First name / given name",
|
label="First name / given name",
|
||||||
error_messages={
|
error_messages={"required": ("Enter the first name / given name of your authorizing official.")},
|
||||||
"required": (
|
|
||||||
"Enter the first name / given name of your authorizing official."
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
middle_name = forms.CharField(
|
middle_name = forms.CharField(
|
||||||
required=False,
|
required=False,
|
||||||
|
@ -358,11 +335,7 @@ class AuthorizingOfficialForm(RegistrarForm):
|
||||||
)
|
)
|
||||||
last_name = forms.CharField(
|
last_name = forms.CharField(
|
||||||
label="Last name / family name",
|
label="Last name / family name",
|
||||||
error_messages={
|
error_messages={"required": ("Enter the last name / family name of your authorizing official.")},
|
||||||
"required": (
|
|
||||||
"Enter the last name / family name of your authorizing official."
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
title = forms.CharField(
|
title = forms.CharField(
|
||||||
label="Title or role in your organization",
|
label="Title or role in your organization",
|
||||||
|
@ -375,17 +348,11 @@ class AuthorizingOfficialForm(RegistrarForm):
|
||||||
)
|
)
|
||||||
email = forms.EmailField(
|
email = forms.EmailField(
|
||||||
label="Email",
|
label="Email",
|
||||||
error_messages={
|
error_messages={"invalid": ("Enter an email address in the required format, like name@example.com.")},
|
||||||
"invalid": (
|
|
||||||
"Enter an email address in the required format, like name@example.com."
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
phone = PhoneNumberField(
|
phone = PhoneNumberField(
|
||||||
label="Phone",
|
label="Phone",
|
||||||
error_messages={
|
error_messages={"required": "Enter the phone number for your authorizing official."},
|
||||||
"required": "Enter the phone number for your authorizing official."
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -394,10 +361,7 @@ class CurrentSitesForm(RegistrarForm):
|
||||||
required=False,
|
required=False,
|
||||||
label="Public website",
|
label="Public website",
|
||||||
error_messages={
|
error_messages={
|
||||||
"invalid": (
|
"invalid": ("Enter your organization's current website in the required format, like www.city.com.")
|
||||||
"Enter your organization's current website in the required format, like"
|
|
||||||
" www.city.com."
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -410,9 +374,7 @@ class BaseCurrentSitesFormSet(RegistrarFormSet):
|
||||||
return website.strip() == ""
|
return website.strip() == ""
|
||||||
|
|
||||||
def to_database(self, obj: DomainApplication):
|
def to_database(self, obj: DomainApplication):
|
||||||
self._to_database(
|
self._to_database(obj, self.JOIN, self.should_delete, self.pre_update, self.pre_create)
|
||||||
obj, self.JOIN, self.should_delete, self.pre_update, self.pre_create
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_database(cls, obj):
|
def from_database(cls, obj):
|
||||||
|
@ -434,13 +396,9 @@ class AlternativeDomainForm(RegistrarForm):
|
||||||
requested = self.cleaned_data.get("alternative_domain", None)
|
requested = self.cleaned_data.get("alternative_domain", None)
|
||||||
validated = DraftDomain.validate(requested, blank_ok=True)
|
validated = DraftDomain.validate(requested, blank_ok=True)
|
||||||
except errors.ExtraDotsError:
|
except errors.ExtraDotsError:
|
||||||
raise forms.ValidationError(
|
raise forms.ValidationError(DOMAIN_API_MESSAGES["extra_dots"], code="extra_dots")
|
||||||
DOMAIN_API_MESSAGES["extra_dots"], code="extra_dots"
|
|
||||||
)
|
|
||||||
except errors.DomainUnavailableError:
|
except errors.DomainUnavailableError:
|
||||||
raise forms.ValidationError(
|
raise forms.ValidationError(DOMAIN_API_MESSAGES["unavailable"], code="unavailable")
|
||||||
DOMAIN_API_MESSAGES["unavailable"], code="unavailable"
|
|
||||||
)
|
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise forms.ValidationError(DOMAIN_API_MESSAGES["invalid"], code="invalid")
|
raise forms.ValidationError(DOMAIN_API_MESSAGES["invalid"], code="invalid")
|
||||||
return validated
|
return validated
|
||||||
|
@ -471,9 +429,7 @@ class BaseAlternativeDomainFormSet(RegistrarFormSet):
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def to_database(self, obj: DomainApplication):
|
def to_database(self, obj: DomainApplication):
|
||||||
self._to_database(
|
self._to_database(obj, self.JOIN, self.should_delete, self.pre_update, self.pre_create)
|
||||||
obj, self.JOIN, self.should_delete, self.pre_update, self.pre_create
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def on_fetch(cls, query):
|
def on_fetch(cls, query):
|
||||||
|
@ -523,17 +479,11 @@ class DotGovDomainForm(RegistrarForm):
|
||||||
requested = self.cleaned_data.get("requested_domain", None)
|
requested = self.cleaned_data.get("requested_domain", None)
|
||||||
validated = DraftDomain.validate(requested)
|
validated = DraftDomain.validate(requested)
|
||||||
except errors.BlankValueError:
|
except errors.BlankValueError:
|
||||||
raise forms.ValidationError(
|
raise forms.ValidationError(DOMAIN_API_MESSAGES["required"], code="required")
|
||||||
DOMAIN_API_MESSAGES["required"], code="required"
|
|
||||||
)
|
|
||||||
except errors.ExtraDotsError:
|
except errors.ExtraDotsError:
|
||||||
raise forms.ValidationError(
|
raise forms.ValidationError(DOMAIN_API_MESSAGES["extra_dots"], code="extra_dots")
|
||||||
DOMAIN_API_MESSAGES["extra_dots"], code="extra_dots"
|
|
||||||
)
|
|
||||||
except errors.DomainUnavailableError:
|
except errors.DomainUnavailableError:
|
||||||
raise forms.ValidationError(
|
raise forms.ValidationError(DOMAIN_API_MESSAGES["unavailable"], code="unavailable")
|
||||||
DOMAIN_API_MESSAGES["unavailable"], code="unavailable"
|
|
||||||
)
|
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise forms.ValidationError(DOMAIN_API_MESSAGES["invalid"], code="invalid")
|
raise forms.ValidationError(DOMAIN_API_MESSAGES["invalid"], code="invalid")
|
||||||
return validated
|
return validated
|
||||||
|
@ -551,9 +501,7 @@ class PurposeForm(RegistrarForm):
|
||||||
message="Response must be less than 1000 characters.",
|
message="Response must be less than 1000 characters.",
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
error_messages={
|
error_messages={"required": "Describe how you'll use the .gov domain you’re requesting."},
|
||||||
"required": "Describe how you'll use the .gov domain you’re requesting."
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -590,20 +538,12 @@ class YourContactForm(RegistrarForm):
|
||||||
title = forms.CharField(
|
title = forms.CharField(
|
||||||
label="Title or role in your organization",
|
label="Title or role in your organization",
|
||||||
error_messages={
|
error_messages={
|
||||||
"required": (
|
"required": ("Enter your title or role in your organization (e.g., Chief Information Officer).")
|
||||||
"Enter your title or role in your organization (e.g., Chief Information"
|
|
||||||
" Officer)."
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
email = forms.EmailField(
|
email = forms.EmailField(
|
||||||
label="Email",
|
label="Email",
|
||||||
error_messages={
|
error_messages={"invalid": ("Enter your email address in the required format, like name@example.com.")},
|
||||||
"invalid": (
|
|
||||||
"Enter your email address in the required format, like"
|
|
||||||
" name@example.com."
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
phone = PhoneNumberField(
|
phone = PhoneNumberField(
|
||||||
label="Phone",
|
label="Phone",
|
||||||
|
@ -614,9 +554,7 @@ class YourContactForm(RegistrarForm):
|
||||||
class OtherContactsForm(RegistrarForm):
|
class OtherContactsForm(RegistrarForm):
|
||||||
first_name = forms.CharField(
|
first_name = forms.CharField(
|
||||||
label="First name / given name",
|
label="First name / given name",
|
||||||
error_messages={
|
error_messages={"required": "Enter the first name / given name of this contact."},
|
||||||
"required": "Enter the first name / given name of this contact."
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
middle_name = forms.CharField(
|
middle_name = forms.CharField(
|
||||||
required=False,
|
required=False,
|
||||||
|
@ -624,26 +562,19 @@ class OtherContactsForm(RegistrarForm):
|
||||||
)
|
)
|
||||||
last_name = forms.CharField(
|
last_name = forms.CharField(
|
||||||
label="Last name / family name",
|
label="Last name / family name",
|
||||||
error_messages={
|
error_messages={"required": "Enter the last name / family name of this contact."},
|
||||||
"required": "Enter the last name / family name of this contact."
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
title = forms.CharField(
|
title = forms.CharField(
|
||||||
label="Title or role in your organization",
|
label="Title or role in your organization",
|
||||||
error_messages={
|
error_messages={
|
||||||
"required": (
|
"required": (
|
||||||
"Enter the title or role in your organization of this contact (e.g.,"
|
"Enter the title or role in your organization of this contact (e.g., Chief Information Officer)."
|
||||||
" Chief Information Officer)."
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
email = forms.EmailField(
|
email = forms.EmailField(
|
||||||
label="Email",
|
label="Email",
|
||||||
error_messages={
|
error_messages={"invalid": ("Enter an email address in the required format, like name@example.com.")},
|
||||||
"invalid": (
|
|
||||||
"Enter an email address in the required format, like name@example.com."
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
phone = PhoneNumberField(
|
phone = PhoneNumberField(
|
||||||
label="Phone",
|
label="Phone",
|
||||||
|
@ -659,9 +590,7 @@ class BaseOtherContactsFormSet(RegistrarFormSet):
|
||||||
return all(empty)
|
return all(empty)
|
||||||
|
|
||||||
def to_database(self, obj: DomainApplication):
|
def to_database(self, obj: DomainApplication):
|
||||||
self._to_database(
|
self._to_database(obj, self.JOIN, self.should_delete, self.pre_update, self.pre_create)
|
||||||
obj, self.JOIN, self.should_delete, self.pre_update, self.pre_create
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_database(cls, obj):
|
def from_database(cls, obj):
|
||||||
|
@ -706,9 +635,6 @@ class RequirementsForm(RegistrarForm):
|
||||||
is_policy_acknowledged = forms.BooleanField(
|
is_policy_acknowledged = forms.BooleanField(
|
||||||
label="I read and agree to the requirements for operating .gov domains.",
|
label="I read and agree to the requirements for operating .gov domains.",
|
||||||
error_messages={
|
error_messages={
|
||||||
"required": (
|
"required": ("Check the box if you read and agree to the requirements for operating .gov domains.")
|
||||||
"Check the box if you read and agree to the requirements for"
|
|
||||||
" operating .gov domains."
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
@ -95,23 +95,17 @@ class DomainNameserverForm(forms.Form):
|
||||||
elif e.code == nsErrorCodes.MISSING_IP:
|
elif e.code == nsErrorCodes.MISSING_IP:
|
||||||
self.add_error(
|
self.add_error(
|
||||||
"ip",
|
"ip",
|
||||||
NameserverError(
|
NameserverError(code=nsErrorCodes.MISSING_IP, nameserver=domain, ip=ip_list),
|
||||||
code=nsErrorCodes.MISSING_IP, nameserver=domain, ip=ip_list
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
elif e.code == nsErrorCodes.MISSING_HOST:
|
elif e.code == nsErrorCodes.MISSING_HOST:
|
||||||
self.add_error(
|
self.add_error(
|
||||||
"server",
|
"server",
|
||||||
NameserverError(
|
NameserverError(code=nsErrorCodes.MISSING_HOST, nameserver=domain, ip=ip_list),
|
||||||
code=nsErrorCodes.MISSING_HOST, nameserver=domain, ip=ip_list
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
elif e.code == nsErrorCodes.INVALID_HOST:
|
elif e.code == nsErrorCodes.INVALID_HOST:
|
||||||
self.add_error(
|
self.add_error(
|
||||||
"server",
|
"server",
|
||||||
NameserverError(
|
NameserverError(code=nsErrorCodes.INVALID_HOST, nameserver=server, ip=ip_list),
|
||||||
code=nsErrorCodes.INVALID_HOST, nameserver=server, ip=ip_list
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.add_error("ip", str(e))
|
self.add_error("ip", str(e))
|
||||||
|
@ -187,17 +181,12 @@ class DomainOrgNameAddressForm(forms.ModelForm):
|
||||||
"urbanization",
|
"urbanization",
|
||||||
]
|
]
|
||||||
error_messages = {
|
error_messages = {
|
||||||
"federal_agency": {
|
"federal_agency": {"required": "Select the federal agency for your organization."},
|
||||||
"required": "Select the federal agency for your organization."
|
|
||||||
},
|
|
||||||
"organization_name": {"required": "Enter the name of your organization."},
|
"organization_name": {"required": "Enter the name of your organization."},
|
||||||
"address_line1": {
|
"address_line1": {"required": "Enter the street address of your organization."},
|
||||||
"required": "Enter the street address of your organization."
|
|
||||||
},
|
|
||||||
"city": {"required": "Enter the city where your organization is located."},
|
"city": {"required": "Enter the city where your organization is located."},
|
||||||
"state_territory": {
|
"state_territory": {
|
||||||
"required": "Select the state, territory, or military post where your"
|
"required": "Select the state, territory, or military post where your organization is located."
|
||||||
"organization is located."
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
widgets = {
|
widgets = {
|
||||||
|
@ -205,9 +194,7 @@ class DomainOrgNameAddressForm(forms.ModelForm):
|
||||||
# state/territory because for these fields we are creating an individual
|
# state/territory because for these fields we are creating an individual
|
||||||
# instance of the Select. For the other fields we use the for loop to set
|
# instance of the Select. For the other fields we use the for loop to set
|
||||||
# the class's required attribute to true.
|
# the class's required attribute to true.
|
||||||
"federal_agency": forms.Select(
|
"federal_agency": forms.Select(attrs={"required": True}, choices=DomainInformation.AGENCY_CHOICES),
|
||||||
attrs={"required": True}, choices=DomainInformation.AGENCY_CHOICES
|
|
||||||
),
|
|
||||||
"organization_name": forms.TextInput,
|
"organization_name": forms.TextInput,
|
||||||
"address_line1": forms.TextInput,
|
"address_line1": forms.TextInput,
|
||||||
"address_line2": forms.TextInput,
|
"address_line2": forms.TextInput,
|
||||||
|
|
|
@ -23,9 +23,7 @@ class Command(BaseCommand):
|
||||||
default="txt",
|
default="txt",
|
||||||
help="What file extensions to look for, like txt or gz",
|
help="What file extensions to look for, like txt or gz",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument("--directory", default="migrationdata", help="Desired directory")
|
||||||
"--directory", default="migrationdata", help="Desired directory"
|
|
||||||
)
|
|
||||||
|
|
||||||
def handle(self, **options):
|
def handle(self, **options):
|
||||||
file_extension: str = options.get("file_extension").lstrip(".")
|
file_extension: str = options.get("file_extension").lstrip(".")
|
||||||
|
|
|
@ -52,20 +52,15 @@ class Command(BaseCommand):
|
||||||
if result.returncode:
|
if result.returncode:
|
||||||
self.stderr.write(
|
self.stderr.write(
|
||||||
self.style.NOTICE(
|
self.style.NOTICE(
|
||||||
"[manage.py lint] Re-try with: [docker-compose exec app] "
|
"[manage.py lint] Re-try with: [docker-compose exec app] " f"{' '.join(linter['args'])}"
|
||||||
f"{' '.join(linter['args'])}"
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
errors.append(CalledProcessError(result.returncode, linter["args"]))
|
errors.append(CalledProcessError(result.returncode, linter["args"]))
|
||||||
else:
|
else:
|
||||||
self.stdout.write(
|
self.stdout.write(f"[manage.py lint] {linter['purpose']} completed with success!")
|
||||||
f"[manage.py lint] {linter['purpose']} completed with success!"
|
|
||||||
)
|
|
||||||
if errors:
|
if errors:
|
||||||
self.stdout.write(f"[manage.py lint] {len(errors)} linter(s) failed.")
|
self.stdout.write(f"[manage.py lint] {len(errors)} linter(s) failed.")
|
||||||
raise LinterError(errors)
|
raise LinterError(errors)
|
||||||
except (CalledProcessError, LinterError) as e:
|
except (CalledProcessError, LinterError) as e:
|
||||||
raise CommandError(e)
|
raise CommandError(e)
|
||||||
self.stdout.write(
|
self.stdout.write(self.style.SUCCESS("[manage.py lint] All linters ran successfully."))
|
||||||
self.style.SUCCESS("[manage.py lint] All linters ran successfully.")
|
|
||||||
)
|
|
||||||
|
|
|
@ -21,9 +21,7 @@ class Command(BaseCommand):
|
||||||
"domain_contacts_filename",
|
"domain_contacts_filename",
|
||||||
help="Data file with domain contact information",
|
help="Data file with domain contact information",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument("contacts_filename", help="Data file with contact information")
|
||||||
"contacts_filename", help="Data file with contact information"
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.add_argument("--sep", default="|", help="Delimiter character")
|
parser.add_argument("--sep", default="|", help="Delimiter character")
|
||||||
|
|
||||||
|
|
|
@ -43,9 +43,7 @@ class Command(BaseCommand):
|
||||||
help = "Load domain data from a delimited text file on stdin."
|
help = "Load domain data from a delimited text file on stdin."
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
def add_arguments(self, parser):
|
||||||
parser.add_argument(
|
parser.add_argument("--sep", default="|", help="Separator character for data file")
|
||||||
"--sep", default="|", help="Separator character for data file"
|
|
||||||
)
|
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
separator_character = options.get("sep")
|
separator_character = options.get("sep")
|
||||||
|
|
|
@ -36,24 +36,18 @@ class Command(BaseCommand):
|
||||||
Use this to trigger a prompt for deleting all table entries. Useful
|
Use this to trigger a prompt for deleting all table entries. Useful
|
||||||
for testing purposes, but USE WITH CAUTION
|
for testing purposes, but USE WITH CAUTION
|
||||||
"""
|
"""
|
||||||
parser.add_argument(
|
parser.add_argument("domain_contacts_filename", help="Data file with domain contact information")
|
||||||
"domain_contacts_filename", help="Data file with domain contact information"
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"contacts_filename",
|
"contacts_filename",
|
||||||
help="Data file with contact information",
|
help="Data file with contact information",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument("domain_statuses_filename", help="Data file with domain status information")
|
||||||
"domain_statuses_filename", help="Data file with domain status information"
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.add_argument("--sep", default="|", help="Delimiter character")
|
parser.add_argument("--sep", default="|", help="Delimiter character")
|
||||||
|
|
||||||
parser.add_argument("--debug", action=argparse.BooleanOptionalAction)
|
parser.add_argument("--debug", action=argparse.BooleanOptionalAction)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument("--limitParse", default=0, help="Sets max number of entries to load")
|
||||||
"--limitParse", default=0, help="Sets max number of entries to load"
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--resetTable",
|
"--resetTable",
|
||||||
|
@ -61,9 +55,7 @@ class Command(BaseCommand):
|
||||||
action=argparse.BooleanOptionalAction,
|
action=argparse.BooleanOptionalAction,
|
||||||
)
|
)
|
||||||
|
|
||||||
def print_debug_mode_statements(
|
def print_debug_mode_statements(self, debug_on: bool, debug_max_entries_to_parse: int):
|
||||||
self, debug_on: bool, debug_max_entries_to_parse: int
|
|
||||||
):
|
|
||||||
"""Prints additional terminal statements to indicate if --debug
|
"""Prints additional terminal statements to indicate if --debug
|
||||||
or --limitParse are in use"""
|
or --limitParse are in use"""
|
||||||
if debug_on:
|
if debug_on:
|
||||||
|
@ -85,9 +77,7 @@ class Command(BaseCommand):
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_domain_user_dict(
|
def get_domain_user_dict(self, domain_statuses_filename: str, sep: str) -> defaultdict[str, str]:
|
||||||
self, domain_statuses_filename: str, sep: str
|
|
||||||
) -> defaultdict[str, str]:
|
|
||||||
"""Creates a mapping of domain name -> status"""
|
"""Creates a mapping of domain name -> status"""
|
||||||
domain_status_dictionary = defaultdict(str)
|
domain_status_dictionary = defaultdict(str)
|
||||||
logger.info("Reading domain statuses data file %s", domain_statuses_filename)
|
logger.info("Reading domain statuses data file %s", domain_statuses_filename)
|
||||||
|
@ -99,9 +89,7 @@ class Command(BaseCommand):
|
||||||
logger.info("Loaded statuses for %d domains", len(domain_status_dictionary))
|
logger.info("Loaded statuses for %d domains", len(domain_status_dictionary))
|
||||||
return domain_status_dictionary
|
return domain_status_dictionary
|
||||||
|
|
||||||
def get_user_emails_dict(
|
def get_user_emails_dict(self, contacts_filename: str, sep) -> defaultdict[str, str]:
|
||||||
self, contacts_filename: str, sep
|
|
||||||
) -> defaultdict[str, str]:
|
|
||||||
"""Creates mapping of userId -> emails"""
|
"""Creates mapping of userId -> emails"""
|
||||||
user_emails_dictionary = defaultdict(str)
|
user_emails_dictionary = defaultdict(str)
|
||||||
logger.info("Reading contacts data file %s", contacts_filename)
|
logger.info("Reading contacts data file %s", contacts_filename)
|
||||||
|
@ -149,19 +137,13 @@ class Command(BaseCommand):
|
||||||
total_duplicate_domains = len(duplicate_domains)
|
total_duplicate_domains = len(duplicate_domains)
|
||||||
total_users_without_email = len(users_without_email)
|
total_users_without_email = len(users_without_email)
|
||||||
if total_users_without_email > 0:
|
if total_users_without_email > 0:
|
||||||
users_without_email_as_string = "{}".format(
|
users_without_email_as_string = "{}".format(", ".join(map(str, duplicate_domain_user_combos)))
|
||||||
", ".join(map(str, duplicate_domain_user_combos))
|
|
||||||
)
|
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"{TerminalColors.YELLOW} No e-mails found for users: {users_without_email_as_string}" # noqa
|
f"{TerminalColors.YELLOW} No e-mails found for users: {users_without_email_as_string}" # noqa
|
||||||
)
|
)
|
||||||
if total_duplicate_pairs > 0 or total_duplicate_domains > 0:
|
if total_duplicate_pairs > 0 or total_duplicate_domains > 0:
|
||||||
duplicate_pairs_as_string = "{}".format(
|
duplicate_pairs_as_string = "{}".format(", ".join(map(str, duplicate_domain_user_combos)))
|
||||||
", ".join(map(str, duplicate_domain_user_combos))
|
duplicate_domains_as_string = "{}".format(", ".join(map(str, duplicate_domains)))
|
||||||
)
|
|
||||||
duplicate_domains_as_string = "{}".format(
|
|
||||||
", ".join(map(str, duplicate_domains))
|
|
||||||
)
|
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"""{TerminalColors.YELLOW}
|
f"""{TerminalColors.YELLOW}
|
||||||
|
|
||||||
|
@ -179,9 +161,7 @@ class Command(BaseCommand):
|
||||||
{TerminalColors.ENDC}"""
|
{TerminalColors.ENDC}"""
|
||||||
)
|
)
|
||||||
|
|
||||||
def print_summary_status_findings(
|
def print_summary_status_findings(self, domains_without_status: list[str], outlier_statuses: list[str]):
|
||||||
self, domains_without_status: list[str], outlier_statuses: list[str]
|
|
||||||
):
|
|
||||||
"""Called at the end of the script execution to print out a summary of
|
"""Called at the end of the script execution to print out a summary of
|
||||||
status anomolies in the imported Verisign data. Currently, we check for:
|
status anomolies in the imported Verisign data. Currently, we check for:
|
||||||
- domains without a status
|
- domains without a status
|
||||||
|
@ -191,9 +171,7 @@ class Command(BaseCommand):
|
||||||
total_domains_without_status = len(domains_without_status)
|
total_domains_without_status = len(domains_without_status)
|
||||||
total_outlier_statuses = len(outlier_statuses)
|
total_outlier_statuses = len(outlier_statuses)
|
||||||
if total_domains_without_status > 0:
|
if total_domains_without_status > 0:
|
||||||
domains_without_status_as_string = "{}".format(
|
domains_without_status_as_string = "{}".format(", ".join(map(str, domains_without_status)))
|
||||||
", ".join(map(str, domains_without_status))
|
|
||||||
)
|
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"""{TerminalColors.YELLOW}
|
f"""{TerminalColors.YELLOW}
|
||||||
|
|
||||||
|
@ -207,9 +185,7 @@ class Command(BaseCommand):
|
||||||
)
|
)
|
||||||
|
|
||||||
if total_outlier_statuses > 0:
|
if total_outlier_statuses > 0:
|
||||||
domains_without_status_as_string = "{}".format(
|
domains_without_status_as_string = "{}".format(", ".join(map(str, outlier_statuses))) # noqa
|
||||||
", ".join(map(str, outlier_statuses))
|
|
||||||
) # noqa
|
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f"""{TerminalColors.YELLOW}
|
f"""{TerminalColors.YELLOW}
|
||||||
|
|
||||||
|
@ -265,18 +241,14 @@ class Command(BaseCommand):
|
||||||
debug_on = options.get("debug")
|
debug_on = options.get("debug")
|
||||||
|
|
||||||
# Get --LimitParse argument
|
# Get --LimitParse argument
|
||||||
debug_max_entries_to_parse = int(
|
debug_max_entries_to_parse = int(options.get("limitParse")) # set to 0 to parse all entries
|
||||||
options.get("limitParse")
|
|
||||||
) # set to 0 to parse all entries
|
|
||||||
|
|
||||||
# print message to terminal about which args are in use
|
# print message to terminal about which args are in use
|
||||||
self.print_debug_mode_statements(debug_on, debug_max_entries_to_parse)
|
self.print_debug_mode_statements(debug_on, debug_max_entries_to_parse)
|
||||||
|
|
||||||
# STEP 1:
|
# STEP 1:
|
||||||
# Create mapping of domain name -> status
|
# Create mapping of domain name -> status
|
||||||
domain_status_dictionary = self.get_domain_user_dict(
|
domain_status_dictionary = self.get_domain_user_dict(domain_statuses_filename, sep)
|
||||||
domain_statuses_filename, sep
|
|
||||||
)
|
|
||||||
|
|
||||||
# STEP 2:
|
# STEP 2:
|
||||||
# Create mapping of userId -> email
|
# Create mapping of userId -> email
|
||||||
|
@ -367,12 +339,7 @@ class Command(BaseCommand):
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
existing_domain_user_pair = next(
|
existing_domain_user_pair = next(
|
||||||
(
|
(x for x in to_create if x.username == new_entry_email and x.domain_name == new_entry_domain_name),
|
||||||
x
|
|
||||||
for x in to_create
|
|
||||||
if x.username == new_entry_email
|
|
||||||
and x.domain_name == new_entry_domain_name
|
|
||||||
),
|
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
if existing_domain is not None:
|
if existing_domain is not None:
|
||||||
|
@ -443,10 +410,7 @@ class Command(BaseCommand):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check Parse limit and exit loop if needed
|
# Check Parse limit and exit loop if needed
|
||||||
if (
|
if total_rows_parsed >= debug_max_entries_to_parse and debug_max_entries_to_parse != 0:
|
||||||
total_rows_parsed >= debug_max_entries_to_parse
|
|
||||||
and debug_max_entries_to_parse != 0
|
|
||||||
):
|
|
||||||
logger.info(
|
logger.info(
|
||||||
f"{TerminalColors.YELLOW}"
|
f"{TerminalColors.YELLOW}"
|
||||||
f"----PARSE LIMIT REACHED. HALTING PARSER.----"
|
f"----PARSE LIMIT REACHED. HALTING PARSER.----"
|
||||||
|
@ -467,7 +431,5 @@ class Command(BaseCommand):
|
||||||
|
|
||||||
# Print a summary of findings (duplicate entries,
|
# Print a summary of findings (duplicate entries,
|
||||||
# missing data..etc.)
|
# missing data..etc.)
|
||||||
self.print_summary_duplications(
|
self.print_summary_duplications(duplicate_domain_user_combos, duplicate_domains, users_without_email)
|
||||||
duplicate_domain_user_combos, duplicate_domains, users_without_email
|
|
||||||
)
|
|
||||||
self.print_summary_status_findings(domains_without_status, outlier_statuses)
|
self.print_summary_status_findings(domains_without_status, outlier_statuses)
|
||||||
|
|
|
@ -94,10 +94,7 @@ class Command(BaseCommand):
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--migrationDirectory",
|
"--migrationDirectory",
|
||||||
default="migrationdata",
|
default="migrationdata",
|
||||||
help=(
|
help=("The location of the files used for load_transition_domain migration script"),
|
||||||
"The location of the files used for"
|
|
||||||
"load_transition_domain migration script"
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--migrationFilenames",
|
"--migrationFilenames",
|
||||||
|
@ -116,17 +113,13 @@ class Command(BaseCommand):
|
||||||
information""",
|
information""",
|
||||||
)
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument("--sep", default="|", help="Delimiter character for the migration files")
|
||||||
"--sep", default="|", help="Delimiter character for the migration files"
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.add_argument("--debug", action=argparse.BooleanOptionalAction)
|
parser.add_argument("--debug", action=argparse.BooleanOptionalAction)
|
||||||
|
|
||||||
parser.add_argument("--prompt", action=argparse.BooleanOptionalAction)
|
parser.add_argument("--prompt", action=argparse.BooleanOptionalAction)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument("--limitParse", default=0, help="Sets max number of entries to load")
|
||||||
"--limitParse", default=0, help="Sets max number of entries to load"
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--resetTable",
|
"--resetTable",
|
||||||
|
@ -173,9 +166,7 @@ class Command(BaseCommand):
|
||||||
# Check Domain table
|
# Check Domain table
|
||||||
matching_domains = Domain.objects.filter(name=transition_domain_name)
|
matching_domains = Domain.objects.filter(name=transition_domain_name)
|
||||||
# Check Domain Information table
|
# Check Domain Information table
|
||||||
matching_domain_informations = DomainInformation.objects.filter(
|
matching_domain_informations = DomainInformation.objects.filter(domain__name=transition_domain_name)
|
||||||
domain__name=transition_domain_name
|
|
||||||
)
|
|
||||||
# Check Domain Invitation table
|
# Check Domain Invitation table
|
||||||
matching_domain_invitations = DomainInvitation.objects.filter(
|
matching_domain_invitations = DomainInvitation.objects.filter(
|
||||||
email=transition_domain_email.lower(),
|
email=transition_domain_email.lower(),
|
||||||
|
@ -215,15 +206,9 @@ class Command(BaseCommand):
|
||||||
total_missing_domain_invitations = len(missing_domain_invites)
|
total_missing_domain_invitations = len(missing_domain_invites)
|
||||||
|
|
||||||
missing_domains_as_string = "{}".format(", ".join(map(str, missing_domains)))
|
missing_domains_as_string = "{}".format(", ".join(map(str, missing_domains)))
|
||||||
duplicate_domains_as_string = "{}".format(
|
duplicate_domains_as_string = "{}".format(", ".join(map(str, duplicate_domains)))
|
||||||
", ".join(map(str, duplicate_domains))
|
missing_domain_informations_as_string = "{}".format(", ".join(map(str, missing_domain_informations)))
|
||||||
)
|
missing_domain_invites_as_string = "{}".format(", ".join(map(str, missing_domain_invites)))
|
||||||
missing_domain_informations_as_string = "{}".format(
|
|
||||||
", ".join(map(str, missing_domain_informations))
|
|
||||||
)
|
|
||||||
missing_domain_invites_as_string = "{}".format(
|
|
||||||
", ".join(map(str, missing_domain_invites))
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
f"""{TerminalColors.OKGREEN}
|
f"""{TerminalColors.OKGREEN}
|
||||||
|
|
|
@ -97,10 +97,7 @@ class Command(BaseCommand):
|
||||||
# domains_with_errors
|
# domains_with_errors
|
||||||
try:
|
try:
|
||||||
# if prior username does not match current username
|
# if prior username does not match current username
|
||||||
if (
|
if not email_context["email"] or email_context["email"] != transition_domain.username:
|
||||||
not email_context["email"]
|
|
||||||
or email_context["email"] != transition_domain.username
|
|
||||||
):
|
|
||||||
# if not first in list of transition_domains
|
# if not first in list of transition_domains
|
||||||
if email_context["email"]:
|
if email_context["email"]:
|
||||||
# append the email context to the emails_to_send array
|
# append the email context to the emails_to_send array
|
||||||
|
@ -110,12 +107,8 @@ class Command(BaseCommand):
|
||||||
email_context["domains"].append(transition_domain.domain_name)
|
email_context["domains"].append(transition_domain.domain_name)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
# error condition if domain not in database
|
# error condition if domain not in database
|
||||||
self.domains_with_errors.append(
|
self.domains_with_errors.append(copy.deepcopy(transition_domain.domain_name))
|
||||||
copy.deepcopy(transition_domain.domain_name)
|
logger.error(f"error retrieving domain {transition_domain.domain_name}: {err}")
|
||||||
)
|
|
||||||
logger.error(
|
|
||||||
f"error retrieving domain {transition_domain.domain_name}: {err}"
|
|
||||||
)
|
|
||||||
# if there are at least one more transition domains than errors,
|
# if there are at least one more transition domains than errors,
|
||||||
# then append one more item
|
# then append one more item
|
||||||
if len(self.transition_domains) > len(self.domains_with_errors):
|
if len(self.transition_domains) > len(self.domains_with_errors):
|
||||||
|
|
|
@ -33,9 +33,7 @@ class Command(BaseCommand):
|
||||||
help="Sets max number of entries to load, set to 0 to load all entries",
|
help="Sets max number of entries to load, set to 0 to load all entries",
|
||||||
)
|
)
|
||||||
|
|
||||||
def print_debug_mode_statements(
|
def print_debug_mode_statements(self, debug_on: bool, debug_max_entries_to_parse: int):
|
||||||
self, debug_on: bool, debug_max_entries_to_parse: int
|
|
||||||
):
|
|
||||||
"""Prints additional terminal statements to indicate if --debug
|
"""Prints additional terminal statements to indicate if --debug
|
||||||
or --limitParse are in use"""
|
or --limitParse are in use"""
|
||||||
TerminalHelper.print_conditional(
|
TerminalHelper.print_conditional(
|
||||||
|
@ -57,9 +55,7 @@ class Command(BaseCommand):
|
||||||
""",
|
""",
|
||||||
)
|
)
|
||||||
|
|
||||||
def update_domain_status(
|
def update_domain_status(self, transition_domain: TransitionDomain, target_domain: Domain, debug_on: bool) -> bool:
|
||||||
self, transition_domain: TransitionDomain, target_domain: Domain, debug_on: bool
|
|
||||||
) -> bool:
|
|
||||||
"""Given a transition domain that matches an existing domain,
|
"""Given a transition domain that matches an existing domain,
|
||||||
updates the existing domain object with that status of
|
updates the existing domain object with that status of
|
||||||
the transition domain.
|
the transition domain.
|
||||||
|
@ -153,9 +149,7 @@ class Command(BaseCommand):
|
||||||
""",
|
""",
|
||||||
)
|
)
|
||||||
|
|
||||||
def try_add_domain_invitation(
|
def try_add_domain_invitation(self, domain_email: str, associated_domain: Domain) -> DomainInvitation | None:
|
||||||
self, domain_email: str, associated_domain: Domain
|
|
||||||
) -> DomainInvitation | None:
|
|
||||||
"""If no domain invitation exists for the given domain and
|
"""If no domain invitation exists for the given domain and
|
||||||
e-mail, create and return a new domain invitation object.
|
e-mail, create and return a new domain invitation object.
|
||||||
If one already exists, or if the email is invalid, return NONE"""
|
If one already exists, or if the email is invalid, return NONE"""
|
||||||
|
@ -187,9 +181,7 @@ class Command(BaseCommand):
|
||||||
).exists()
|
).exists()
|
||||||
if not domain_email_already_in_domain_invites:
|
if not domain_email_already_in_domain_invites:
|
||||||
# Create new domain invitation
|
# Create new domain invitation
|
||||||
new_domain_invitation = DomainInvitation(
|
new_domain_invitation = DomainInvitation(email=domain_email.lower(), domain=associated_domain)
|
||||||
email=domain_email.lower(), domain=associated_domain
|
|
||||||
)
|
|
||||||
return new_domain_invitation
|
return new_domain_invitation
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -203,9 +195,7 @@ class Command(BaseCommand):
|
||||||
|
|
||||||
# grab command line arguments and store locally...
|
# grab command line arguments and store locally...
|
||||||
debug_on = options.get("debug")
|
debug_on = options.get("debug")
|
||||||
debug_max_entries_to_parse = int(
|
debug_max_entries_to_parse = int(options.get("limitParse")) # set to 0 to parse all entries
|
||||||
options.get("limitParse")
|
|
||||||
) # set to 0 to parse all entries
|
|
||||||
|
|
||||||
self.print_debug_mode_statements(debug_on, debug_max_entries_to_parse)
|
self.print_debug_mode_statements(debug_on, debug_max_entries_to_parse)
|
||||||
|
|
||||||
|
@ -258,18 +248,14 @@ class Command(BaseCommand):
|
||||||
|
|
||||||
# for existing entry, update the status to
|
# for existing entry, update the status to
|
||||||
# the transition domain status
|
# the transition domain status
|
||||||
update_made = self.update_domain_status(
|
update_made = self.update_domain_status(transition_domain, domain_to_update, debug_on)
|
||||||
transition_domain, domain_to_update, debug_on
|
|
||||||
)
|
|
||||||
if update_made:
|
if update_made:
|
||||||
# keep track of updated domains for data analysis purposes
|
# keep track of updated domains for data analysis purposes
|
||||||
updated_domain_entries.append(transition_domain.domain_name)
|
updated_domain_entries.append(transition_domain.domain_name)
|
||||||
|
|
||||||
# check if we need to add a domain invitation
|
# check if we need to add a domain invitation
|
||||||
# (eg. for a new user)
|
# (eg. for a new user)
|
||||||
new_domain_invitation = self.try_add_domain_invitation(
|
new_domain_invitation = self.try_add_domain_invitation(transition_domain_email, domain_to_update)
|
||||||
transition_domain_email, domain_to_update
|
|
||||||
)
|
|
||||||
|
|
||||||
except Domain.MultipleObjectsReturned:
|
except Domain.MultipleObjectsReturned:
|
||||||
# This exception was thrown once before during testing.
|
# This exception was thrown once before during testing.
|
||||||
|
@ -338,18 +324,14 @@ class Command(BaseCommand):
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# no matching entry, make one
|
# no matching entry, make one
|
||||||
new_domain = Domain(
|
new_domain = Domain(name=transition_domain_name, state=transition_domain_status)
|
||||||
name=transition_domain_name, state=transition_domain_status
|
|
||||||
)
|
|
||||||
domains_to_create.append(new_domain)
|
domains_to_create.append(new_domain)
|
||||||
# DEBUG:
|
# DEBUG:
|
||||||
TerminalHelper.print_conditional(
|
TerminalHelper.print_conditional(
|
||||||
debug_on,
|
debug_on,
|
||||||
f"{TerminalColors.OKCYAN} Adding domain: {new_domain} {TerminalColors.ENDC}", # noqa
|
f"{TerminalColors.OKCYAN} Adding domain: {new_domain} {TerminalColors.ENDC}", # noqa
|
||||||
)
|
)
|
||||||
new_domain_invitation = self.try_add_domain_invitation(
|
new_domain_invitation = self.try_add_domain_invitation(transition_domain_email, new_domain)
|
||||||
transition_domain_email, new_domain
|
|
||||||
)
|
|
||||||
|
|
||||||
if new_domain_invitation is None:
|
if new_domain_invitation is None:
|
||||||
logger.info(
|
logger.info(
|
||||||
|
@ -365,10 +347,7 @@ class Command(BaseCommand):
|
||||||
domain_invitations_to_create.append(new_domain_invitation)
|
domain_invitations_to_create.append(new_domain_invitation)
|
||||||
|
|
||||||
# Check parse limit and exit loop if parse limit has been reached
|
# Check parse limit and exit loop if parse limit has been reached
|
||||||
if (
|
if debug_max_entries_to_parse > 0 and total_rows_parsed >= debug_max_entries_to_parse:
|
||||||
debug_max_entries_to_parse > 0
|
|
||||||
and total_rows_parsed >= debug_max_entries_to_parse
|
|
||||||
):
|
|
||||||
logger.info(
|
logger.info(
|
||||||
f"""{TerminalColors.YELLOW}
|
f"""{TerminalColors.YELLOW}
|
||||||
----PARSE LIMIT REACHED. HALTING PARSER.----
|
----PARSE LIMIT REACHED. HALTING PARSER.----
|
||||||
|
|
|
@ -64,9 +64,7 @@ class TerminalHelper:
|
||||||
logger.info(print_statement)
|
logger.info(print_statement)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def prompt_for_execution(
|
def prompt_for_execution(system_exit_on_terminate: bool, info_to_inspect: str, prompt_title: str) -> bool:
|
||||||
system_exit_on_terminate: bool, info_to_inspect: str, prompt_title: str
|
|
||||||
) -> bool:
|
|
||||||
"""Create to reduce code complexity.
|
"""Create to reduce code complexity.
|
||||||
Prompts the user to inspect the given string
|
Prompts the user to inspect the given string
|
||||||
and asks if they wish to proceed.
|
and asks if they wish to proceed.
|
||||||
|
|
|
@ -32,9 +32,7 @@ class Migration(migrations.Migration):
|
||||||
("password", models.CharField(max_length=128, verbose_name="password")),
|
("password", models.CharField(max_length=128, verbose_name="password")),
|
||||||
(
|
(
|
||||||
"last_login",
|
"last_login",
|
||||||
models.DateTimeField(
|
models.DateTimeField(blank=True, null=True, verbose_name="last login"),
|
||||||
blank=True, null=True, verbose_name="last login"
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"is_superuser",
|
"is_superuser",
|
||||||
|
@ -47,35 +45,25 @@ class Migration(migrations.Migration):
|
||||||
(
|
(
|
||||||
"username",
|
"username",
|
||||||
models.CharField(
|
models.CharField(
|
||||||
error_messages={
|
error_messages={"unique": "A user with that username already exists."},
|
||||||
"unique": "A user with that username already exists."
|
|
||||||
},
|
|
||||||
help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
|
help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
|
||||||
max_length=150,
|
max_length=150,
|
||||||
unique=True,
|
unique=True,
|
||||||
validators=[
|
validators=[django.contrib.auth.validators.UnicodeUsernameValidator()],
|
||||||
django.contrib.auth.validators.UnicodeUsernameValidator()
|
|
||||||
],
|
|
||||||
verbose_name="username",
|
verbose_name="username",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"first_name",
|
"first_name",
|
||||||
models.CharField(
|
models.CharField(blank=True, max_length=150, verbose_name="first name"),
|
||||||
blank=True, max_length=150, verbose_name="first name"
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"last_name",
|
"last_name",
|
||||||
models.CharField(
|
models.CharField(blank=True, max_length=150, verbose_name="last name"),
|
||||||
blank=True, max_length=150, verbose_name="last name"
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"email",
|
"email",
|
||||||
models.EmailField(
|
models.EmailField(blank=True, max_length=254, verbose_name="email address"),
|
||||||
blank=True, max_length=254, verbose_name="email address"
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"is_staff",
|
"is_staff",
|
||||||
|
@ -95,9 +83,7 @@ class Migration(migrations.Migration):
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"date_joined",
|
"date_joined",
|
||||||
models.DateTimeField(
|
models.DateTimeField(default=django.utils.timezone.now, verbose_name="date joined"),
|
||||||
default=django.utils.timezone.now, verbose_name="date joined"
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"groups",
|
"groups",
|
||||||
|
@ -145,9 +131,7 @@ class Migration(migrations.Migration):
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"first_name",
|
"first_name",
|
||||||
models.TextField(
|
models.TextField(blank=True, db_index=True, help_text="First name", null=True),
|
||||||
blank=True, db_index=True, help_text="First name", null=True
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"middle_name",
|
"middle_name",
|
||||||
|
@ -155,22 +139,16 @@ class Migration(migrations.Migration):
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"last_name",
|
"last_name",
|
||||||
models.TextField(
|
models.TextField(blank=True, db_index=True, help_text="Last name", null=True),
|
||||||
blank=True, db_index=True, help_text="Last name", null=True
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
("title", models.TextField(blank=True, help_text="Title", null=True)),
|
("title", models.TextField(blank=True, help_text="Title", null=True)),
|
||||||
(
|
(
|
||||||
"email",
|
"email",
|
||||||
models.TextField(
|
models.TextField(blank=True, db_index=True, help_text="Email", null=True),
|
||||||
blank=True, db_index=True, help_text="Email", null=True
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"phone",
|
"phone",
|
||||||
models.TextField(
|
models.TextField(blank=True, db_index=True, help_text="Phone", null=True),
|
||||||
blank=True, db_index=True, help_text="Phone", null=True
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -280,21 +258,15 @@ class Migration(migrations.Migration):
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"unit_type",
|
"unit_type",
|
||||||
models.CharField(
|
models.CharField(blank=True, help_text="Unit type", max_length=15, null=True),
|
||||||
blank=True, help_text="Unit type", max_length=15, null=True
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"unit_number",
|
"unit_number",
|
||||||
models.CharField(
|
models.CharField(blank=True, help_text="Unit number", max_length=255, null=True),
|
||||||
blank=True, help_text="Unit number", max_length=255, null=True
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"state_territory",
|
"state_territory",
|
||||||
models.CharField(
|
models.CharField(blank=True, help_text="State/Territory", max_length=2, null=True),
|
||||||
blank=True, help_text="State/Territory", max_length=2, null=True
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"zip_code",
|
"zip_code",
|
||||||
|
@ -308,9 +280,7 @@ class Migration(migrations.Migration):
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"purpose",
|
"purpose",
|
||||||
models.TextField(
|
models.TextField(blank=True, help_text="Purpose of the domain", null=True),
|
||||||
blank=True, help_text="Purpose of the domain", null=True
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"security_email",
|
"security_email",
|
||||||
|
@ -323,9 +293,7 @@ class Migration(migrations.Migration):
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"anything_else",
|
"anything_else",
|
||||||
models.TextField(
|
models.TextField(blank=True, help_text="Anything else we should know?", null=True),
|
||||||
blank=True, help_text="Anything else we should know?", null=True
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"acknowledged_policy",
|
"acknowledged_policy",
|
||||||
|
@ -337,9 +305,7 @@ class Migration(migrations.Migration):
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"alternative_domains",
|
"alternative_domains",
|
||||||
models.ManyToManyField(
|
models.ManyToManyField(blank=True, related_name="alternatives+", to="registrar.website"),
|
||||||
blank=True, related_name="alternatives+", to="registrar.website"
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"authorizing_official",
|
"authorizing_official",
|
||||||
|
@ -361,9 +327,7 @@ class Migration(migrations.Migration):
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"current_websites",
|
"current_websites",
|
||||||
models.ManyToManyField(
|
models.ManyToManyField(blank=True, related_name="current+", to="registrar.website"),
|
||||||
blank=True, related_name="current+", to="registrar.website"
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"investigator",
|
"investigator",
|
||||||
|
|
|
@ -48,9 +48,7 @@ class Migration(migrations.Migration):
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name="domainapplication",
|
model_name="domainapplication",
|
||||||
name="address_line2",
|
name="address_line2",
|
||||||
field=models.CharField(
|
field=models.CharField(blank=True, help_text="Address line 2", max_length=15, null=True),
|
||||||
blank=True, help_text="Address line 2", max_length=15, null=True
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name="domainapplication",
|
model_name="domainapplication",
|
||||||
|
|
|
@ -22,8 +22,6 @@ class Migration(migrations.Migration):
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name="domainapplication",
|
model_name="domainapplication",
|
||||||
name="federal_agency",
|
name="federal_agency",
|
||||||
field=models.TextField(
|
field=models.TextField(blank=True, help_text="Top level federal agency", null=True),
|
||||||
blank=True, help_text="Top level federal agency", null=True
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -21,9 +21,7 @@ class Migration(migrations.Migration):
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name="domainapplication",
|
model_name="domainapplication",
|
||||||
name="type_of_work",
|
name="type_of_work",
|
||||||
field=models.TextField(
|
field=models.TextField(blank=True, help_text="Type of work of the organization", null=True),
|
||||||
blank=True, help_text="Type of work of the organization", null=True
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name="domainapplication",
|
model_name="domainapplication",
|
||||||
|
@ -33,9 +31,7 @@ class Migration(migrations.Migration):
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name="domainapplication",
|
model_name="domainapplication",
|
||||||
name="address_line2",
|
name="address_line2",
|
||||||
field=models.CharField(
|
field=models.CharField(blank=True, help_text="Street address line 2", max_length=15, null=True),
|
||||||
blank=True, help_text="Street address line 2", max_length=15, null=True
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name="domainapplication",
|
model_name="domainapplication",
|
||||||
|
@ -95,9 +91,7 @@ class Migration(migrations.Migration):
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name="domainapplication",
|
model_name="domainapplication",
|
||||||
name="purpose",
|
name="purpose",
|
||||||
field=models.TextField(
|
field=models.TextField(blank=True, help_text="Purpose of your domain", null=True),
|
||||||
blank=True, help_text="Purpose of your domain", null=True
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name="domainapplication",
|
model_name="domainapplication",
|
||||||
|
@ -112,9 +106,7 @@ class Migration(migrations.Migration):
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name="domainapplication",
|
model_name="domainapplication",
|
||||||
name="urbanization",
|
name="urbanization",
|
||||||
field=models.TextField(
|
field=models.TextField(blank=True, help_text="Urbanization (Puerto Rico only)", null=True),
|
||||||
blank=True, help_text="Urbanization (Puerto Rico only)", null=True
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name="domainapplication",
|
model_name="domainapplication",
|
||||||
|
|
|
@ -21,9 +21,7 @@ class Migration(migrations.Migration):
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name="contact",
|
model_name="contact",
|
||||||
name="created_at",
|
name="created_at",
|
||||||
field=models.DateTimeField(
|
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
|
||||||
auto_now_add=True, default=django.utils.timezone.now
|
|
||||||
),
|
|
||||||
preserve_default=False,
|
preserve_default=False,
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
|
@ -34,9 +32,7 @@ class Migration(migrations.Migration):
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name="website",
|
model_name="website",
|
||||||
name="created_at",
|
name="created_at",
|
||||||
field=models.DateTimeField(
|
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
|
||||||
auto_now_add=True, default=django.utils.timezone.now
|
|
||||||
),
|
|
||||||
preserve_default=False,
|
preserve_default=False,
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
|
|
|
@ -12,16 +12,12 @@ class Migration(migrations.Migration):
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name="domainapplication",
|
model_name="domainapplication",
|
||||||
name="federally_recognized_tribe",
|
name="federally_recognized_tribe",
|
||||||
field=models.BooleanField(
|
field=models.BooleanField(help_text="Is the tribe federally recognized", null=True),
|
||||||
help_text="Is the tribe federally recognized", null=True
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name="domainapplication",
|
model_name="domainapplication",
|
||||||
name="state_recognized_tribe",
|
name="state_recognized_tribe",
|
||||||
field=models.BooleanField(
|
field=models.BooleanField(help_text="Is the tribe recognized by a state", null=True),
|
||||||
help_text="Is the tribe recognized by a state", null=True
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name="domainapplication",
|
model_name="domainapplication",
|
||||||
|
|
|
@ -59,8 +59,6 @@ class Migration(migrations.Migration):
|
||||||
),
|
),
|
||||||
migrations.AddConstraint(
|
migrations.AddConstraint(
|
||||||
model_name="userdomainrole",
|
model_name="userdomainrole",
|
||||||
constraint=models.UniqueConstraint(
|
constraint=models.UniqueConstraint(fields=("user", "domain"), name="unique_user_domain_role"),
|
||||||
fields=("user", "domain"), name="unique_user_domain_role"
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -64,15 +64,11 @@ class Migration(migrations.Migration):
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"federally_recognized_tribe",
|
"federally_recognized_tribe",
|
||||||
models.BooleanField(
|
models.BooleanField(help_text="Is the tribe federally recognized", null=True),
|
||||||
help_text="Is the tribe federally recognized", null=True
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"state_recognized_tribe",
|
"state_recognized_tribe",
|
||||||
models.BooleanField(
|
models.BooleanField(help_text="Is the tribe recognized by a state", null=True),
|
||||||
help_text="Is the tribe recognized by a state", null=True
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"tribe_name",
|
"tribe_name",
|
||||||
|
@ -172,9 +168,7 @@ class Migration(migrations.Migration):
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"purpose",
|
"purpose",
|
||||||
models.TextField(
|
models.TextField(blank=True, help_text="Purpose of your domain", null=True),
|
||||||
blank=True, help_text="Purpose of your domain", null=True
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"no_other_contacts_rationale",
|
"no_other_contacts_rationale",
|
||||||
|
@ -186,9 +180,7 @@ class Migration(migrations.Migration):
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"anything_else",
|
"anything_else",
|
||||||
models.TextField(
|
models.TextField(blank=True, help_text="Anything else we should know?", null=True),
|
||||||
blank=True, help_text="Anything else we should know?", null=True
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"is_policy_acknowledged",
|
"is_policy_acknowledged",
|
||||||
|
|
|
@ -76,9 +76,7 @@ class Migration(migrations.Migration):
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name="publiccontact",
|
model_name="publiccontact",
|
||||||
name="org",
|
name="org",
|
||||||
field=models.TextField(
|
field=models.TextField(help_text="Contact's organization (null ok)", null=True),
|
||||||
help_text="Contact's organization (null ok)", null=True
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name="publiccontact",
|
model_name="publiccontact",
|
||||||
|
@ -88,9 +86,7 @@ class Migration(migrations.Migration):
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name="publiccontact",
|
model_name="publiccontact",
|
||||||
name="pw",
|
name="pw",
|
||||||
field=models.TextField(
|
field=models.TextField(help_text="Contact's authorization code. 16 characters minimum."),
|
||||||
help_text="Contact's authorization code. 16 characters minimum."
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name="publiccontact",
|
model_name="publiccontact",
|
||||||
|
@ -115,8 +111,6 @@ class Migration(migrations.Migration):
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name="publiccontact",
|
model_name="publiccontact",
|
||||||
name="voice",
|
name="voice",
|
||||||
field=models.TextField(
|
field=models.TextField(help_text="Contact's phone number. Must be in ITU.E164.2005 format"),
|
||||||
help_text="Contact's phone number. Must be in ITU.E164.2005 format"
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -12,8 +12,6 @@ class Migration(migrations.Migration):
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name="contact",
|
model_name="contact",
|
||||||
name="email",
|
name="email",
|
||||||
field=models.EmailField(
|
field=models.EmailField(blank=True, db_index=True, help_text="Email", max_length=254, null=True),
|
||||||
blank=True, db_index=True, help_text="Email", max_length=254, null=True
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -12,15 +12,11 @@ class Migration(migrations.Migration):
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name="domainapplication",
|
model_name="domainapplication",
|
||||||
name="address_line2",
|
name="address_line2",
|
||||||
field=models.TextField(
|
field=models.TextField(blank=True, help_text="Street address line 2", null=True),
|
||||||
blank=True, help_text="Street address line 2", null=True
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name="domaininformation",
|
model_name="domaininformation",
|
||||||
name="address_line2",
|
name="address_line2",
|
||||||
field=models.TextField(
|
field=models.TextField(blank=True, help_text="Street address line 2", null=True),
|
||||||
blank=True, help_text="Street address line 2", null=True
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -95,16 +95,12 @@ class Migration(migrations.Migration):
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name="domainapplication",
|
model_name="domainapplication",
|
||||||
name="about_your_organization",
|
name="about_your_organization",
|
||||||
field=models.TextField(
|
field=models.TextField(blank=True, help_text="Information about your organization", null=True),
|
||||||
blank=True, help_text="Information about your organization", null=True
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name="domaininformation",
|
model_name="domaininformation",
|
||||||
name="about_your_organization",
|
name="about_your_organization",
|
||||||
field=models.TextField(
|
field=models.TextField(blank=True, help_text="Information about your organization", null=True),
|
||||||
blank=True, help_text="Information about your organization", null=True
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name="domainapplication",
|
model_name="domainapplication",
|
||||||
|
|
37
src/registrar/migrations/0044_create_groups_v04.py
Normal file
37
src/registrar/migrations/0044_create_groups_v04.py
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
# This migration creates the create_full_access_group and create_cisa_analyst_group groups
|
||||||
|
# It is dependent on 0035 (which populates ContentType and Permissions)
|
||||||
|
# If permissions on the groups need changing, edit CISA_ANALYST_GROUP_PERMISSIONS
|
||||||
|
# in the user_group model then:
|
||||||
|
# [NOT RECOMMENDED]
|
||||||
|
# step 1: docker-compose exec app ./manage.py migrate --fake registrar 0035_contenttypes_permissions
|
||||||
|
# step 2: docker-compose exec app ./manage.py migrate registrar 0036_create_groups
|
||||||
|
# step 3: fake run the latest migration in the migrations list
|
||||||
|
# [RECOMMENDED]
|
||||||
|
# Alternatively:
|
||||||
|
# step 1: duplicate the migration that loads data
|
||||||
|
# step 2: docker-compose exec app ./manage.py migrate
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
from registrar.models import UserGroup
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
|
# For linting: RunPython expects a function reference,
|
||||||
|
# so let's give it one
|
||||||
|
def create_groups(apps, schema_editor) -> Any:
|
||||||
|
UserGroup.create_cisa_analyst_group(apps, schema_editor)
|
||||||
|
UserGroup.create_full_access_group(apps, schema_editor)
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("registrar", "0043_domain_expiration_date"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(
|
||||||
|
create_groups,
|
||||||
|
reverse_code=migrations.RunPython.noop,
|
||||||
|
atomic=True,
|
||||||
|
),
|
||||||
|
]
|
|
@ -262,10 +262,7 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
doesn't add the created host to the domain
|
doesn't add the created host to the domain
|
||||||
returns ErrorCode (int)"""
|
returns ErrorCode (int)"""
|
||||||
if addrs is not None and addrs != []:
|
if addrs is not None and addrs != []:
|
||||||
addresses = [
|
addresses = [epp.Ip(addr=addr, ip="v6" if self.is_ipv6(addr) else None) for addr in addrs]
|
||||||
epp.Ip(addr=addr, ip="v6" if self.is_ipv6(addr) else None)
|
|
||||||
for addr in addrs
|
|
||||||
]
|
|
||||||
request = commands.CreateHost(name=host, addrs=addresses)
|
request = commands.CreateHost(name=host, addrs=addresses)
|
||||||
else:
|
else:
|
||||||
request = commands.CreateHost(name=host)
|
request = commands.CreateHost(name=host)
|
||||||
|
@ -358,15 +355,11 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
raise NameserverError(code=nsErrorCodes.MISSING_IP, nameserver=nameserver)
|
raise NameserverError(code=nsErrorCodes.MISSING_IP, nameserver=nameserver)
|
||||||
|
|
||||||
elif not cls.isSubdomain(name, nameserver) and (ip is not None and ip != []):
|
elif not cls.isSubdomain(name, nameserver) and (ip is not None and ip != []):
|
||||||
raise NameserverError(
|
raise NameserverError(code=nsErrorCodes.GLUE_RECORD_NOT_ALLOWED, nameserver=nameserver, ip=ip)
|
||||||
code=nsErrorCodes.GLUE_RECORD_NOT_ALLOWED, nameserver=nameserver, ip=ip
|
|
||||||
)
|
|
||||||
elif ip is not None and ip != []:
|
elif ip is not None and ip != []:
|
||||||
for addr in ip:
|
for addr in ip:
|
||||||
if not cls._valid_ip_addr(addr):
|
if not cls._valid_ip_addr(addr):
|
||||||
raise NameserverError(
|
raise NameserverError(code=nsErrorCodes.INVALID_IP, nameserver=nameserver[:40], ip=ip)
|
||||||
code=nsErrorCodes.INVALID_IP, nameserver=nameserver[:40], ip=ip
|
|
||||||
)
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -382,9 +375,7 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def getNameserverChanges(
|
def getNameserverChanges(self, hosts: list[tuple[str, list]]) -> tuple[list, list, dict, dict]:
|
||||||
self, hosts: list[tuple[str, list]]
|
|
||||||
) -> tuple[list, list, dict, dict]:
|
|
||||||
"""
|
"""
|
||||||
calls self.nameserver, it should pull from cache but may result
|
calls self.nameserver, it should pull from cache but may result
|
||||||
in an epp call
|
in an epp call
|
||||||
|
@ -420,40 +411,27 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
else:
|
else:
|
||||||
# TODO - host is being updated when previous was None+new is empty list
|
# TODO - host is being updated when previous was None+new is empty list
|
||||||
# add check here
|
# add check here
|
||||||
if newHostDict[prevHost] is not None and set(
|
if newHostDict[prevHost] is not None and set(newHostDict[prevHost]) != set(addrs):
|
||||||
newHostDict[prevHost]
|
self.__class__.checkHostIPCombo(name=self.name, nameserver=prevHost, ip=newHostDict[prevHost])
|
||||||
) != set(addrs):
|
|
||||||
self.__class__.checkHostIPCombo(
|
|
||||||
name=self.name, nameserver=prevHost, ip=newHostDict[prevHost]
|
|
||||||
)
|
|
||||||
updated_values.append((prevHost, newHostDict[prevHost]))
|
updated_values.append((prevHost, newHostDict[prevHost]))
|
||||||
|
|
||||||
new_values = {
|
new_values = {
|
||||||
key: newHostDict.get(key)
|
key: newHostDict.get(key) for key in newHostDict if key not in previousHostDict and key.strip() != ""
|
||||||
for key in newHostDict
|
|
||||||
if key not in previousHostDict and key.strip() != ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for nameserver, ip in new_values.items():
|
for nameserver, ip in new_values.items():
|
||||||
self.__class__.checkHostIPCombo(
|
self.__class__.checkHostIPCombo(name=self.name, nameserver=nameserver, ip=ip)
|
||||||
name=self.name, nameserver=nameserver, ip=ip
|
|
||||||
)
|
|
||||||
|
|
||||||
return (deleted_values, updated_values, new_values, previousHostDict)
|
return (deleted_values, updated_values, new_values, previousHostDict)
|
||||||
|
|
||||||
def _update_host_values(self, updated_values, oldNameservers):
|
def _update_host_values(self, updated_values, oldNameservers):
|
||||||
for hostTuple in updated_values:
|
for hostTuple in updated_values:
|
||||||
updated_response_code = self._update_host(
|
updated_response_code = self._update_host(hostTuple[0], hostTuple[1], oldNameservers.get(hostTuple[0]))
|
||||||
hostTuple[0], hostTuple[1], oldNameservers.get(hostTuple[0])
|
|
||||||
)
|
|
||||||
if updated_response_code not in [
|
if updated_response_code not in [
|
||||||
ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY,
|
ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY,
|
||||||
ErrorCode.OBJECT_EXISTS,
|
ErrorCode.OBJECT_EXISTS,
|
||||||
]:
|
]:
|
||||||
logger.warning(
|
logger.warning("Could not update host %s. Error code was: %s " % (hostTuple[0], updated_response_code))
|
||||||
"Could not update host %s. Error code was: %s "
|
|
||||||
% (hostTuple[0], updated_response_code)
|
|
||||||
)
|
|
||||||
|
|
||||||
def createNewHostList(self, new_values: dict):
|
def createNewHostList(self, new_values: dict):
|
||||||
"""convert the dictionary of new values to a list of HostObjSet
|
"""convert the dictionary of new values to a list of HostObjSet
|
||||||
|
@ -469,13 +447,8 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
|
|
||||||
hostStringList = []
|
hostStringList = []
|
||||||
for key, value in new_values.items():
|
for key, value in new_values.items():
|
||||||
createdCode = self._create_host(
|
createdCode = self._create_host(host=key, addrs=value) # creates in registry
|
||||||
host=key, addrs=value
|
if createdCode == ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY or createdCode == ErrorCode.OBJECT_EXISTS:
|
||||||
) # creates in registry
|
|
||||||
if (
|
|
||||||
createdCode == ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY
|
|
||||||
or createdCode == ErrorCode.OBJECT_EXISTS
|
|
||||||
):
|
|
||||||
hostStringList.append(key)
|
hostStringList.append(key)
|
||||||
if hostStringList == []:
|
if hostStringList == []:
|
||||||
return [], 0
|
return [], 0
|
||||||
|
@ -522,9 +495,7 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
logger.info("Domain does not have dnssec data defined %s" % err)
|
logger.info("Domain does not have dnssec data defined %s" % err)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def getDnssecdataChanges(
|
def getDnssecdataChanges(self, _dnssecdata: Optional[extensions.DNSSECExtension]) -> tuple[dict, dict]:
|
||||||
self, _dnssecdata: Optional[extensions.DNSSECExtension]
|
|
||||||
) -> tuple[dict, dict]:
|
|
||||||
"""
|
"""
|
||||||
calls self.dnssecdata, it should pull from cache but may result
|
calls self.dnssecdata, it should pull from cache but may result
|
||||||
in an epp call
|
in an epp call
|
||||||
|
@ -550,20 +521,12 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
|
|
||||||
if oldDnssecdata and len(oldDnssecdata.dsData) > 0:
|
if oldDnssecdata and len(oldDnssecdata.dsData) > 0:
|
||||||
# if existing dsData not in new dsData, mark for removal
|
# if existing dsData not in new dsData, mark for removal
|
||||||
dsDataForRemoval = [
|
dsDataForRemoval = [dsData for dsData in oldDnssecdata.dsData if dsData not in _dnssecdata.dsData]
|
||||||
dsData
|
|
||||||
for dsData in oldDnssecdata.dsData
|
|
||||||
if dsData not in _dnssecdata.dsData
|
|
||||||
]
|
|
||||||
if len(dsDataForRemoval) > 0:
|
if len(dsDataForRemoval) > 0:
|
||||||
remDnssecdata["dsData"] = dsDataForRemoval
|
remDnssecdata["dsData"] = dsDataForRemoval
|
||||||
|
|
||||||
# if new dsData not in existing dsData, mark for add
|
# if new dsData not in existing dsData, mark for add
|
||||||
dsDataForAdd = [
|
dsDataForAdd = [dsData for dsData in _dnssecdata.dsData if dsData not in oldDnssecdata.dsData]
|
||||||
dsData
|
|
||||||
for dsData in _dnssecdata.dsData
|
|
||||||
if dsData not in oldDnssecdata.dsData
|
|
||||||
]
|
|
||||||
if len(dsDataForAdd) > 0:
|
if len(dsDataForAdd) > 0:
|
||||||
addDnssecdata["dsData"] = dsDataForAdd
|
addDnssecdata["dsData"] = dsDataForAdd
|
||||||
else:
|
else:
|
||||||
|
@ -598,9 +561,7 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
if "dsData" in _remDnssecdata and _remDnssecdata["dsData"] is not None:
|
if "dsData" in _remDnssecdata and _remDnssecdata["dsData"] is not None:
|
||||||
registry.send(remRequest, cleaned=True)
|
registry.send(remRequest, cleaned=True)
|
||||||
except RegistryError as e:
|
except RegistryError as e:
|
||||||
logger.error(
|
logger.error("Error updating DNSSEC, code was %s error was %s" % (e.code, e))
|
||||||
"Error updating DNSSEC, code was %s error was %s" % (e.code, e)
|
|
||||||
)
|
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
@nameservers.setter # type: ignore
|
@nameservers.setter # type: ignore
|
||||||
|
@ -630,14 +591,10 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
oldNameservers,
|
oldNameservers,
|
||||||
) = self.getNameserverChanges(hosts=hosts)
|
) = self.getNameserverChanges(hosts=hosts)
|
||||||
|
|
||||||
_ = self._update_host_values(
|
_ = self._update_host_values(updated_values, oldNameservers) # returns nothing, just need to be run and errors
|
||||||
updated_values, oldNameservers
|
|
||||||
) # returns nothing, just need to be run and errors
|
|
||||||
addToDomainList, addToDomainCount = self.createNewHostList(new_values)
|
addToDomainList, addToDomainCount = self.createNewHostList(new_values)
|
||||||
deleteHostList, deleteCount = self.createDeleteHostList(deleted_values)
|
deleteHostList, deleteCount = self.createDeleteHostList(deleted_values)
|
||||||
responseCode = self.addAndRemoveHostsFromDomain(
|
responseCode = self.addAndRemoveHostsFromDomain(hostsToAdd=addToDomainList, hostsToDelete=deleteHostList)
|
||||||
hostsToAdd=addToDomainList, hostsToDelete=deleteHostList
|
|
||||||
)
|
|
||||||
|
|
||||||
# if unable to update domain raise error and stop
|
# if unable to update domain raise error and stop
|
||||||
if responseCode != ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY:
|
if responseCode != ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY:
|
||||||
|
@ -651,19 +608,13 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
self.dns_needed()
|
self.dns_needed()
|
||||||
self.save()
|
self.save()
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
logger.info(
|
logger.info("nameserver setter checked for dns_needed state and it did not succeed. Warning: %s" % err)
|
||||||
"nameserver setter checked for dns_needed state "
|
|
||||||
"and it did not succeed. Warning: %s" % err
|
|
||||||
)
|
|
||||||
elif successTotalNameservers >= 2 and successTotalNameservers <= 13:
|
elif successTotalNameservers >= 2 and successTotalNameservers <= 13:
|
||||||
try:
|
try:
|
||||||
self.ready()
|
self.ready()
|
||||||
self.save()
|
self.save()
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
logger.info(
|
logger.info("nameserver setter checked for create state and it did not succeed. Warning: %s" % err)
|
||||||
"nameserver setter checked for create state "
|
|
||||||
"and it did not succeed. Warning: %s" % err
|
|
||||||
)
|
|
||||||
|
|
||||||
@Cache
|
@Cache
|
||||||
def statuses(self) -> list[str]:
|
def statuses(self) -> list[str]:
|
||||||
|
@ -698,9 +649,7 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
so follow on additions will update the current registrant"""
|
so follow on additions will update the current registrant"""
|
||||||
|
|
||||||
logger.info("making registrant contact")
|
logger.info("making registrant contact")
|
||||||
self._set_singleton_contact(
|
self._set_singleton_contact(contact=contact, expectedType=contact.ContactTypeChoices.REGISTRANT)
|
||||||
contact=contact, expectedType=contact.ContactTypeChoices.REGISTRANT
|
|
||||||
)
|
|
||||||
|
|
||||||
@Cache
|
@Cache
|
||||||
def administrative_contact(self) -> PublicContact | None:
|
def administrative_contact(self) -> PublicContact | None:
|
||||||
|
@ -712,9 +661,7 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
def administrative_contact(self, contact: PublicContact):
|
def administrative_contact(self, contact: PublicContact):
|
||||||
logger.info("making admin contact")
|
logger.info("making admin contact")
|
||||||
if contact.contact_type != contact.ContactTypeChoices.ADMINISTRATIVE:
|
if contact.contact_type != contact.ContactTypeChoices.ADMINISTRATIVE:
|
||||||
raise ValueError(
|
raise ValueError("Cannot set a registrant contact with a different contact type")
|
||||||
"Cannot set a registrant contact with a different contact type"
|
|
||||||
)
|
|
||||||
self._make_contact_in_registry(contact=contact)
|
self._make_contact_in_registry(contact=contact)
|
||||||
self._update_domain_with_contact(contact, rem=False)
|
self._update_domain_with_contact(contact, rem=False)
|
||||||
|
|
||||||
|
@ -736,20 +683,14 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
try:
|
try:
|
||||||
registry.send(updateContact, cleaned=True)
|
registry.send(updateContact, cleaned=True)
|
||||||
except RegistryError as e:
|
except RegistryError as e:
|
||||||
logger.error(
|
logger.error("Error updating contact, code was %s error was %s" % (e.code, e))
|
||||||
"Error updating contact, code was %s error was %s" % (e.code, e)
|
|
||||||
)
|
|
||||||
# TODO - ticket 433 human readable error handling here
|
# TODO - ticket 433 human readable error handling here
|
||||||
|
|
||||||
def _update_domain_with_contact(self, contact: PublicContact, rem=False):
|
def _update_domain_with_contact(self, contact: PublicContact, rem=False):
|
||||||
"""adds or removes a contact from a domain
|
"""adds or removes a contact from a domain
|
||||||
rem being true indicates the contact will be removed from registry"""
|
rem being true indicates the contact will be removed from registry"""
|
||||||
logger.info(
|
logger.info("_update_domain_with_contact() received type %s " % contact.contact_type)
|
||||||
"_update_domain_with_contact() received type %s " % contact.contact_type
|
domainContact = epp.DomainContact(contact=contact.registry_id, type=contact.contact_type)
|
||||||
)
|
|
||||||
domainContact = epp.DomainContact(
|
|
||||||
contact=contact.registry_id, type=contact.contact_type
|
|
||||||
)
|
|
||||||
|
|
||||||
updateDomain = commands.UpdateDomain(name=self.name, add=[domainContact])
|
updateDomain = commands.UpdateDomain(name=self.name, add=[domainContact])
|
||||||
if rem:
|
if rem:
|
||||||
|
@ -758,17 +699,12 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
try:
|
try:
|
||||||
registry.send(updateDomain, cleaned=True)
|
registry.send(updateDomain, cleaned=True)
|
||||||
except RegistryError as e:
|
except RegistryError as e:
|
||||||
logger.error(
|
logger.error("Error changing contact on a domain. Error code is %s error was %s" % (e.code, e))
|
||||||
"Error changing contact on a domain. Error code is %s error was %s"
|
|
||||||
% (e.code, e)
|
|
||||||
)
|
|
||||||
action = "add"
|
action = "add"
|
||||||
if rem:
|
if rem:
|
||||||
action = "remove"
|
action = "remove"
|
||||||
|
|
||||||
raise Exception(
|
raise Exception("Can't %s the contact of type %s" % (action, contact.contact_type))
|
||||||
"Can't %s the contact of type %s" % (action, contact.contact_type)
|
|
||||||
)
|
|
||||||
|
|
||||||
@Cache
|
@Cache
|
||||||
def security_contact(self) -> PublicContact | None:
|
def security_contact(self) -> PublicContact | None:
|
||||||
|
@ -778,16 +714,11 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
|
|
||||||
def _add_registrant_to_existing_domain(self, contact: PublicContact):
|
def _add_registrant_to_existing_domain(self, contact: PublicContact):
|
||||||
"""Used to change the registrant contact on an existing domain"""
|
"""Used to change the registrant contact on an existing domain"""
|
||||||
updateDomain = commands.UpdateDomain(
|
updateDomain = commands.UpdateDomain(name=self.name, registrant=contact.registry_id)
|
||||||
name=self.name, registrant=contact.registry_id
|
|
||||||
)
|
|
||||||
try:
|
try:
|
||||||
registry.send(updateDomain, cleaned=True)
|
registry.send(updateDomain, cleaned=True)
|
||||||
except RegistryError as e:
|
except RegistryError as e:
|
||||||
logger.error(
|
logger.error("Error changing to new registrant error code is %s, error is %s" % (e.code, e))
|
||||||
"Error changing to new registrant error code is %s, error is %s"
|
|
||||||
% (e.code, e)
|
|
||||||
)
|
|
||||||
# TODO-error handling better here?
|
# TODO-error handling better here?
|
||||||
|
|
||||||
def _set_singleton_contact(self, contact: PublicContact, expectedType: str): # noqa
|
def _set_singleton_contact(self, contact: PublicContact, expectedType: str): # noqa
|
||||||
|
@ -800,16 +731,10 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
Will throw error if contact type is not the same as expectType
|
Will throw error if contact type is not the same as expectType
|
||||||
Raises ValueError if expected type doesn't match the contact type"""
|
Raises ValueError if expected type doesn't match the contact type"""
|
||||||
if expectedType != contact.contact_type:
|
if expectedType != contact.contact_type:
|
||||||
raise ValueError(
|
raise ValueError("Cannot set a contact with a different contact type, expected type was %s" % expectedType)
|
||||||
"Cannot set a contact with a different contact type,"
|
|
||||||
" expected type was %s" % expectedType
|
|
||||||
)
|
|
||||||
|
|
||||||
isRegistrant = contact.contact_type == contact.ContactTypeChoices.REGISTRANT
|
isRegistrant = contact.contact_type == contact.ContactTypeChoices.REGISTRANT
|
||||||
isEmptySecurity = (
|
isEmptySecurity = contact.contact_type == contact.ContactTypeChoices.SECURITY and contact.email == ""
|
||||||
contact.contact_type == contact.ContactTypeChoices.SECURITY
|
|
||||||
and contact.email == ""
|
|
||||||
)
|
|
||||||
|
|
||||||
# get publicContact objects that have the matching
|
# get publicContact objects that have the matching
|
||||||
# domain and type but a different id
|
# domain and type but a different id
|
||||||
|
@ -827,10 +752,7 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
# contact is already added to the domain, but something may have changed on it
|
# contact is already added to the domain, but something may have changed on it
|
||||||
alreadyExistsInRegistry = errorCode == ErrorCode.OBJECT_EXISTS
|
alreadyExistsInRegistry = errorCode == ErrorCode.OBJECT_EXISTS
|
||||||
# if an error occured besides duplication, stop
|
# if an error occured besides duplication, stop
|
||||||
if (
|
if not alreadyExistsInRegistry and errorCode != ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY:
|
||||||
not alreadyExistsInRegistry
|
|
||||||
and errorCode != ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY
|
|
||||||
):
|
|
||||||
# TODO- ticket #433 look here for error handling
|
# TODO- ticket #433 look here for error handling
|
||||||
raise RegistryError(code=errorCode)
|
raise RegistryError(code=errorCode)
|
||||||
|
|
||||||
|
@ -839,9 +761,7 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
|
|
||||||
# if has conflicting contacts in our db remove them
|
# if has conflicting contacts in our db remove them
|
||||||
if hasOtherContact:
|
if hasOtherContact:
|
||||||
logger.info(
|
logger.info("_set_singleton_contact()-> updating domain, removing old contact")
|
||||||
"_set_singleton_contact()-> updating domain, removing old contact"
|
|
||||||
)
|
|
||||||
|
|
||||||
existing_contact = (
|
existing_contact = (
|
||||||
PublicContact.objects.exclude(registry_id=contact.registry_id)
|
PublicContact.objects.exclude(registry_id=contact.registry_id)
|
||||||
|
@ -859,9 +779,7 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
self._update_domain_with_contact(contact=existing_contact, rem=True)
|
self._update_domain_with_contact(contact=existing_contact, rem=True)
|
||||||
existing_contact.delete()
|
existing_contact.delete()
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
logger.error(
|
logger.error("Raising error after removing and adding a new contact")
|
||||||
"Raising error after removing and adding a new contact"
|
|
||||||
)
|
|
||||||
raise (err)
|
raise (err)
|
||||||
|
|
||||||
# update domain with contact or update the contact itself
|
# update domain with contact or update the contact itself
|
||||||
|
@ -870,9 +788,7 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
self._update_domain_with_contact(contact=contact, rem=False)
|
self._update_domain_with_contact(contact=contact, rem=False)
|
||||||
# if already exists just update
|
# if already exists just update
|
||||||
elif alreadyExistsInRegistry:
|
elif alreadyExistsInRegistry:
|
||||||
current_contact = PublicContact.objects.filter(
|
current_contact = PublicContact.objects.filter(registry_id=contact.registry_id).get()
|
||||||
registry_id=contact.registry_id
|
|
||||||
).get()
|
|
||||||
|
|
||||||
if current_contact.email != contact.email:
|
if current_contact.email != contact.email:
|
||||||
self._update_epp_contact(contact=contact)
|
self._update_epp_contact(contact=contact)
|
||||||
|
@ -880,9 +796,7 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
logger.info("removing security contact and setting default again")
|
logger.info("removing security contact and setting default again")
|
||||||
|
|
||||||
# get the current contact registry id for security
|
# get the current contact registry id for security
|
||||||
current_contact = PublicContact.objects.filter(
|
current_contact = PublicContact.objects.filter(registry_id=contact.registry_id).get()
|
||||||
registry_id=contact.registry_id
|
|
||||||
).get()
|
|
||||||
|
|
||||||
# don't let user delete the default without adding a new email
|
# don't let user delete the default without adding a new email
|
||||||
if current_contact.email != PublicContact.get_default_security().email:
|
if current_contact.email != PublicContact.get_default_security().email:
|
||||||
|
@ -900,9 +814,7 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
from domain information (not domain application)
|
from domain information (not domain application)
|
||||||
and should have the security email from DomainApplication"""
|
and should have the security email from DomainApplication"""
|
||||||
logger.info("making security contact in registry")
|
logger.info("making security contact in registry")
|
||||||
self._set_singleton_contact(
|
self._set_singleton_contact(contact, expectedType=contact.ContactTypeChoices.SECURITY)
|
||||||
contact, expectedType=contact.ContactTypeChoices.SECURITY
|
|
||||||
)
|
|
||||||
|
|
||||||
@Cache
|
@Cache
|
||||||
def technical_contact(self) -> PublicContact | None:
|
def technical_contact(self) -> PublicContact | None:
|
||||||
|
@ -913,9 +825,7 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
@technical_contact.setter # type: ignore
|
@technical_contact.setter # type: ignore
|
||||||
def technical_contact(self, contact: PublicContact):
|
def technical_contact(self, contact: PublicContact):
|
||||||
logger.info("making technical contact")
|
logger.info("making technical contact")
|
||||||
self._set_singleton_contact(
|
self._set_singleton_contact(contact, expectedType=contact.ContactTypeChoices.TECHNICAL)
|
||||||
contact, expectedType=contact.ContactTypeChoices.TECHNICAL
|
|
||||||
)
|
|
||||||
|
|
||||||
def is_active(self) -> bool:
|
def is_active(self) -> bool:
|
||||||
"""Currently just returns if the state is created,
|
"""Currently just returns if the state is created,
|
||||||
|
@ -996,17 +906,13 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
|
|
||||||
expiration_date = DateField(
|
expiration_date = DateField(
|
||||||
null=True,
|
null=True,
|
||||||
help_text=(
|
help_text=("Duplication of registry's expiration date saved for ease of reporting"),
|
||||||
"Duplication of registry's expiration" "date saved for ease of reporting"
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def isActive(self):
|
def isActive(self):
|
||||||
return self.state == Domain.State.CREATED
|
return self.state == Domain.State.CREATED
|
||||||
|
|
||||||
def map_epp_contact_to_public_contact(
|
def map_epp_contact_to_public_contact(self, contact: eppInfo.InfoContactResultData, contact_id, contact_type):
|
||||||
self, contact: eppInfo.InfoContactResultData, contact_id, contact_type
|
|
||||||
):
|
|
||||||
"""Maps the Epp contact representation to a PublicContact object.
|
"""Maps the Epp contact representation to a PublicContact object.
|
||||||
|
|
||||||
contact -> eppInfo.InfoContactResultData: The converted contact object
|
contact -> eppInfo.InfoContactResultData: The converted contact object
|
||||||
|
@ -1028,10 +934,7 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
# Since contact_id is registry_id,
|
# Since contact_id is registry_id,
|
||||||
# check that its the right length
|
# check that its the right length
|
||||||
contact_id_length = len(contact_id)
|
contact_id_length = len(contact_id)
|
||||||
if (
|
if contact_id_length > PublicContact.get_max_id_length() or contact_id_length < 1:
|
||||||
contact_id_length > PublicContact.get_max_id_length()
|
|
||||||
or contact_id_length < 1
|
|
||||||
):
|
|
||||||
raise ContactError(code=ContactErrorCodes.CONTACT_ID_INVALID_LENGTH)
|
raise ContactError(code=ContactErrorCodes.CONTACT_ID_INVALID_LENGTH)
|
||||||
|
|
||||||
if not isinstance(contact, eppInfo.InfoContactResultData):
|
if not isinstance(contact, eppInfo.InfoContactResultData):
|
||||||
|
@ -1114,9 +1017,7 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
)
|
)
|
||||||
raise error
|
raise error
|
||||||
|
|
||||||
def generic_contact_getter(
|
def generic_contact_getter(self, contact_type_choice: PublicContact.ContactTypeChoices) -> PublicContact | None:
|
||||||
self, contact_type_choice: PublicContact.ContactTypeChoices
|
|
||||||
) -> PublicContact | None:
|
|
||||||
"""Retrieves the desired PublicContact from the registry.
|
"""Retrieves the desired PublicContact from the registry.
|
||||||
This abstracts the caching and EPP retrieval for
|
This abstracts the caching and EPP retrieval for
|
||||||
all contact items and thus may result in EPP calls being sent.
|
all contact items and thus may result in EPP calls being sent.
|
||||||
|
@ -1187,9 +1088,7 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
if contact_type == PublicContact.ContactTypeChoices.REGISTRANT:
|
if contact_type == PublicContact.ContactTypeChoices.REGISTRANT:
|
||||||
desired_contact = None
|
desired_contact = None
|
||||||
if isinstance(contacts, str):
|
if isinstance(contacts, str):
|
||||||
desired_contact = self._registrant_to_public_contact(
|
desired_contact = self._registrant_to_public_contact(self._cache["registrant"])
|
||||||
self._cache["registrant"]
|
|
||||||
)
|
|
||||||
# Set the cache with the updated object
|
# Set the cache with the updated object
|
||||||
# for performance reasons.
|
# for performance reasons.
|
||||||
if "registrant" in self._cache:
|
if "registrant" in self._cache:
|
||||||
|
@ -1203,9 +1102,7 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
if contacts is not None and contact_type in contacts:
|
if contacts is not None and contact_type in contacts:
|
||||||
_registry_id = contacts.get(contact_type)
|
_registry_id = contacts.get(contact_type)
|
||||||
|
|
||||||
desired = PublicContact.objects.filter(
|
desired = PublicContact.objects.filter(registry_id=_registry_id, domain=self, contact_type=contact_type)
|
||||||
registry_id=_registry_id, domain=self, contact_type=contact_type
|
|
||||||
)
|
|
||||||
|
|
||||||
if desired.count() == 1:
|
if desired.count() == 1:
|
||||||
return desired.get()
|
return desired.get()
|
||||||
|
@ -1214,10 +1111,7 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _handle_registrant_contact(self, contact):
|
def _handle_registrant_contact(self, contact):
|
||||||
if (
|
if contact.contact_type is not None and contact.contact_type == PublicContact.ContactTypeChoices.REGISTRANT:
|
||||||
contact.contact_type is not None
|
|
||||||
and contact.contact_type == PublicContact.ContactTypeChoices.REGISTRANT
|
|
||||||
):
|
|
||||||
return contact
|
return contact
|
||||||
else:
|
else:
|
||||||
raise ValueError("Invalid contact object for registrant_contact")
|
raise ValueError("Invalid contact object for registrant_contact")
|
||||||
|
@ -1297,9 +1191,7 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
administrative_contact = self.get_default_administrative_contact()
|
administrative_contact = self.get_default_administrative_contact()
|
||||||
administrative_contact.save()
|
administrative_contact.save()
|
||||||
|
|
||||||
@transition(
|
@transition(field="state", source=[State.READY, State.ON_HOLD], target=State.ON_HOLD)
|
||||||
field="state", source=[State.READY, State.ON_HOLD], target=State.ON_HOLD
|
|
||||||
)
|
|
||||||
def place_client_hold(self, ignoreEPP=False):
|
def place_client_hold(self, ignoreEPP=False):
|
||||||
"""place a clienthold on a domain (no longer should resolve)
|
"""place a clienthold on a domain (no longer should resolve)
|
||||||
ignoreEPP (boolean) - set to true to by-pass EPP (used for transition domains)
|
ignoreEPP (boolean) - set to true to by-pass EPP (used for transition domains)
|
||||||
|
@ -1325,9 +1217,7 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
self._remove_client_hold()
|
self._remove_client_hold()
|
||||||
# TODO -on the client hold ticket any additional error handling here
|
# TODO -on the client hold ticket any additional error handling here
|
||||||
|
|
||||||
@transition(
|
@transition(field="state", source=[State.ON_HOLD, State.DNS_NEEDED], target=State.DELETED)
|
||||||
field="state", source=[State.ON_HOLD, State.DNS_NEEDED], target=State.DELETED
|
|
||||||
)
|
|
||||||
def deletedInEpp(self):
|
def deletedInEpp(self):
|
||||||
"""Domain is deleted in epp but is saved in our database.
|
"""Domain is deleted in epp but is saved in our database.
|
||||||
Error handling should be provided by the caller."""
|
Error handling should be provided by the caller."""
|
||||||
|
@ -1345,9 +1235,7 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
logger.error("Could not delete domain. FSM failure: {err}")
|
logger.error("Could not delete domain. FSM failure: {err}")
|
||||||
raise err
|
raise err
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
logger.error(
|
logger.error(f"Could not delete domain. An unspecified error occured: {err}")
|
||||||
f"Could not delete domain. An unspecified error occured: {err}"
|
|
||||||
)
|
|
||||||
raise err
|
raise err
|
||||||
else:
|
else:
|
||||||
self._invalidate_cache()
|
self._invalidate_cache()
|
||||||
|
@ -1407,9 +1295,7 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
is_security = contact.contact_type == contact.ContactTypeChoices.SECURITY
|
is_security = contact.contact_type == contact.ContactTypeChoices.SECURITY
|
||||||
DF = epp.DiscloseField
|
DF = epp.DiscloseField
|
||||||
fields = {DF.EMAIL}
|
fields = {DF.EMAIL}
|
||||||
disclose = (
|
disclose = is_security and contact.email != PublicContact.get_default_security().email
|
||||||
is_security and contact.email != PublicContact.get_default_security().email
|
|
||||||
)
|
|
||||||
# Will only disclose DF.EMAIL if its not the default
|
# Will only disclose DF.EMAIL if its not the default
|
||||||
return epp.Disclose(
|
return epp.Disclose(
|
||||||
flag=disclose,
|
flag=disclose,
|
||||||
|
@ -1421,9 +1307,7 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
name=contact.name,
|
name=contact.name,
|
||||||
addr=epp.ContactAddr(
|
addr=epp.ContactAddr(
|
||||||
street=[
|
street=[
|
||||||
getattr(contact, street)
|
getattr(contact, street) for street in ["street1", "street2", "street3"] if hasattr(contact, street)
|
||||||
for street in ["street1", "street2", "street3"]
|
|
||||||
if hasattr(contact, street)
|
|
||||||
], # type: ignore
|
], # type: ignore
|
||||||
city=contact.city,
|
city=contact.city,
|
||||||
pc=contact.pc,
|
pc=contact.pc,
|
||||||
|
@ -1488,9 +1372,7 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
data = registry.send(req, cleaned=True).res_data[0]
|
data = registry.send(req, cleaned=True).res_data[0]
|
||||||
|
|
||||||
# Map the object we recieved from EPP to a PublicContact
|
# Map the object we recieved from EPP to a PublicContact
|
||||||
mapped_object = self.map_epp_contact_to_public_contact(
|
mapped_object = self.map_epp_contact_to_public_contact(data, domainContact.contact, domainContact.type)
|
||||||
data, domainContact.contact, domainContact.type
|
|
||||||
)
|
|
||||||
|
|
||||||
# Find/create it in the DB
|
# Find/create it in the DB
|
||||||
in_db = self._get_or_create_public_contact(mapped_object)
|
in_db = self._get_or_create_public_contact(mapped_object)
|
||||||
|
@ -1503,9 +1385,7 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
return self._request_contact_info(contact)
|
return self._request_contact_info(contact)
|
||||||
except RegistryError as e:
|
except RegistryError as e:
|
||||||
if e.code == ErrorCode.OBJECT_DOES_NOT_EXIST:
|
if e.code == ErrorCode.OBJECT_DOES_NOT_EXIST:
|
||||||
logger.info(
|
logger.info("_get_or_create_contact()-> contact doesn't exist so making it")
|
||||||
"_get_or_create_contact()-> contact doesn't exist so making it"
|
|
||||||
)
|
|
||||||
contact.domain = self
|
contact.domain = self
|
||||||
contact.save() # this will call the function based on type of contact
|
contact.save() # this will call the function based on type of contact
|
||||||
return self._request_contact_info(contact=contact)
|
return self._request_contact_info(contact=contact)
|
||||||
|
@ -1560,9 +1440,7 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
for ip_addr in ip_list:
|
for ip_addr in ip_list:
|
||||||
edited_ip_list.append(
|
edited_ip_list.append(epp.Ip(addr=ip_addr, ip="v6" if self.is_ipv6(ip_addr) else None))
|
||||||
epp.Ip(addr=ip_addr, ip="v6" if self.is_ipv6(ip_addr) else None)
|
|
||||||
)
|
|
||||||
|
|
||||||
return edited_ip_list
|
return edited_ip_list
|
||||||
|
|
||||||
|
@ -1579,12 +1457,7 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
if (
|
if ip_list is None or len(ip_list) == 0 and isinstance(old_ip_list, list) and len(old_ip_list) != 0:
|
||||||
ip_list is None
|
|
||||||
or len(ip_list) == 0
|
|
||||||
and isinstance(old_ip_list, list)
|
|
||||||
and len(old_ip_list) != 0
|
|
||||||
):
|
|
||||||
return ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY
|
return ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY
|
||||||
|
|
||||||
added_ip_list = set(ip_list).difference(old_ip_list)
|
added_ip_list = set(ip_list).difference(old_ip_list)
|
||||||
|
@ -1607,9 +1480,7 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
else:
|
else:
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
def addAndRemoveHostsFromDomain(
|
def addAndRemoveHostsFromDomain(self, hostsToAdd: list[str], hostsToDelete: list[str]):
|
||||||
self, hostsToAdd: list[str], hostsToDelete: list[str]
|
|
||||||
):
|
|
||||||
"""sends an UpdateDomain message to the registry with the hosts provided
|
"""sends an UpdateDomain message to the registry with the hosts provided
|
||||||
Args:
|
Args:
|
||||||
hostsToDelete (list[epp.HostObjSet])- list of host objects to delete
|
hostsToDelete (list[epp.HostObjSet])- list of host objects to delete
|
||||||
|
@ -1624,22 +1495,14 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
return ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY
|
return ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY
|
||||||
|
|
||||||
try:
|
try:
|
||||||
updateReq = commands.UpdateDomain(
|
updateReq = commands.UpdateDomain(name=self.name, rem=hostsToDelete, add=hostsToAdd)
|
||||||
name=self.name, rem=hostsToDelete, add=hostsToAdd
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.info(
|
logger.info("addAndRemoveHostsFromDomain()-> sending update domain req as %s" % updateReq)
|
||||||
"addAndRemoveHostsFromDomain()-> sending update domain req as %s"
|
|
||||||
% updateReq
|
|
||||||
)
|
|
||||||
response = registry.send(updateReq, cleaned=True)
|
response = registry.send(updateReq, cleaned=True)
|
||||||
|
|
||||||
return response.code
|
return response.code
|
||||||
except RegistryError as e:
|
except RegistryError as e:
|
||||||
logger.error(
|
logger.error("Error addAndRemoveHostsFromDomain, code was %s error was %s" % (e.code, e))
|
||||||
"Error addAndRemoveHostsFromDomain, code was %s error was %s"
|
|
||||||
% (e.code, e)
|
|
||||||
)
|
|
||||||
return e.code
|
return e.code
|
||||||
|
|
||||||
def _delete_hosts_if_not_used(self, hostsToDelete: list[str]):
|
def _delete_hosts_if_not_used(self, hostsToDelete: list[str]):
|
||||||
|
@ -1657,22 +1520,13 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
for nameserver in hostsToDelete:
|
for nameserver in hostsToDelete:
|
||||||
deleteHostReq = commands.DeleteHost(name=nameserver)
|
deleteHostReq = commands.DeleteHost(name=nameserver)
|
||||||
registry.send(deleteHostReq, cleaned=True)
|
registry.send(deleteHostReq, cleaned=True)
|
||||||
logger.info(
|
logger.info("_delete_hosts_if_not_used()-> sending delete host req as %s" % deleteHostReq)
|
||||||
"_delete_hosts_if_not_used()-> sending delete host req as %s"
|
|
||||||
% deleteHostReq
|
|
||||||
)
|
|
||||||
|
|
||||||
except RegistryError as e:
|
except RegistryError as e:
|
||||||
if e.code == ErrorCode.OBJECT_ASSOCIATION_PROHIBITS_OPERATION:
|
if e.code == ErrorCode.OBJECT_ASSOCIATION_PROHIBITS_OPERATION:
|
||||||
logger.info(
|
logger.info("Did not remove host %s because it is in use on another domain." % nameserver)
|
||||||
"Did not remove host %s because it is in use on another domain."
|
|
||||||
% nameserver
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
logger.error(
|
logger.error("Error _delete_hosts_if_not_used, code was %s error was %s" % (e.code, e))
|
||||||
"Error _delete_hosts_if_not_used, code was %s error was %s"
|
|
||||||
% (e.code, e)
|
|
||||||
)
|
|
||||||
|
|
||||||
def _fetch_cache(self, fetch_hosts=False, fetch_contacts=False):
|
def _fetch_cache(self, fetch_hosts=False, fetch_contacts=False):
|
||||||
"""Contact registry for info about a domain."""
|
"""Contact registry for info about a domain."""
|
||||||
|
@ -1768,9 +1622,7 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
# Raise an error if we find duplicates.
|
# Raise an error if we find duplicates.
|
||||||
# This should not occur
|
# This should not occur
|
||||||
if db_contact.count() > 1:
|
if db_contact.count() > 1:
|
||||||
raise Exception(
|
raise Exception(f"Multiple contacts found for {public_contact.contact_type}")
|
||||||
f"Multiple contacts found for {public_contact.contact_type}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Save to DB if it doesn't exist already.
|
# Save to DB if it doesn't exist already.
|
||||||
if db_contact.count() == 0:
|
if db_contact.count() == 0:
|
||||||
|
@ -1784,10 +1636,7 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
|
|
||||||
# Does the item we're grabbing match
|
# Does the item we're grabbing match
|
||||||
# what we have in our DB?
|
# what we have in our DB?
|
||||||
if (
|
if existing_contact.email != public_contact.email or existing_contact.registry_id != public_contact.registry_id:
|
||||||
existing_contact.email != public_contact.email
|
|
||||||
or existing_contact.registry_id != public_contact.registry_id
|
|
||||||
):
|
|
||||||
existing_contact.delete()
|
existing_contact.delete()
|
||||||
public_contact.save()
|
public_contact.save()
|
||||||
logger.warning("Requested PublicContact is out of sync " "with DB.")
|
logger.warning("Requested PublicContact is out of sync " "with DB.")
|
||||||
|
@ -1809,9 +1658,7 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
# Grabs the expanded contact
|
# Grabs the expanded contact
|
||||||
full_object = self._request_contact_info(contact)
|
full_object = self._request_contact_info(contact)
|
||||||
# Maps it to type PublicContact
|
# Maps it to type PublicContact
|
||||||
mapped_object = self.map_epp_contact_to_public_contact(
|
mapped_object = self.map_epp_contact_to_public_contact(full_object, contact.registry_id, contact.contact_type)
|
||||||
full_object, contact.registry_id, contact.contact_type
|
|
||||||
)
|
|
||||||
return self._get_or_create_public_contact(mapped_object)
|
return self._get_or_create_public_contact(mapped_object)
|
||||||
|
|
||||||
def _invalidate_cache(self):
|
def _invalidate_cache(self):
|
||||||
|
@ -1829,6 +1676,4 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
if property in self._cache:
|
if property in self._cache:
|
||||||
return self._cache[property]
|
return self._cache[property]
|
||||||
else:
|
else:
|
||||||
raise KeyError(
|
raise KeyError("Requested key %s was not found in registry cache." % str(property))
|
||||||
"Requested key %s was not found in registry cache." % str(property)
|
|
||||||
)
|
|
||||||
|
|
|
@ -131,8 +131,7 @@ class DomainApplication(TimeStampedModel):
|
||||||
|
|
||||||
FEDERAL = (
|
FEDERAL = (
|
||||||
"federal",
|
"federal",
|
||||||
"Federal: an agency of the U.S. government's executive, "
|
"Federal: an agency of the U.S. government's executive, legislative, or judicial branches",
|
||||||
"legislative, or judicial branches",
|
|
||||||
)
|
)
|
||||||
INTERSTATE = "interstate", "Interstate: an organization of two or more states"
|
INTERSTATE = "interstate", "Interstate: an organization of two or more states"
|
||||||
STATE_OR_TERRITORY = (
|
STATE_OR_TERRITORY = (
|
||||||
|
@ -143,8 +142,7 @@ class DomainApplication(TimeStampedModel):
|
||||||
)
|
)
|
||||||
TRIBAL = (
|
TRIBAL = (
|
||||||
"tribal",
|
"tribal",
|
||||||
"Tribal: a tribal government recognized by the federal or a state "
|
"Tribal: a tribal government recognized by the federal or a state government",
|
||||||
"government",
|
|
||||||
)
|
)
|
||||||
COUNTY = "county", "County: a county, parish, or borough"
|
COUNTY = "county", "County: a county, parish, or borough"
|
||||||
CITY = "city", "City: a city, town, township, village, etc."
|
CITY = "city", "City: a city, town, township, village, etc."
|
||||||
|
@ -154,8 +152,7 @@ class DomainApplication(TimeStampedModel):
|
||||||
)
|
)
|
||||||
SCHOOL_DISTRICT = (
|
SCHOOL_DISTRICT = (
|
||||||
"school_district",
|
"school_district",
|
||||||
"School district: a school district that is not part of a local "
|
"School district: a school district that is not part of a local government",
|
||||||
"government",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class BranchChoices(models.TextChoices):
|
class BranchChoices(models.TextChoices):
|
||||||
|
@ -168,10 +165,7 @@ class DomainApplication(TimeStampedModel):
|
||||||
"Advisory Council on Historic Preservation",
|
"Advisory Council on Historic Preservation",
|
||||||
"American Battle Monuments Commission",
|
"American Battle Monuments Commission",
|
||||||
"Appalachian Regional Commission",
|
"Appalachian Regional Commission",
|
||||||
(
|
("Appraisal Subcommittee of the Federal Financial Institutions Examination Council"),
|
||||||
"Appraisal Subcommittee of the Federal Financial "
|
|
||||||
"Institutions Examination Council"
|
|
||||||
),
|
|
||||||
"Armed Forces Retirement Home",
|
"Armed Forces Retirement Home",
|
||||||
"Barry Goldwater Scholarship and Excellence in Education Program",
|
"Barry Goldwater Scholarship and Excellence in Education Program",
|
||||||
"Central Intelligence Agency",
|
"Central Intelligence Agency",
|
||||||
|
@ -507,9 +501,7 @@ class DomainApplication(TimeStampedModel):
|
||||||
return not self.approved_domain.is_active()
|
return not self.approved_domain.is_active()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _send_status_update_email(
|
def _send_status_update_email(self, new_status, email_template, email_template_subject):
|
||||||
self, new_status, email_template, email_template_subject
|
|
||||||
):
|
|
||||||
"""Send a atatus update email to the submitter.
|
"""Send a atatus update email to the submitter.
|
||||||
|
|
||||||
The email goes to the email address that the submitter gave as their
|
The email goes to the email address that the submitter gave as their
|
||||||
|
@ -518,9 +510,7 @@ class DomainApplication(TimeStampedModel):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.submitter is None or self.submitter.email is None:
|
if self.submitter is None or self.submitter.email is None:
|
||||||
logger.warning(
|
logger.warning(f"Cannot send {new_status} email, no submitter email address.")
|
||||||
f"Cannot send {new_status} email, no submitter email address."
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
send_templated_email(
|
send_templated_email(
|
||||||
|
@ -533,9 +523,7 @@ class DomainApplication(TimeStampedModel):
|
||||||
except EmailSendingError:
|
except EmailSendingError:
|
||||||
logger.warning("Failed to send confirmation email", exc_info=True)
|
logger.warning("Failed to send confirmation email", exc_info=True)
|
||||||
|
|
||||||
@transition(
|
@transition(field="status", source=[STARTED, ACTION_NEEDED, WITHDRAWN], target=SUBMITTED)
|
||||||
field="status", source=[STARTED, ACTION_NEEDED, WITHDRAWN], target=SUBMITTED
|
|
||||||
)
|
|
||||||
def submit(self):
|
def submit(self):
|
||||||
"""Submit an application that is started.
|
"""Submit an application that is started.
|
||||||
|
|
||||||
|
|
|
@ -57,9 +57,7 @@ class DomainInvitation(TimeStampedModel):
|
||||||
except User.DoesNotExist:
|
except User.DoesNotExist:
|
||||||
# should not happen because a matching user should exist before
|
# should not happen because a matching user should exist before
|
||||||
# we retrieve this invitation
|
# we retrieve this invitation
|
||||||
raise RuntimeError(
|
raise RuntimeError("Cannot find the user to retrieve this domain invitation.")
|
||||||
"Cannot find the user to retrieve this domain invitation."
|
|
||||||
)
|
|
||||||
|
|
||||||
# and create a role for that user on this domain
|
# and create a role for that user on this domain
|
||||||
_, created = UserDomainRole.objects.get_or_create(
|
_, created = UserDomainRole.objects.get_or_create(
|
||||||
|
@ -68,6 +66,4 @@ class DomainInvitation(TimeStampedModel):
|
||||||
if not created:
|
if not created:
|
||||||
# something strange happened and this role already existed when
|
# something strange happened and this role already existed when
|
||||||
# the invitation was retrieved. Log that this occurred.
|
# the invitation was retrieved. Log that this occurred.
|
||||||
logger.warn(
|
logger.warn("Invitation %s was retrieved for a role that already exists.", self)
|
||||||
"Invitation %s was retrieved for a role that already exists.", self
|
|
||||||
)
|
|
||||||
|
|
|
@ -10,9 +10,7 @@ from .utility.time_stamped_model import TimeStampedModel
|
||||||
def get_id():
|
def get_id():
|
||||||
"""Generate a 16 character registry ID with a low probability of collision."""
|
"""Generate a 16 character registry ID with a low probability of collision."""
|
||||||
day = datetime.today().strftime("%A")[:2]
|
day = datetime.today().strftime("%A")[:2]
|
||||||
rand = "".join(
|
rand = "".join(choices(ascii_uppercase + ascii_lowercase + digits, k=14)) # nosec B311
|
||||||
choices(ascii_uppercase + ascii_lowercase + digits, k=14) # nosec B311
|
|
||||||
)
|
|
||||||
return f"{day}{rand}"
|
return f"{day}{rand}"
|
||||||
|
|
||||||
|
|
||||||
|
@ -69,16 +67,12 @@ class PublicContact(TimeStampedModel):
|
||||||
pc = models.TextField(null=False, help_text="Contact's postal code")
|
pc = models.TextField(null=False, help_text="Contact's postal code")
|
||||||
cc = models.TextField(null=False, help_text="Contact's country code")
|
cc = models.TextField(null=False, help_text="Contact's country code")
|
||||||
email = models.TextField(null=False, help_text="Contact's email address")
|
email = models.TextField(null=False, help_text="Contact's email address")
|
||||||
voice = models.TextField(
|
voice = models.TextField(null=False, help_text="Contact's phone number. Must be in ITU.E164.2005 format")
|
||||||
null=False, help_text="Contact's phone number. Must be in ITU.E164.2005 format"
|
|
||||||
)
|
|
||||||
fax = models.TextField(
|
fax = models.TextField(
|
||||||
null=True,
|
null=True,
|
||||||
help_text="Contact's fax number (null ok). Must be in ITU.E164.2005 format.",
|
help_text="Contact's fax number (null ok). Must be in ITU.E164.2005 format.",
|
||||||
)
|
)
|
||||||
pw = models.TextField(
|
pw = models.TextField(null=False, help_text="Contact's authorization code. 16 characters minimum.")
|
||||||
null=False, help_text="Contact's authorization code. 16 characters minimum."
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_default_registrant(cls):
|
def get_default_registrant(cls):
|
||||||
|
@ -154,8 +148,4 @@ class PublicContact(TimeStampedModel):
|
||||||
return cls._meta.get_field("registry_id").max_length
|
return cls._meta.get_field("registry_id").max_length
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return (
|
return f"{self.name} <{self.email}>" f"id: {self.registry_id} " f"type: {self.contact_type}"
|
||||||
f"{self.name} <{self.email}>"
|
|
||||||
f"id: {self.registry_id} "
|
|
||||||
f"type: {self.contact_type}"
|
|
||||||
)
|
|
||||||
|
|
|
@ -68,9 +68,7 @@ class User(AbstractUser):
|
||||||
def check_domain_invitations_on_login(self):
|
def check_domain_invitations_on_login(self):
|
||||||
"""When a user first arrives on the site, we need to retrieve any domain
|
"""When a user first arrives on the site, we need to retrieve any domain
|
||||||
invitations that match their email address."""
|
invitations that match their email address."""
|
||||||
for invitation in DomainInvitation.objects.filter(
|
for invitation in DomainInvitation.objects.filter(email=self.email, status=DomainInvitation.INVITED):
|
||||||
email=self.email, status=DomainInvitation.INVITED
|
|
||||||
):
|
|
||||||
try:
|
try:
|
||||||
invitation.retrieve()
|
invitation.retrieve()
|
||||||
invitation.save()
|
invitation.save()
|
||||||
|
@ -78,9 +76,7 @@ class User(AbstractUser):
|
||||||
# retrieving should not fail because of a missing user, but
|
# retrieving should not fail because of a missing user, but
|
||||||
# if it does fail, log the error so a new user can continue
|
# if it does fail, log the error so a new user can continue
|
||||||
# logging in
|
# logging in
|
||||||
logger.warn(
|
logger.warn("Failed to retrieve invitation %s", invitation, exc_info=True)
|
||||||
"Failed to retrieve invitation %s", invitation, exc_info=True
|
|
||||||
)
|
|
||||||
|
|
||||||
def create_domain_and_invite(self, transition_domain: TransitionDomain):
|
def create_domain_and_invite(self, transition_domain: TransitionDomain):
|
||||||
transition_domain_name = transition_domain.domain_name
|
transition_domain_name = transition_domain.domain_name
|
||||||
|
@ -89,9 +85,7 @@ class User(AbstractUser):
|
||||||
|
|
||||||
# type safety check. name should never be none
|
# type safety check. name should never be none
|
||||||
if transition_domain_name is not None:
|
if transition_domain_name is not None:
|
||||||
new_domain = Domain(
|
new_domain = Domain(name=transition_domain_name, state=transition_domain_status)
|
||||||
name=transition_domain_name, state=transition_domain_status
|
|
||||||
)
|
|
||||||
new_domain.save()
|
new_domain.save()
|
||||||
# check that a domain invitation doesn't already
|
# check that a domain invitation doesn't already
|
||||||
# exist for this e-mail / Domain pair
|
# exist for this e-mail / Domain pair
|
||||||
|
@ -100,9 +94,7 @@ class User(AbstractUser):
|
||||||
).exists()
|
).exists()
|
||||||
if not domain_email_already_in_domain_invites:
|
if not domain_email_already_in_domain_invites:
|
||||||
# Create new domain invitation
|
# Create new domain invitation
|
||||||
new_domain_invitation = DomainInvitation(
|
new_domain_invitation = DomainInvitation(email=transition_domain_email.lower(), domain=new_domain)
|
||||||
email=transition_domain_email.lower(), domain=new_domain
|
|
||||||
)
|
|
||||||
new_domain_invitation.save()
|
new_domain_invitation.save()
|
||||||
|
|
||||||
def check_transition_domains_on_login(self):
|
def check_transition_domains_on_login(self):
|
||||||
|
@ -129,9 +121,7 @@ class User(AbstractUser):
|
||||||
# with our data and migrations need to be run again.
|
# with our data and migrations need to be run again.
|
||||||
|
|
||||||
# Get the domain that corresponds with this transition domain
|
# Get the domain that corresponds with this transition domain
|
||||||
domain_exists = Domain.objects.filter(
|
domain_exists = Domain.objects.filter(name=transition_domain.domain_name).exists()
|
||||||
name=transition_domain.domain_name
|
|
||||||
).exists()
|
|
||||||
if not domain_exists:
|
if not domain_exists:
|
||||||
logger.warn(
|
logger.warn(
|
||||||
"""There are transition domains without
|
"""There are transition domains without
|
||||||
|
@ -147,17 +137,15 @@ class User(AbstractUser):
|
||||||
|
|
||||||
# Create a domain information object, if one doesn't
|
# Create a domain information object, if one doesn't
|
||||||
# already exist
|
# already exist
|
||||||
domain_info_exists = DomainInformation.objects.filter(
|
domain_info_exists = DomainInformation.objects.filter(domain=domain).exists()
|
||||||
domain=domain
|
|
||||||
).exists()
|
|
||||||
if not domain_info_exists:
|
if not domain_info_exists:
|
||||||
new_domain_info = DomainInformation(creator=self, domain=domain)
|
new_domain_info = DomainInformation(creator=self, domain=domain)
|
||||||
new_domain_info.save()
|
new_domain_info.save()
|
||||||
|
|
||||||
def first_login(self):
|
def on_each_login(self):
|
||||||
"""Callback when the user is authenticated for the very first time.
|
"""Callback each time the user is authenticated.
|
||||||
|
|
||||||
When a user first arrives on the site, we need to retrieve any domain
|
When a user arrives on the site each time, we need to retrieve any domain
|
||||||
invitations that match their email address.
|
invitations that match their email address.
|
||||||
|
|
||||||
We also need to check if they are logging in with the same e-mail
|
We also need to check if they are logging in with the same e-mail
|
||||||
|
|
|
@ -44,7 +44,5 @@ class UserDomainRole(TimeStampedModel):
|
||||||
constraints = [
|
constraints = [
|
||||||
# a user can have only one role on a given domain, that is, there can
|
# a user can have only one role on a given domain, that is, there can
|
||||||
# be only a single row with a certain (user, domain) pair.
|
# be only a single row with a certain (user, domain) pair.
|
||||||
models.UniqueConstraint(
|
models.UniqueConstraint(fields=["user", "domain"], name="unique_user_domain_role")
|
||||||
fields=["user", "domain"], name="unique_user_domain_role"
|
|
||||||
)
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -87,14 +87,10 @@ class UserGroup(Group):
|
||||||
permissions = permission["permissions"]
|
permissions = permission["permissions"]
|
||||||
|
|
||||||
# Retrieve the content type for the app and model
|
# Retrieve the content type for the app and model
|
||||||
content_type = ContentType.objects.get(
|
content_type = ContentType.objects.get(app_label=app_label, model=model_name)
|
||||||
app_label=app_label, model=model_name
|
|
||||||
)
|
|
||||||
|
|
||||||
# Retrieve the permissions based on their codenames
|
# Retrieve the permissions based on their codenames
|
||||||
permissions = Permission.objects.filter(
|
permissions = Permission.objects.filter(content_type=content_type, codename__in=permissions)
|
||||||
content_type=content_type, codename__in=permissions
|
|
||||||
)
|
|
||||||
|
|
||||||
# Assign the permissions to the group
|
# Assign the permissions to the group
|
||||||
cisa_analysts_group.permissions.add(*permissions)
|
cisa_analysts_group.permissions.add(*permissions)
|
||||||
|
@ -113,9 +109,7 @@ class UserGroup(Group):
|
||||||
)
|
)
|
||||||
|
|
||||||
cisa_analysts_group.save()
|
cisa_analysts_group.save()
|
||||||
logger.debug(
|
logger.debug("CISA Analyt permissions added to group " + cisa_analysts_group.name)
|
||||||
"CISA Analyt permissions added to group " + cisa_analysts_group.name
|
|
||||||
)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error creating analyst permissions group: {e}")
|
logger.error(f"Error creating analyst permissions group: {e}")
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
Would you like to <a href="{% url 'login' %}"> try logging in again?</a>
|
Would you like to <a href="{% url 'login' %}"> try logging in again?</a>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
If you'd like help with this error <a href="{% public_site_url 'contact/' %}"> contact us </a>.
|
If you'd like help with this error <a class="usa-link" rel="noopener noreferrer" target="_blank" href="{% public_site_url 'contact/' %}">contact us</a>.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{% if log_identifier %}
|
{% if log_identifier %}
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
Would you like to <a href="{% url 'login' %}"> try logging in again</a>?
|
Would you like to <a href="{% url 'login' %}"> try logging in again</a>?
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
If you'd like help with this error <a href="{% public_site_url 'contact' %}"> contact us </a>.
|
If you'd like help with this error <a class="usa-link" rel="noopener noreferrer" target="_blank" href="{% public_site_url 'contact' %}">contact us</a>.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{% if log_identifier %}
|
{% if log_identifier %}
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
{% translate "Status 404" %}
|
{% translate "Status 404" %}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<p> Try going to the <a href="/">homepage</a>. If you can’t find what you’re looking for, <a href="{% public_site_url 'contact' %}"> contact us </a>.
|
<p> Try going to the <a href="/">homepage</a>. If you can’t find what you’re looking for, <a class="usa-link" rel="noopener noreferrer" target="_blank" href="{% public_site_url 'contact' %}">contact us</a>.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>
|
<p>
|
||||||
Sorry! Try waiting a few minutes and then reloading the page.
|
Sorry! Try waiting a few minutes and then reloading the page.
|
||||||
<a href="{% public_site_url 'contact' %}"> contact us </a> if you need help.
|
<a class="usa-link" rel="noopener noreferrer" target="_blank" href="{% public_site_url 'contact' %}"> Contact us</a> if you need help.
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
{% include "includes/ao_example.html" %}
|
{% include "includes/ao_example.html" %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p>We typically don’t reach out to the authorizing official, but if contact is necessary, our practice is to coordinate first with you, the requestor. Read more about <a href="{% public_site_url 'domains/eligibility/#you-must-have-approval-from-an-authorizing-official-within-your-organization' %}">who can serve as an authorizing official</a>.</p>
|
<p>We typically don’t reach out to the authorizing official, but if contact is necessary, our practice is to coordinate first with you, the requestor. Read more about <a class="usa-link" rel="noopener noreferrer" target="_blank" href="{% public_site_url 'domains/eligibility/#you-must-have-approval-from-an-authorizing-official-within-your-organization' %}">who can serve as an authorizing official</a>.</p>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
{% load static field_helpers url_helpers %}
|
{% load static field_helpers url_helpers %}
|
||||||
|
|
||||||
{% block form_instructions %}
|
{% block form_instructions %}
|
||||||
<p>Before requesting a .gov domain, <a href="{% public_site_url 'domains/choosing' %}">please make sure it
|
<p>Before requesting a .gov domain, <a class="usa-link" rel="noopener noreferrer" target="_blank" href="{% public_site_url 'domains/choosing' %}">please make sure it
|
||||||
meets our naming requirements</a>. Your domain name must:
|
meets our naming requirements</a>. Your domain name must:
|
||||||
<ul class="usa-list">
|
<ul class="usa-list">
|
||||||
<li>Be available </li>
|
<li>Be available </li>
|
||||||
|
|
|
@ -8,7 +8,7 @@ domain name or for mainly internal use.</p>
|
||||||
<p>Describe the reason for your domain request. Explain how you plan to use this domain.
|
<p>Describe the reason for your domain request. Explain how you plan to use this domain.
|
||||||
Who is your intended audience? Will you use it for a website and/or email? Are you moving
|
Who is your intended audience? Will you use it for a website and/or email? Are you moving
|
||||||
your website from another top-level domain (like .com or .org)?
|
your website from another top-level domain (like .com or .org)?
|
||||||
Read about <a href="{% public_site_url 'domains/requirements/' %}">activities that are prohibited on .gov domains.</a></p>
|
Read about <a class="usa-link" rel="noopener noreferrer" target="_blank" href="{% public_site_url 'domains/requirements/' %}">activities that are prohibited on .gov domains.</a></p>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
{% with sublabel_text="Please include the entire name of your tribe as recognized by the Bureau of Indian Affairs." %}
|
{% with sublabel_text="Please include the entire name of your tribe as recognized by the Bureau of Indian Affairs." %}
|
||||||
{% with link_text="Bureau of Indian Affairs" %}
|
{% with link_text="Bureau of Indian Affairs" %}
|
||||||
{% with link_href="https://www.federalregister.gov/documents/2023/01/12/2023-00504/indian-entities-recognized-by-and-eligible-to-receive-services-from-the-united-states-bureau-of" %}
|
{% with link_href="https://www.federalregister.gov/documents/2023/01/12/2023-00504/indian-entities-recognized-by-and-eligible-to-receive-services-from-the-united-states-bureau-of" %}
|
||||||
{% with target_blank="true" %}
|
{% with external_link="true" target_blank="true" %}
|
||||||
{% input_with_errors forms.0.tribe_name %}
|
{% input_with_errors forms.0.tribe_name %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
|
@ -3,6 +3,14 @@
|
||||||
<html class="no-js" lang="{{ LANGUAGE_CODE }}">
|
<html class="no-js" lang="{{ LANGUAGE_CODE }}">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
|
{% if IS_PRODUCTION %}
|
||||||
|
<!-- Google tag (gtag.js) -->
|
||||||
|
<script async src="https://www.googletagmanager.com/gtag/js?id=G-PZ5QSP6QPL"></script>
|
||||||
|
<script>
|
||||||
|
window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', 'G-PZ5QSP6QPL');
|
||||||
|
</script>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
||||||
<title>
|
<title>
|
||||||
|
@ -62,10 +70,11 @@
|
||||||
|
|
||||||
{% if IS_DEMO_SITE %}
|
{% if IS_DEMO_SITE %}
|
||||||
<section aria-label="Alert" >
|
<section aria-label="Alert" >
|
||||||
<div class="usa-alert usa-alert--warning usa-alert--no-icon">
|
<div class="usa-alert usa-alert--info">
|
||||||
<div class="usa-alert__body">
|
<div class="usa-alert__body">
|
||||||
<p class="usa-alert__text">
|
<h4 class="usa-alert__heading">New domain requests are paused</h4>
|
||||||
<strong>BETA SITE:</strong> We’re building a new way to get a .gov. Take a look around, but don’t rely on this site yet. This site is for testing purposes only. Don’t enter real data into any form on this site. To learn about requesting a .gov domain, visit <a href="https://get.gov" class="usa-link">get.gov</a>
|
<p class="usa-alert__text measure-none">
|
||||||
|
This is the new registrar for managing .gov domains. Note that we’re not accepting requests for new .gov domains until January 2024. Follow .gov updates at <a href="https://get.gov/updates/" class="usa-link">get.gov/updates/</a>.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
<h1>Authorizing official</h1>
|
<h1>Authorizing official</h1>
|
||||||
|
|
||||||
<p>Your authorizing official is the person within your organization who can
|
<p>Your authorizing official is the person within your organization who can
|
||||||
authorize domain requests. This person must be in a role of significant, executive responsibility within the organization. Read more about <a class="usa-link" href="{% public_site_url 'domains/eligibility/#you-must-have-approval-from-an-authorizing-official-within-your-organization' %}">who can serve as an authorizing official</a>.</p>
|
authorize domain requests. This person must be in a role of significant, executive responsibility within the organization. Read more about <a class="usa-link" rel="noopener noreferrer" target="_blank" href="{% public_site_url 'domains/eligibility/#you-must-have-approval-from-an-authorizing-official-within-your-organization' %}">who can serve as an authorizing official</a>.</p>
|
||||||
|
|
||||||
{% include "includes/required_fields.html" %}
|
{% include "includes/required_fields.html" %}
|
||||||
|
|
||||||
|
|
|
@ -16,12 +16,23 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="grid-row grid-gap">
|
<div class="grid-row grid-gap">
|
||||||
<div class="tablet:grid-col-3">
|
<div class="tablet:grid-col-3">
|
||||||
|
{% if domain.domain_info %}
|
||||||
{% include 'domain_sidebar.html' %}
|
{% include 'domain_sidebar.html' %}
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tablet:grid-col-9">
|
<div class="tablet:grid-col-9">
|
||||||
<main id="main-content" class="grid-container">
|
<main id="main-content" class="grid-container">
|
||||||
|
{% if not domain.domain_info %}
|
||||||
|
<div class="usa-alert usa-alert--error margin-bottom-2">
|
||||||
|
<div class="usa-alert__body">
|
||||||
|
<h4 class="usa-alert__heading larger-font-sizing">Domain missing domain information</h4>
|
||||||
|
<p class="usa-alert__text ">
|
||||||
|
You are attempting to manage a domain, {{ domain.name }}, which does not have a domain information object. Please correct this in the admin by editing the domain, and adding domain information, as appropriate.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
{% if is_analyst_or_superuser and analyst_action == 'edit' and analyst_action_location == domain.pk %}
|
{% if is_analyst_or_superuser and analyst_action == 'edit' and analyst_action_location == domain.pk %}
|
||||||
<div class="usa-alert usa-alert--warning margin-bottom-2">
|
<div class="usa-alert usa-alert--warning margin-bottom-2">
|
||||||
<div class="usa-alert__body">
|
<div class="usa-alert__body">
|
||||||
|
@ -58,7 +69,7 @@
|
||||||
<h1 class="break-word">{{ domain.name }}</h1>
|
<h1 class="break-word">{{ domain.name }}</h1>
|
||||||
|
|
||||||
{% endblock %} {# domain_content #}
|
{% endblock %} {# domain_content #}
|
||||||
|
{% endif %}
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
<h1>Security email</h1>
|
<h1>Security email</h1>
|
||||||
|
|
||||||
<p>We strongly recommend that you provide a security email. This email will allow the public to report observed or suspected security issues on your domain. Security emails are made public and included in the <a href="{% public_site_url 'about/data/' %}">.gov domain data</a> we provide.</p>
|
<p>We strongly recommend that you provide a security email. This email will allow the public to report observed or suspected security issues on your domain. Security emails are made public and included in the <a class="usa-link" rel="noopener noreferrer" target="_blank" href="{% public_site_url 'about/data/' %}">.gov domain data</a> we provide.</p>
|
||||||
|
|
||||||
<p>A security contact should be capable of evaluating or triaging security reports for your entire domain. Use a team email address, not an individual’s email. We recommend using an alias, like security@domain.gov.</p>
|
<p>A security contact should be capable of evaluating or triaging security reports for your entire domain. Use a team email address, not an individual’s email. We recommend using an alias, like security@domain.gov.</p>
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,41 @@
|
||||||
{% autoescape off %}{# In a text file, we don't want to have HTML entities escaped #}
|
{% autoescape off %}{# In a text file, we don't want to have HTML entities escaped #}
|
||||||
Hi.
|
Hello from .gov.
|
||||||
|
|
||||||
You have been added as a manager on {% if domains|length > 1 %}multiple domains (listed below){% else %}{{ domains.0 }}{% endif %}.
|
Our new domain management system—the .gov registrar—is now available. The organization, contacts, and DNS information for your .gov domain{% if domains|length > 1 %}s{% endif %} have been transferred to our new registrar.
|
||||||
|
|
||||||
|
Your .gov domain{% if domains|length > 1 %}s{% endif %}:
|
||||||
|
{% for domain in domains %} - {{ domain }}
|
||||||
|
{% endfor %}
|
||||||
|
If you’re not affiliated with the above domain{% if domains|length > 1 %}s{% endif %} or think you received this message in error, let us know in a reply to this email.
|
||||||
|
|
||||||
|
|
||||||
|
CREATE A LOGIN.GOV ACCOUNT
|
||||||
|
|
||||||
|
You can’t use your old credentials to access the new registrar. Access is now managed through Login.gov, a simple and secure process for signing into many government services with one account. Follow these steps to create your Login.gov account <https://login.gov/help/get-started/create-your-account/>.
|
||||||
|
|
||||||
|
When creating an account, you’ll need to provide the same email address you used to log in to the old registrar. That will ensure your domains are linked to your Login.gov account.
|
||||||
|
|
||||||
|
If you need help finding the email address you used in the past, let us know in a reply to this email.
|
||||||
|
|
||||||
|
CHECK YOUR .GOV DOMAIN CONTACTS
|
||||||
|
|
||||||
|
This is a good time to check who has access to your .gov domain{% if domains|length > 1 %}s{% endif %}. The admin, technical, and billing contacts listed for your domain{% if domains|length > 1 %}s{% endif %} in our old system also received this email. In our new registrar, these contacts are all considered “domain managers.” We no longer have the admin, technical, and billing roles, and you aren’t limited to three domain managers like in the old system.
|
||||||
|
|
||||||
|
1. Once you have your Login.gov account, sign in to the new registrar at <https://manage.get.gov>.
|
||||||
|
2. Click the “Manage” link next to your .gov domain, then click on “Domain managers” to see who has access to your domain.
|
||||||
|
3. If any of these users should not have access to your domain, let us know in a reply to this email.
|
||||||
|
|
||||||
|
After verifying who has access to your domain{% if domains|length > 1 %}s{% endif %}, review your contact information to make sure it's up to date.
|
||||||
|
|
||||||
|
|
||||||
|
DOMAIN EXPIRATION DATES EXTENDED BY ONE YEAR
|
||||||
|
|
||||||
|
Expiration dates for .gov domains in good standing have been extended for one year. Expiration dates won't be shown in the new registrar yet.
|
||||||
|
|
||||||
YOU NEED A LOGIN.GOV ACCOUNT
|
|
||||||
You’ll need a Login.gov account to manage your .gov domain{% if domains|length > 1 %}s{% endif %}. Login.gov provides a simple and secure process for signing into many government services with one account. If you don’t already have one, follow these steps to create your Login.gov account <https://login.gov/help/get-started/create-your-account/>.
|
|
||||||
|
|
||||||
DOMAIN MANAGEMENT
|
|
||||||
As a .gov domain manager you can add or update information about your domain{% if domains|length > 1 %}s{% endif %}. You’ll also serve as a contact for your .gov domain{% if domains|length > 1 %}s{% endif %}. Please keep your contact information updated. Learn more about domain management <https://get.gov/help/>.
|
|
||||||
{% if domains|length > 1 %}
|
|
||||||
DOMAINS
|
|
||||||
{% for domain in domains %} {{ domain }}
|
|
||||||
{% endfor %}{% else %}
|
|
||||||
{% endif %}
|
|
||||||
SOMETHING WRONG?
|
SOMETHING WRONG?
|
||||||
If you’re not affiliated with {{ domain }} or think you received this message in error, contact the .gov team <https://get.gov/help/#contact-us>.
|
|
||||||
|
If you think you received this message in error or have a question, let us know in a reply to this email.
|
||||||
|
|
||||||
|
|
||||||
THANK YOU
|
THANK YOU
|
||||||
|
@ -24,6 +45,8 @@ THANK YOU
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
|
|
||||||
The .gov team
|
The .gov team
|
||||||
Contact us: <https://get.gov/contact/>
|
|
||||||
Visit <https://get.gov>
|
.Gov blog <https://get.gov/updates/>
|
||||||
|
Domain management <https://manage.get.gov>
|
||||||
|
Get.gov <https://get.gov>
|
||||||
{% endautoescape %}
|
{% endautoescape %}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
You've been added to a .gov domain
|
(Action required) Manage your .gov domain{% if domains|length > 1 %}s{% endif %} in the new registrar
|
|
@ -13,9 +13,22 @@
|
||||||
<h1>Manage your domains</h2>
|
<h1>Manage your domains</h2>
|
||||||
|
|
||||||
<p class="margin-top-4">
|
<p class="margin-top-4">
|
||||||
<a href="{% url 'application:' %}" class="usa-button">
|
{% if IS_PRODUCTION %}
|
||||||
|
<a href="javascript:void(0)"
|
||||||
|
class="usa-button usa-tooltip disabled-link"
|
||||||
|
data-position="right"
|
||||||
|
title="Coming in 2024"
|
||||||
|
aria-disabled="true"
|
||||||
|
data-tooltip="true"
|
||||||
|
>
|
||||||
Start a new domain request
|
Start a new domain request
|
||||||
</a>
|
</a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{% url 'application:' %}" class="usa-button"
|
||||||
|
>
|
||||||
|
Start a new domain request
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<section class="section--outlined tablet:grid-col-11 desktop:grid-col-10">
|
<section class="section--outlined tablet:grid-col-11 desktop:grid-col-10">
|
||||||
|
@ -126,11 +139,11 @@
|
||||||
></div>
|
></div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>You don't have any active domain requests right now</p>
|
<p>You don't have any active domain requests right now</p>
|
||||||
<p><a href="{% url 'application:' %}" class="usa-button">Start a new domain request</a></p>
|
<!-- <p><a href="{% url 'application:' %}" class="usa-button">Start a new domain request</a></p> -->
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{# Note: Reimplement this after MVP.. #}
|
{# Note: Reimplement this after MVP #}
|
||||||
<!--
|
<!--
|
||||||
<section class="section--outlined tablet:grid-col-11 desktop:grid-col-10">
|
<section class="section--outlined tablet:grid-col-11 desktop:grid-col-10">
|
||||||
<h2>Archived domains</h2>
|
<h2>Archived domains</h2>
|
||||||
|
|
|
@ -44,7 +44,7 @@
|
||||||
<p>Domain requests from state legislatures and courts must be authorized by an agency’s <strong>Chief Information Officer</strong> or <strong>highest-ranking executive</strong>.</p>
|
<p>Domain requests from state legislatures and courts must be authorized by an agency’s <strong>Chief Information Officer</strong> or <strong>highest-ranking executive</strong>.</p>
|
||||||
|
|
||||||
{% elif organization_type == 'tribal' %}
|
{% elif organization_type == 'tribal' %}
|
||||||
<p><strong>Domain requests from federally-recognized tribal governments must be authorized by the leader of the tribe</strong>, as recognized by the <a href="https://www.bia.gov/service/tribal-leaders-directory" class="usa-link">Bureau of Indian Affairs.</a></p>
|
<p><strong>Domain requests from federally-recognized tribal governments must be authorized by the leader of the tribe</strong>, as recognized by the <a class="usa-link usa-link--external" rel="noopener noreferrer" target="_blank" href="https://www.bia.gov/service/tribal-leaders-directory">Bureau of Indian Affairs</a>.</p>
|
||||||
<p><strong>Domain requests from state-recognized tribal governments must be authorized by the leader of the tribe</strong>, as determined by the state’s tribal recognition initiative.</p>
|
<p><strong>Domain requests from state-recognized tribal governments must be authorized by the leader of the tribe</strong>, as determined by the state’s tribal recognition initiative.</p>
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -55,7 +55,7 @@
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
{% elif organization_type == 'county' %}
|
{% elif organization_type == 'county' %}
|
||||||
<p>Most county .gov domains must include the two-letter state abbreviation or the full state name. County names that aren’t shared by any other city, county, parish, town, borough, village or equivalent in the U.S., at the time a domain is granted, can be requested without referring to the state. Counties can include “county” in their domain to distinguish it from other places with similar names. We use the <a href="https://www.census.gov/geographies/reference-files/time-series/geo/gazetteer-files.html">Census Bureau’s National Places Gazetteer Files</a> to determine if county names are unique.</p>
|
<p>Most county .gov domains must include the two-letter state abbreviation or the full state name. County names that aren’t shared by any other city, county, parish, town, borough, village or equivalent in the U.S., at the time a domain is granted, can be requested without referring to the state. Counties can include “county” in their domain to distinguish it from other places with similar names. We use the <a class="usa-link usa-link--external" rel="noopener noreferrer" target="_blank" href="https://www.census.gov/geographies/reference-files/time-series/geo/gazetteer-files.html">Census Bureau’s National Places Gazetteer Files</a> to determine if county names are unique.</p>
|
||||||
<p><strong>Examples:</strong></p>
|
<p><strong>Examples:</strong></p>
|
||||||
<ul class="usa-list">
|
<ul class="usa-list">
|
||||||
<li>AdamsCountyMS.gov</li>
|
<li>AdamsCountyMS.gov</li>
|
||||||
|
@ -68,7 +68,7 @@
|
||||||
<p>Most city domains must include the two-letter state abbreviation or clearly spell out the state name. Using phrases like “City of” or “Town of” is optional.</p>
|
<p>Most city domains must include the two-letter state abbreviation or clearly spell out the state name. Using phrases like “City of” or “Town of” is optional.</p>
|
||||||
<p>Cities that meet one of the criteria below don’t have to refer to their state in the domain name.
|
<p>Cities that meet one of the criteria below don’t have to refer to their state in the domain name.
|
||||||
<ul class="usa-list">
|
<ul class="usa-list">
|
||||||
<li>City names that are not shared by any other U.S. city, town, or village can be requested without referring to the state. We use the <a href="https://www.census.gov/geographies/reference-files/time-series/geo/gazetteer-files.html">Census Bureau’s National Places Gazetteer Files</a> to determine if names are unique.</li>
|
<li>City names that are not shared by any other U.S. city, town, or village can be requested without referring to the state. We use the <a class="usa-link usa-link--external" rel="noopener noreferrer" target="_blank" href="https://www.census.gov/geographies/reference-files/time-series/geo/gazetteer-files.html">Census Bureau’s National Places Gazetteer Files</a> to determine if names are unique.</li>
|
||||||
<li>Certain cities are so well-known that they may not require a state reference to communicate location. We use the list of U.S. “dateline cities” in the Associated Press Stylebook to make this determination.</li>
|
<li>Certain cities are so well-known that they may not require a state reference to communicate location. We use the list of U.S. “dateline cities” in the Associated Press Stylebook to make this determination.</li>
|
||||||
<li>The 50 largest cities, as measured by population according to the Census Bureau, can have .gov domain names that don’t refer to their state.</li>
|
<li>The 50 largest cities, as measured by population according to the Census Bureau, can have .gov domain names that don’t refer to their state.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -81,7 +81,7 @@
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{% elif organization_type == 'special_district' %}
|
{% elif organization_type == 'special_district' %}
|
||||||
<p>Domain names must represent your organization or institutional name, not solely the services you provide. It also needs to include your two-letter state abbreviation or clearly spell out the state name unless <a href="{% public_site_url 'domains/choosing/#counties' %}">county or city exceptions apply</a>.</p>
|
<p>Domain names must represent your organization or institutional name, not solely the services you provide. It also needs to include your two-letter state abbreviation or clearly spell out the state name unless <a class="usa-link" rel="noopener noreferrer" target="_blank" href="{% public_site_url 'domains/choosing/#counties' %}">county or city exceptions apply</a>.</p>
|
||||||
<p><strong>Examples:</strong></p>
|
<p><strong>Examples:</strong></p>
|
||||||
<ul class="usa-list">
|
<ul class="usa-list">
|
||||||
<li>ElectionsShelbyTN.gov</li>
|
<li>ElectionsShelbyTN.gov</li>
|
||||||
|
|
|
@ -27,11 +27,11 @@
|
||||||
<address class="usa-footer__address">
|
<address class="usa-footer__address">
|
||||||
<div class="usa-footer__contact-info grid-row grid-gap-md">
|
<div class="usa-footer__contact-info grid-row grid-gap-md">
|
||||||
<div class="grid-col-auto">
|
<div class="grid-col-auto">
|
||||||
<a href="{% public_site_url 'help/' %}" class="usa-link"> Help </a>
|
<a class="usa-link" rel="noopener noreferrer" target="_blank" href="{% public_site_url 'help/' %}">Help </a>
|
||||||
</div>
|
</div>
|
||||||
<span class=""> | </span>
|
<span class=""> | </span>
|
||||||
<div class="grid-col-auto">
|
<div class="grid-col-auto">
|
||||||
<a href="{% public_site_url 'contact/' %}" class="usa-link">Contact us</a>
|
<a class="usa-link" rel="noopener noreferrer" target="_blank" href="{% public_site_url 'contact/' %}">Contact us</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</address>
|
</address>
|
||||||
|
@ -47,7 +47,7 @@
|
||||||
>
|
>
|
||||||
<div class="usa-identifier__container">
|
<div class="usa-identifier__container">
|
||||||
<div class="usa-identifier__logos">
|
<div class="usa-identifier__logos">
|
||||||
<a href="https://www.cisa.gov" class="usa-identifier__logo"
|
<a rel="noopener noreferrer" target="_blank" href="https://www.cisa.gov" class="usa-identifier__logo"
|
||||||
><img
|
><img
|
||||||
class="usa-identifier__logo-img"
|
class="usa-identifier__logo-img"
|
||||||
src="{% static 'img/CISA_logo.png' %}"
|
src="{% static 'img/CISA_logo.png' %}"
|
||||||
|
@ -62,7 +62,7 @@
|
||||||
>
|
>
|
||||||
<p class="usa-identifier__identity-domain">get.gov</p>
|
<p class="usa-identifier__identity-domain">get.gov</p>
|
||||||
<p class="usa-identifier__identity-disclaimer">
|
<p class="usa-identifier__identity-disclaimer">
|
||||||
An official website of the <a href="https://www.cisa.gov" class="usa-link">Cybersecurity and Infrastructure Security Agency</a>
|
An official website of the <a rel="noopener noreferrer" target="_blank" href="https://www.cisa.gov" class="usa-link">Cybersecurity and Infrastructure Security Agency</a>
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
@ -74,35 +74,35 @@
|
||||||
<div class="usa-identifier__container">
|
<div class="usa-identifier__container">
|
||||||
<ul class="usa-identifier__required-links-list">
|
<ul class="usa-identifier__required-links-list">
|
||||||
<li class="usa-identifier__required-links-item">
|
<li class="usa-identifier__required-links-item">
|
||||||
<a href="{% public_site_url 'about/' %}"
|
<a rel="noopener noreferrer" target="_blank" href="{% public_site_url 'about/' %}"
|
||||||
class="usa-identifier__required-link usa-link">About .gov</a>
|
class="usa-identifier__required-link usa-link">About .gov</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="usa-identifier__required-links-item">
|
<li class="usa-identifier__required-links-item">
|
||||||
<a
|
<a rel="noopener noreferrer" target="_blank"
|
||||||
href="https://github.com/cisagov/getgov"
|
href="https://github.com/cisagov/getgov"
|
||||||
class="usa-identifier__required-link usa-link usa-link--external"
|
class="usa-identifier__required-link usa-link usa-link--external"
|
||||||
>.gov on Github</a
|
>.gov on Github</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li class="usa-identifier__required-links-item">
|
<li class="usa-identifier__required-links-item">
|
||||||
<a href="{% public_site_url 'privacy-policy/' %}" class="usa-identifier__required-link usa-link">Privacy policy</a>
|
<a rel="noopener noreferrer" target="_blank" href="{% public_site_url 'privacy-policy/' %}" class="usa-identifier__required-link usa-link">Privacy policy</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="usa-identifier__required-links-item">
|
<li class="usa-identifier__required-links-item">
|
||||||
<a href="https://www.dhs.gov/accessibility" class="usa-identifier__required-link usa-link usa-link--external"
|
<a rel="noopener noreferrer" target="_blank" href="https://www.dhs.gov/accessibility" class="usa-identifier__required-link usa-link usa-link--external"
|
||||||
>Accessibility</a
|
>Accessibility</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li class="usa-identifier__required-links-item">
|
<li class="usa-identifier__required-links-item">
|
||||||
<a href="{% public_site_url 'vulnerability-disclosure-policy/' %}" class="usa-identifier__required-link usa-link"
|
<a rel="noopener noreferrer" target="_blank" href="{% public_site_url 'vulnerability-disclosure-policy/' %}" class="usa-identifier__required-link usa-link"
|
||||||
>Vulnerability disclosure policy</a>
|
>Vulnerability disclosure policy</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="usa-identifier__required-links-item">
|
<li class="usa-identifier__required-links-item">
|
||||||
<a href="https://www.cisa.gov/cisa-no-fear-act-reporting" class="usa-identifier__required-link usa-link"
|
<a rel="noopener noreferrer" target="_blank" href="https://www.cisa.gov/cisa-no-fear-act-reporting" class="usa-identifier__required-link usa-link"
|
||||||
>No FEAR Act data</a
|
>No FEAR Act data</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li class="usa-identifier__required-links-item">
|
<li class="usa-identifier__required-links-item">
|
||||||
<a href="https://www.dhs.gov/freedom-information-act-foia" class="usa-identifier__required-link usa-link usa-link--external"
|
<a rel="noopener noreferrer" target="_blank" href="https://www.dhs.gov/freedom-information-act-foia" class="usa-identifier__required-link usa-link usa-link--external"
|
||||||
>FOIA requests</a
|
>FOIA requests</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
|
@ -117,6 +117,6 @@
|
||||||
<div class="usa-identifier__usagov-description">
|
<div class="usa-identifier__usagov-description">
|
||||||
Looking for U.S. government information and services?
|
Looking for U.S. government information and services?
|
||||||
</div>
|
</div>
|
||||||
<a href="https://www.usa.gov/" class="usa-link usa-link--external">Visit USA.gov</a>
|
<a rel="noopener noreferrer" target="_blank" href="https://www.usa.gov/" class="usa-link usa-link--external">Visit USA.gov</a>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -37,7 +37,7 @@ error messages, if necessary.
|
||||||
{% with link_index=sublabel_text|find_index:link_text %}
|
{% with link_index=sublabel_text|find_index:link_text %}
|
||||||
{{ sublabel_text|slice:link_index }}
|
{{ sublabel_text|slice:link_index }}
|
||||||
{% comment %} HTML will convert a new line into a space, resulting with a space before the fullstop in case link_text is at the end of sublabel_text, hence the unfortunate line below {% endcomment %}
|
{% comment %} HTML will convert a new line into a space, resulting with a space before the fullstop in case link_text is at the end of sublabel_text, hence the unfortunate line below {% endcomment %}
|
||||||
<a {% if target_blank == "true" %}target="_blank" {% endif %}href="{{ link_href }}">{{ link_text }}</a>{% with sublabel_part_after=sublabel_text|slice_after:link_text %}{{ sublabel_part_after }}{% endwith %}
|
<a {% if external_link == "true" %}rel="noopener noreferrer" class="usa-link usa-link--external" {% endif %}{% if target_blank == "true" %}target="_blank" {% endif %}href="{{ link_href }}">{{ link_text }}</a>{% with sublabel_part_after=sublabel_text|slice_after:link_text %}{{ sublabel_part_after }}{% endwith %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ sublabel_text }}
|
{{ sublabel_text }}
|
||||||
|
|
|
@ -55,9 +55,7 @@ def contains_checkbox(html_list):
|
||||||
|
|
||||||
@register.filter
|
@register.filter
|
||||||
def get_organization_long_name(organization_type):
|
def get_organization_long_name(organization_type):
|
||||||
organization_choices_dict = dict(
|
organization_choices_dict = dict(DomainApplication.OrganizationChoicesVerbose.choices)
|
||||||
DomainApplication.OrganizationChoicesVerbose.choices
|
|
||||||
)
|
|
||||||
long_form_type = organization_choices_dict[organization_type]
|
long_form_type = organization_choices_dict[organization_type]
|
||||||
if long_form_type is None:
|
if long_form_type is None:
|
||||||
logger.error("Organization type error, triggered by a template's custom filter")
|
logger.error("Organization type error, triggered by a template's custom filter")
|
||||||
|
|
|
@ -323,9 +323,7 @@ class AuditedAdminMockData:
|
||||||
of either DomainApplication, DomainInvitation, or DomainInformation
|
of either DomainApplication, DomainInvitation, or DomainInformation
|
||||||
based on the 'domain_type' field.
|
based on the 'domain_type' field.
|
||||||
""" # noqa
|
""" # noqa
|
||||||
common_args = self.get_common_domain_arg_dictionary(
|
common_args = self.get_common_domain_arg_dictionary(item_name, org_type, federal_type, purpose)
|
||||||
item_name, org_type, federal_type, purpose
|
|
||||||
)
|
|
||||||
full_arg_dict = None
|
full_arg_dict = None
|
||||||
match domain_type:
|
match domain_type:
|
||||||
case self.APPLICATION:
|
case self.APPLICATION:
|
||||||
|
@ -350,40 +348,22 @@ class AuditedAdminMockData:
|
||||||
)
|
)
|
||||||
return full_arg_dict
|
return full_arg_dict
|
||||||
|
|
||||||
def create_full_dummy_domain_application(
|
def create_full_dummy_domain_application(self, item_name, status=DomainApplication.STARTED):
|
||||||
self, item_name, status=DomainApplication.STARTED
|
|
||||||
):
|
|
||||||
"""Creates a dummy domain application object"""
|
"""Creates a dummy domain application object"""
|
||||||
domain_application_kwargs = self.dummy_kwarg_boilerplate(
|
domain_application_kwargs = self.dummy_kwarg_boilerplate(self.APPLICATION, item_name, status)
|
||||||
self.APPLICATION, item_name, status
|
application = DomainApplication.objects.get_or_create(**domain_application_kwargs)[0]
|
||||||
)
|
|
||||||
application = DomainApplication.objects.get_or_create(
|
|
||||||
**domain_application_kwargs
|
|
||||||
)[0]
|
|
||||||
return application
|
return application
|
||||||
|
|
||||||
def create_full_dummy_domain_information(
|
def create_full_dummy_domain_information(self, item_name, status=DomainApplication.STARTED):
|
||||||
self, item_name, status=DomainApplication.STARTED
|
|
||||||
):
|
|
||||||
"""Creates a dummy domain information object"""
|
"""Creates a dummy domain information object"""
|
||||||
domain_application_kwargs = self.dummy_kwarg_boilerplate(
|
domain_application_kwargs = self.dummy_kwarg_boilerplate(self.INFORMATION, item_name, status)
|
||||||
self.INFORMATION, item_name, status
|
application = DomainInformation.objects.get_or_create(**domain_application_kwargs)[0]
|
||||||
)
|
|
||||||
application = DomainInformation.objects.get_or_create(
|
|
||||||
**domain_application_kwargs
|
|
||||||
)[0]
|
|
||||||
return application
|
return application
|
||||||
|
|
||||||
def create_full_dummy_domain_invitation(
|
def create_full_dummy_domain_invitation(self, item_name, status=DomainApplication.STARTED):
|
||||||
self, item_name, status=DomainApplication.STARTED
|
|
||||||
):
|
|
||||||
"""Creates a dummy domain invitation object"""
|
"""Creates a dummy domain invitation object"""
|
||||||
domain_application_kwargs = self.dummy_kwarg_boilerplate(
|
domain_application_kwargs = self.dummy_kwarg_boilerplate(self.INVITATION, item_name, status)
|
||||||
self.INVITATION, item_name, status
|
application = DomainInvitation.objects.get_or_create(**domain_application_kwargs)[0]
|
||||||
)
|
|
||||||
application = DomainInvitation.objects.get_or_create(
|
|
||||||
**domain_application_kwargs
|
|
||||||
)[0]
|
|
||||||
|
|
||||||
return application
|
return application
|
||||||
|
|
||||||
|
@ -400,17 +380,11 @@ class AuditedAdminMockData:
|
||||||
application = None
|
application = None
|
||||||
match domain_type:
|
match domain_type:
|
||||||
case self.APPLICATION:
|
case self.APPLICATION:
|
||||||
application = self.create_full_dummy_domain_application(
|
application = self.create_full_dummy_domain_application(item_name, status)
|
||||||
item_name, status
|
|
||||||
)
|
|
||||||
case self.INVITATION:
|
case self.INVITATION:
|
||||||
application = self.create_full_dummy_domain_invitation(
|
application = self.create_full_dummy_domain_invitation(item_name, status)
|
||||||
item_name, status
|
|
||||||
)
|
|
||||||
case self.INFORMATION:
|
case self.INFORMATION:
|
||||||
application = self.create_full_dummy_domain_information(
|
application = self.create_full_dummy_domain_information(item_name, status)
|
||||||
item_name, status
|
|
||||||
)
|
|
||||||
case _:
|
case _:
|
||||||
raise ValueError("Invalid domain_type, must conform to given constants")
|
raise ValueError("Invalid domain_type, must conform to given constants")
|
||||||
|
|
||||||
|
@ -459,7 +433,7 @@ def create_user():
|
||||||
p = "userpass"
|
p = "userpass"
|
||||||
user = User.objects.create_user(
|
user = User.objects.create_user(
|
||||||
username="staffuser",
|
username="staffuser",
|
||||||
email="user@example.com",
|
email="staff@example.com",
|
||||||
is_staff=True,
|
is_staff=True,
|
||||||
password=p,
|
password=p,
|
||||||
)
|
)
|
||||||
|
@ -533,9 +507,7 @@ def completed_application(
|
||||||
if has_anything_else:
|
if has_anything_else:
|
||||||
domain_application_kwargs["anything_else"] = "There is more"
|
domain_application_kwargs["anything_else"] = "There is more"
|
||||||
|
|
||||||
application, _ = DomainApplication.objects.get_or_create(
|
application, _ = DomainApplication.objects.get_or_create(**domain_application_kwargs)
|
||||||
**domain_application_kwargs
|
|
||||||
)
|
|
||||||
|
|
||||||
if has_other_contacts:
|
if has_other_contacts:
|
||||||
application.other_contacts.add(other)
|
application.other_contacts.add(other)
|
||||||
|
@ -637,11 +609,7 @@ class MockEppLib(TestCase):
|
||||||
mockDataInfoDomain = fakedEppObject(
|
mockDataInfoDomain = fakedEppObject(
|
||||||
"fakePw",
|
"fakePw",
|
||||||
cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35),
|
cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35),
|
||||||
contacts=[
|
contacts=[common.DomainContact(contact="123", type=PublicContact.ContactTypeChoices.SECURITY)],
|
||||||
common.DomainContact(
|
|
||||||
contact="123", type=PublicContact.ContactTypeChoices.SECURITY
|
|
||||||
)
|
|
||||||
],
|
|
||||||
hosts=["fake.host.com"],
|
hosts=["fake.host.com"],
|
||||||
statuses=[
|
statuses=[
|
||||||
common.Status(state="serverTransferProhibited", description="", lang="en"),
|
common.Status(state="serverTransferProhibited", description="", lang="en"),
|
||||||
|
@ -711,21 +679,11 @@ class MockEppLib(TestCase):
|
||||||
mockDefaultTechnicalContact = InfoDomainWithContacts.dummyInfoContactResultData(
|
mockDefaultTechnicalContact = InfoDomainWithContacts.dummyInfoContactResultData(
|
||||||
"defaultTech", "dotgov@cisa.dhs.gov"
|
"defaultTech", "dotgov@cisa.dhs.gov"
|
||||||
)
|
)
|
||||||
mockDefaultSecurityContact = InfoDomainWithContacts.dummyInfoContactResultData(
|
mockDefaultSecurityContact = InfoDomainWithContacts.dummyInfoContactResultData("defaultSec", "dotgov@cisa.dhs.gov")
|
||||||
"defaultSec", "dotgov@cisa.dhs.gov"
|
mockSecurityContact = InfoDomainWithContacts.dummyInfoContactResultData("securityContact", "security@mail.gov")
|
||||||
)
|
mockTechnicalContact = InfoDomainWithContacts.dummyInfoContactResultData("technicalContact", "tech@mail.gov")
|
||||||
mockSecurityContact = InfoDomainWithContacts.dummyInfoContactResultData(
|
mockAdministrativeContact = InfoDomainWithContacts.dummyInfoContactResultData("adminContact", "admin@mail.gov")
|
||||||
"securityContact", "security@mail.gov"
|
mockRegistrantContact = InfoDomainWithContacts.dummyInfoContactResultData("regContact", "registrant@mail.gov")
|
||||||
)
|
|
||||||
mockTechnicalContact = InfoDomainWithContacts.dummyInfoContactResultData(
|
|
||||||
"technicalContact", "tech@mail.gov"
|
|
||||||
)
|
|
||||||
mockAdministrativeContact = InfoDomainWithContacts.dummyInfoContactResultData(
|
|
||||||
"adminContact", "admin@mail.gov"
|
|
||||||
)
|
|
||||||
mockRegistrantContact = InfoDomainWithContacts.dummyInfoContactResultData(
|
|
||||||
"regContact", "registrant@mail.gov"
|
|
||||||
)
|
|
||||||
|
|
||||||
infoDomainNoContact = fakedEppObject(
|
infoDomainNoContact = fakedEppObject(
|
||||||
"security",
|
"security",
|
||||||
|
@ -765,9 +723,7 @@ class MockEppLib(TestCase):
|
||||||
addrs=[common.Ip(addr="1.2.3.4"), common.Ip(addr="2.3.4.5")],
|
addrs=[common.Ip(addr="1.2.3.4"), common.Ip(addr="2.3.4.5")],
|
||||||
)
|
)
|
||||||
|
|
||||||
mockDataHostChange = fakedEppObject(
|
mockDataHostChange = fakedEppObject("lastPw", cr_date=datetime.datetime(2023, 8, 25, 19, 45, 35))
|
||||||
"lastPw", cr_date=datetime.datetime(2023, 8, 25, 19, 45, 35)
|
|
||||||
)
|
|
||||||
addDsData1 = {
|
addDsData1 = {
|
||||||
"keyTag": 1234,
|
"keyTag": 1234,
|
||||||
"alg": 3,
|
"alg": 3,
|
||||||
|
@ -858,9 +814,7 @@ class MockEppLib(TestCase):
|
||||||
def _mockDomainName(self, _name, _avail=False):
|
def _mockDomainName(self, _name, _avail=False):
|
||||||
return MagicMock(
|
return MagicMock(
|
||||||
res_data=[
|
res_data=[
|
||||||
responses.check.CheckDomainResultData(
|
responses.check.CheckDomainResultData(name=_name, avail=_avail, reason=None),
|
||||||
name=_name, avail=_avail, reason=None
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -933,9 +887,7 @@ class MockEppLib(TestCase):
|
||||||
name = getattr(_request, "name", None)
|
name = getattr(_request, "name", None)
|
||||||
fake_nameserver = "ns1.failDelete.gov"
|
fake_nameserver = "ns1.failDelete.gov"
|
||||||
if name in fake_nameserver:
|
if name in fake_nameserver:
|
||||||
raise RegistryError(
|
raise RegistryError(code=ErrorCode.OBJECT_ASSOCIATION_PROHIBITS_OPERATION)
|
||||||
code=ErrorCode.OBJECT_ASSOCIATION_PROHIBITS_OPERATION
|
|
||||||
)
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def mockInfoDomainCommands(self, _request, cleaned):
|
def mockInfoDomainCommands(self, _request, cleaned):
|
||||||
|
@ -954,9 +906,7 @@ class MockEppLib(TestCase):
|
||||||
),
|
),
|
||||||
"dnssec-none.gov": (self.mockDataInfoDomain, None),
|
"dnssec-none.gov": (self.mockDataInfoDomain, None),
|
||||||
"my-nameserver.gov": (
|
"my-nameserver.gov": (
|
||||||
self.infoDomainTwoHosts
|
self.infoDomainTwoHosts if self.mockedSendFunction.call_count == 5 else self.infoDomainNoHost,
|
||||||
if self.mockedSendFunction.call_count == 5
|
|
||||||
else self.infoDomainNoHost,
|
|
||||||
None,
|
None,
|
||||||
),
|
),
|
||||||
"nameserverwithip.gov": (self.infoDomainHasIP, None),
|
"nameserverwithip.gov": (self.infoDomainHasIP, None),
|
||||||
|
@ -969,9 +919,7 @@ class MockEppLib(TestCase):
|
||||||
}
|
}
|
||||||
|
|
||||||
# Retrieve the corresponding values from the dictionary
|
# Retrieve the corresponding values from the dictionary
|
||||||
res_data, extensions = request_mappings.get(
|
res_data, extensions = request_mappings.get(request_name, (self.mockDataInfoDomain, None))
|
||||||
request_name, (self.mockDataInfoDomain, None)
|
|
||||||
)
|
|
||||||
|
|
||||||
return MagicMock(
|
return MagicMock(
|
||||||
res_data=[res_data],
|
res_data=[res_data],
|
||||||
|
@ -1002,10 +950,7 @@ class MockEppLib(TestCase):
|
||||||
return MagicMock(res_data=[mocked_result])
|
return MagicMock(res_data=[mocked_result])
|
||||||
|
|
||||||
def mockCreateContactCommands(self, _request, cleaned):
|
def mockCreateContactCommands(self, _request, cleaned):
|
||||||
if (
|
if getattr(_request, "id", None) == "fail" and self.mockedSendFunction.call_count == 3:
|
||||||
getattr(_request, "id", None) == "fail"
|
|
||||||
and self.mockedSendFunction.call_count == 3
|
|
||||||
):
|
|
||||||
# use this for when a contact is being updated
|
# use this for when a contact is being updated
|
||||||
# sets the second send() to fail
|
# sets the second send() to fail
|
||||||
raise RegistryError(code=ErrorCode.OBJECT_EXISTS)
|
raise RegistryError(code=ErrorCode.OBJECT_EXISTS)
|
||||||
|
@ -1025,9 +970,7 @@ class MockEppLib(TestCase):
|
||||||
self.mockedSendFunction = self.mockSendPatch.start()
|
self.mockedSendFunction = self.mockSendPatch.start()
|
||||||
self.mockedSendFunction.side_effect = self.mockSend
|
self.mockedSendFunction.side_effect = self.mockSend
|
||||||
|
|
||||||
def _convertPublicContactToEpp(
|
def _convertPublicContactToEpp(self, contact: PublicContact, disclose_email=False, createContact=True):
|
||||||
self, contact: PublicContact, disclose_email=False, createContact=True
|
|
||||||
):
|
|
||||||
DF = common.DiscloseField
|
DF = common.DiscloseField
|
||||||
fields = {DF.EMAIL}
|
fields = {DF.EMAIL}
|
||||||
|
|
||||||
|
@ -1039,9 +982,7 @@ class MockEppLib(TestCase):
|
||||||
# check docs here looks like we may have more than one address field but
|
# check docs here looks like we may have more than one address field but
|
||||||
addr = common.ContactAddr(
|
addr = common.ContactAddr(
|
||||||
[
|
[
|
||||||
getattr(contact, street)
|
getattr(contact, street) for street in ["street1", "street2", "street3"] if hasattr(contact, street)
|
||||||
for street in ["street1", "street2", "street3"]
|
|
||||||
if hasattr(contact, street)
|
|
||||||
], # type: ignore
|
], # type: ignore
|
||||||
city=contact.city,
|
city=contact.city,
|
||||||
pc=contact.pc,
|
pc=contact.pc,
|
||||||
|
|
|
@ -67,9 +67,7 @@ class TestDomainAdmin(MockEppLib):
|
||||||
# for our actual application
|
# for our actual application
|
||||||
self.assertContains(response, "Federal", count=4)
|
self.assertContains(response, "Federal", count=4)
|
||||||
# This may be a bit more robust
|
# This may be a bit more robust
|
||||||
self.assertContains(
|
self.assertContains(response, '<td class="field-organization_type">Federal</td>', count=1)
|
||||||
response, '<td class="field-organization_type">Federal</td>', count=1
|
|
||||||
)
|
|
||||||
# Now let's make sure the long description does not exist
|
# Now let's make sure the long description does not exist
|
||||||
self.assertNotContains(response, "Federal: an agency of the U.S. government")
|
self.assertNotContains(response, "Federal: an agency of the U.S. government")
|
||||||
|
|
||||||
|
@ -318,9 +316,7 @@ class TestDomainApplicationAdmin(MockEppLib):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
self.site = AdminSite()
|
self.site = AdminSite()
|
||||||
self.factory = RequestFactory()
|
self.factory = RequestFactory()
|
||||||
self.admin = DomainApplicationAdmin(
|
self.admin = DomainApplicationAdmin(model=DomainApplication, admin_site=self.site)
|
||||||
model=DomainApplication, admin_site=self.site
|
|
||||||
)
|
|
||||||
self.superuser = create_superuser()
|
self.superuser = create_superuser()
|
||||||
self.staffuser = create_user()
|
self.staffuser = create_user()
|
||||||
|
|
||||||
|
@ -335,9 +331,7 @@ class TestDomainApplicationAdmin(MockEppLib):
|
||||||
# for our actual application
|
# for our actual application
|
||||||
self.assertContains(response, "Federal", count=4)
|
self.assertContains(response, "Federal", count=4)
|
||||||
# This may be a bit more robust
|
# This may be a bit more robust
|
||||||
self.assertContains(
|
self.assertContains(response, '<td class="field-organization_type">Federal</td>', count=1)
|
||||||
response, '<td class="field-organization_type">Federal</td>', count=1
|
|
||||||
)
|
|
||||||
# Now let's make sure the long description does not exist
|
# Now let's make sure the long description does not exist
|
||||||
self.assertNotContains(response, "Federal: an agency of the U.S. government")
|
self.assertNotContains(response, "Federal: an agency of the U.S. government")
|
||||||
|
|
||||||
|
@ -355,9 +349,7 @@ class TestDomainApplicationAdmin(MockEppLib):
|
||||||
application = completed_application()
|
application = completed_application()
|
||||||
|
|
||||||
# Create a mock request
|
# Create a mock request
|
||||||
request = self.factory.post(
|
request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk))
|
||||||
"/admin/registrar/domainapplication/{}/change/".format(application.pk)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Modify the application's property
|
# Modify the application's property
|
||||||
application.status = DomainApplication.SUBMITTED
|
application.status = DomainApplication.SUBMITTED
|
||||||
|
@ -398,9 +390,7 @@ class TestDomainApplicationAdmin(MockEppLib):
|
||||||
application = completed_application(status=DomainApplication.SUBMITTED)
|
application = completed_application(status=DomainApplication.SUBMITTED)
|
||||||
|
|
||||||
# Create a mock request
|
# Create a mock request
|
||||||
request = self.factory.post(
|
request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk))
|
||||||
"/admin/registrar/domainapplication/{}/change/".format(application.pk)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Modify the application's property
|
# Modify the application's property
|
||||||
application.status = DomainApplication.IN_REVIEW
|
application.status = DomainApplication.IN_REVIEW
|
||||||
|
@ -441,9 +431,7 @@ class TestDomainApplicationAdmin(MockEppLib):
|
||||||
application = completed_application(status=DomainApplication.IN_REVIEW)
|
application = completed_application(status=DomainApplication.IN_REVIEW)
|
||||||
|
|
||||||
# Create a mock request
|
# Create a mock request
|
||||||
request = self.factory.post(
|
request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk))
|
||||||
"/admin/registrar/domainapplication/{}/change/".format(application.pk)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Modify the application's property
|
# Modify the application's property
|
||||||
application.status = DomainApplication.APPROVED
|
application.status = DomainApplication.APPROVED
|
||||||
|
@ -479,9 +467,7 @@ class TestDomainApplicationAdmin(MockEppLib):
|
||||||
application = completed_application(status=DomainApplication.IN_REVIEW)
|
application = completed_application(status=DomainApplication.IN_REVIEW)
|
||||||
|
|
||||||
# Create a mock request
|
# Create a mock request
|
||||||
request = self.factory.post(
|
request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk))
|
||||||
"/admin/registrar/domainapplication/{}/change/".format(application.pk)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Modify the application's property
|
# Modify the application's property
|
||||||
application.status = DomainApplication.APPROVED
|
application.status = DomainApplication.APPROVED
|
||||||
|
@ -490,9 +476,7 @@ class TestDomainApplicationAdmin(MockEppLib):
|
||||||
self.admin.save_model(request, application, form=None, change=True)
|
self.admin.save_model(request, application, form=None, change=True)
|
||||||
|
|
||||||
# Test that approved domain exists and equals requested domain
|
# Test that approved domain exists and equals requested domain
|
||||||
self.assertEqual(
|
self.assertEqual(application.requested_domain.name, application.approved_domain.name)
|
||||||
application.requested_domain.name, application.approved_domain.name
|
|
||||||
)
|
|
||||||
|
|
||||||
@boto3_mocking.patching
|
@boto3_mocking.patching
|
||||||
def test_save_model_sends_action_needed_email(self):
|
def test_save_model_sends_action_needed_email(self):
|
||||||
|
@ -508,9 +492,7 @@ class TestDomainApplicationAdmin(MockEppLib):
|
||||||
application = completed_application(status=DomainApplication.IN_REVIEW)
|
application = completed_application(status=DomainApplication.IN_REVIEW)
|
||||||
|
|
||||||
# Create a mock request
|
# Create a mock request
|
||||||
request = self.factory.post(
|
request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk))
|
||||||
"/admin/registrar/domainapplication/{}/change/".format(application.pk)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Modify the application's property
|
# Modify the application's property
|
||||||
application.status = DomainApplication.ACTION_NEEDED
|
application.status = DomainApplication.ACTION_NEEDED
|
||||||
|
@ -529,10 +511,7 @@ class TestDomainApplicationAdmin(MockEppLib):
|
||||||
email_body = email_content["Simple"]["Body"]["Text"]["Data"]
|
email_body = email_content["Simple"]["Body"]["Text"]["Data"]
|
||||||
|
|
||||||
# Assert or perform other checks on the email details
|
# Assert or perform other checks on the email details
|
||||||
expected_string = (
|
expected_string = "We've identified an action needed to complete the review of your .gov domain request."
|
||||||
"We've identified an action needed to complete the "
|
|
||||||
"review of your .gov domain request."
|
|
||||||
)
|
|
||||||
self.assertEqual(from_email, settings.DEFAULT_FROM_EMAIL)
|
self.assertEqual(from_email, settings.DEFAULT_FROM_EMAIL)
|
||||||
self.assertEqual(to_email, EMAIL)
|
self.assertEqual(to_email, EMAIL)
|
||||||
self.assertIn(expected_string, email_body)
|
self.assertIn(expected_string, email_body)
|
||||||
|
@ -554,9 +533,7 @@ class TestDomainApplicationAdmin(MockEppLib):
|
||||||
application = completed_application(status=DomainApplication.IN_REVIEW)
|
application = completed_application(status=DomainApplication.IN_REVIEW)
|
||||||
|
|
||||||
# Create a mock request
|
# Create a mock request
|
||||||
request = self.factory.post(
|
request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk))
|
||||||
"/admin/registrar/domainapplication/{}/change/".format(application.pk)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Modify the application's property
|
# Modify the application's property
|
||||||
application.status = DomainApplication.REJECTED
|
application.status = DomainApplication.REJECTED
|
||||||
|
@ -592,9 +569,7 @@ class TestDomainApplicationAdmin(MockEppLib):
|
||||||
application = completed_application(status=DomainApplication.IN_REVIEW)
|
application = completed_application(status=DomainApplication.IN_REVIEW)
|
||||||
|
|
||||||
# Create a mock request
|
# Create a mock request
|
||||||
request = self.factory.post(
|
request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk))
|
||||||
"/admin/registrar/domainapplication/{}/change/".format(application.pk)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Modify the application's property
|
# Modify the application's property
|
||||||
application.status = DomainApplication.INELIGIBLE
|
application.status = DomainApplication.INELIGIBLE
|
||||||
|
@ -699,8 +674,7 @@ class TestDomainApplicationAdmin(MockEppLib):
|
||||||
# Assert that the error message was called with the correct argument
|
# Assert that the error message was called with the correct argument
|
||||||
mock_error.assert_called_once_with(
|
mock_error.assert_called_once_with(
|
||||||
request,
|
request,
|
||||||
"This action is not permitted for applications "
|
"This action is not permitted for applications with a restricted creator.",
|
||||||
+ "with a restricted creator.",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Assert that the status has not changed
|
# Assert that the status has not changed
|
||||||
|
@ -714,9 +688,7 @@ class TestDomainApplicationAdmin(MockEppLib):
|
||||||
|
|
||||||
with patch("django.contrib.messages.warning") as mock_warning:
|
with patch("django.contrib.messages.warning") as mock_warning:
|
||||||
# Create a request object with a superuser
|
# Create a request object with a superuser
|
||||||
request = self.factory.get(
|
request = self.factory.get("/admin/your_app/domainapplication/{}/change/".format(application.pk))
|
||||||
"/admin/your_app/domainapplication/{}/change/".format(application.pk)
|
|
||||||
)
|
|
||||||
request.user = self.superuser
|
request.user = self.superuser
|
||||||
|
|
||||||
self.admin.display_restricted_warning(request, application)
|
self.admin.display_restricted_warning(request, application)
|
||||||
|
@ -735,9 +707,7 @@ class TestDomainApplicationAdmin(MockEppLib):
|
||||||
application.save()
|
application.save()
|
||||||
|
|
||||||
# Create a request object with a superuser
|
# Create a request object with a superuser
|
||||||
request = self.factory.post(
|
request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk))
|
||||||
"/admin/registrar/domainapplication/{}/change/".format(application.pk)
|
|
||||||
)
|
|
||||||
request.user = self.superuser
|
request.user = self.superuser
|
||||||
|
|
||||||
# Define a custom implementation for is_active
|
# Define a custom implementation for is_active
|
||||||
|
@ -764,16 +734,12 @@ class TestDomainApplicationAdmin(MockEppLib):
|
||||||
# Create an instance of the model
|
# Create an instance of the model
|
||||||
application = completed_application(status=DomainApplication.APPROVED)
|
application = completed_application(status=DomainApplication.APPROVED)
|
||||||
domain = Domain.objects.create(name=application.requested_domain.name)
|
domain = Domain.objects.create(name=application.requested_domain.name)
|
||||||
domain_information = DomainInformation.objects.create(
|
domain_information = DomainInformation.objects.create(creator=self.superuser, domain=domain)
|
||||||
creator=self.superuser, domain=domain
|
|
||||||
)
|
|
||||||
application.approved_domain = domain
|
application.approved_domain = domain
|
||||||
application.save()
|
application.save()
|
||||||
|
|
||||||
# Create a request object with a superuser
|
# Create a request object with a superuser
|
||||||
request = self.factory.post(
|
request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk))
|
||||||
"/admin/registrar/domainapplication/{}/change/".format(application.pk)
|
|
||||||
)
|
|
||||||
request.user = self.superuser
|
request.user = self.superuser
|
||||||
|
|
||||||
# Define a custom implementation for is_active
|
# Define a custom implementation for is_active
|
||||||
|
@ -811,9 +777,7 @@ class TestDomainApplicationAdmin(MockEppLib):
|
||||||
application.save()
|
application.save()
|
||||||
|
|
||||||
# Create a request object with a superuser
|
# Create a request object with a superuser
|
||||||
request = self.factory.post(
|
request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk))
|
||||||
"/admin/registrar/domainapplication/{}/change/".format(application.pk)
|
|
||||||
)
|
|
||||||
request.user = self.superuser
|
request.user = self.superuser
|
||||||
|
|
||||||
# Define a custom implementation for is_active
|
# Define a custom implementation for is_active
|
||||||
|
@ -840,16 +804,12 @@ class TestDomainApplicationAdmin(MockEppLib):
|
||||||
# Create an instance of the model
|
# Create an instance of the model
|
||||||
application = completed_application(status=DomainApplication.APPROVED)
|
application = completed_application(status=DomainApplication.APPROVED)
|
||||||
domain = Domain.objects.create(name=application.requested_domain.name)
|
domain = Domain.objects.create(name=application.requested_domain.name)
|
||||||
domain_information = DomainInformation.objects.create(
|
domain_information = DomainInformation.objects.create(creator=self.superuser, domain=domain)
|
||||||
creator=self.superuser, domain=domain
|
|
||||||
)
|
|
||||||
application.approved_domain = domain
|
application.approved_domain = domain
|
||||||
application.save()
|
application.save()
|
||||||
|
|
||||||
# Create a request object with a superuser
|
# Create a request object with a superuser
|
||||||
request = self.factory.post(
|
request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk))
|
||||||
"/admin/registrar/domainapplication/{}/change/".format(application.pk)
|
|
||||||
)
|
|
||||||
request.user = self.superuser
|
request.user = self.superuser
|
||||||
|
|
||||||
# Define a custom implementation for is_active
|
# Define a custom implementation for is_active
|
||||||
|
@ -1012,17 +972,13 @@ class AuditedAdminTest(TestCase):
|
||||||
self.factory = RequestFactory()
|
self.factory = RequestFactory()
|
||||||
self.client = Client(HTTP_HOST="localhost:8080")
|
self.client = Client(HTTP_HOST="localhost:8080")
|
||||||
|
|
||||||
def order_by_desired_field_helper(
|
def order_by_desired_field_helper(self, obj_to_sort: AuditedAdmin, request, field_name, *obj_names):
|
||||||
self, obj_to_sort: AuditedAdmin, request, field_name, *obj_names
|
|
||||||
):
|
|
||||||
formatted_sort_fields = []
|
formatted_sort_fields = []
|
||||||
for obj in obj_names:
|
for obj in obj_names:
|
||||||
formatted_sort_fields.append("{}__{}".format(field_name, obj))
|
formatted_sort_fields.append("{}__{}".format(field_name, obj))
|
||||||
|
|
||||||
ordered_list = list(
|
ordered_list = list(
|
||||||
obj_to_sort.get_queryset(request)
|
obj_to_sort.get_queryset(request).order_by(*formatted_sort_fields).values_list(*formatted_sort_fields)
|
||||||
.order_by(*formatted_sort_fields)
|
|
||||||
.values_list(*formatted_sort_fields)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return ordered_list
|
return ordered_list
|
||||||
|
@ -1040,9 +996,7 @@ class AuditedAdminTest(TestCase):
|
||||||
applications = multiple_unalphabetical_domain_objects("application")
|
applications = multiple_unalphabetical_domain_objects("application")
|
||||||
|
|
||||||
# Create a mock request
|
# Create a mock request
|
||||||
request = self.factory.post(
|
request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(applications[0].pk))
|
||||||
"/admin/registrar/domainapplication/{}/change/".format(applications[0].pk)
|
|
||||||
)
|
|
||||||
|
|
||||||
model_admin = AuditedAdmin(DomainApplication, self.site)
|
model_admin = AuditedAdmin(DomainApplication, self.site)
|
||||||
|
|
||||||
|
@ -1058,12 +1012,8 @@ class AuditedAdminTest(TestCase):
|
||||||
sorted_fields = ["first_name", "last_name"]
|
sorted_fields = ["first_name", "last_name"]
|
||||||
# We want both of these to be lists, as it is richer test wise.
|
# We want both of these to be lists, as it is richer test wise.
|
||||||
|
|
||||||
desired_order = self.order_by_desired_field_helper(
|
desired_order = self.order_by_desired_field_helper(model_admin, request, field.name, *sorted_fields)
|
||||||
model_admin, request, field.name, *sorted_fields
|
current_sort_order = list(model_admin.formfield_for_foreignkey(field, request).queryset)
|
||||||
)
|
|
||||||
current_sort_order = list(
|
|
||||||
model_admin.formfield_for_foreignkey(field, request).queryset
|
|
||||||
)
|
|
||||||
|
|
||||||
# Conforms to the same object structure as desired_order
|
# Conforms to the same object structure as desired_order
|
||||||
current_sort_order_coerced_type = []
|
current_sort_order_coerced_type = []
|
||||||
|
@ -1101,9 +1051,7 @@ class AuditedAdminTest(TestCase):
|
||||||
applications = multiple_unalphabetical_domain_objects("information")
|
applications = multiple_unalphabetical_domain_objects("information")
|
||||||
|
|
||||||
# Create a mock request
|
# Create a mock request
|
||||||
request = self.factory.post(
|
request = self.factory.post("/admin/registrar/domaininformation/{}/change/".format(applications[0].pk))
|
||||||
"/admin/registrar/domaininformation/{}/change/".format(applications[0].pk)
|
|
||||||
)
|
|
||||||
|
|
||||||
model_admin = AuditedAdmin(DomainInformation, self.site)
|
model_admin = AuditedAdmin(DomainInformation, self.site)
|
||||||
|
|
||||||
|
@ -1121,12 +1069,8 @@ class AuditedAdminTest(TestCase):
|
||||||
sorted_fields = ["first_name", "last_name"]
|
sorted_fields = ["first_name", "last_name"]
|
||||||
field_obj = field
|
field_obj = field
|
||||||
# We want both of these to be lists, as it is richer test wise.
|
# We want both of these to be lists, as it is richer test wise.
|
||||||
desired_order = self.order_by_desired_field_helper(
|
desired_order = self.order_by_desired_field_helper(model_admin, request, field_obj.name, *sorted_fields)
|
||||||
model_admin, request, field_obj.name, *sorted_fields
|
current_sort_order = list(model_admin.formfield_for_foreignkey(field_obj, request).queryset)
|
||||||
)
|
|
||||||
current_sort_order = list(
|
|
||||||
model_admin.formfield_for_foreignkey(field_obj, request).queryset
|
|
||||||
)
|
|
||||||
|
|
||||||
# Conforms to the same object structure as desired_order
|
# Conforms to the same object structure as desired_order
|
||||||
current_sort_order_coerced_type = []
|
current_sort_order_coerced_type = []
|
||||||
|
@ -1144,9 +1088,7 @@ class AuditedAdminTest(TestCase):
|
||||||
elif field_obj == DomainInformation.domain_application.field:
|
elif field_obj == DomainInformation.domain_application.field:
|
||||||
first = obj.requested_domain.name
|
first = obj.requested_domain.name
|
||||||
|
|
||||||
name_tuple = self.coerced_fk_field_helper(
|
name_tuple = self.coerced_fk_field_helper(first, last, field_obj.name, ":")
|
||||||
first, last, field_obj.name, ":"
|
|
||||||
)
|
|
||||||
if name_tuple is not None:
|
if name_tuple is not None:
|
||||||
current_sort_order_coerced_type.append(name_tuple)
|
current_sort_order_coerced_type.append(name_tuple)
|
||||||
|
|
||||||
|
@ -1163,9 +1105,7 @@ class AuditedAdminTest(TestCase):
|
||||||
applications = multiple_unalphabetical_domain_objects("invitation")
|
applications = multiple_unalphabetical_domain_objects("invitation")
|
||||||
|
|
||||||
# Create a mock request
|
# Create a mock request
|
||||||
request = self.factory.post(
|
request = self.factory.post("/admin/registrar/domaininvitation/{}/change/".format(applications[0].pk))
|
||||||
"/admin/registrar/domaininvitation/{}/change/".format(applications[0].pk)
|
|
||||||
)
|
|
||||||
|
|
||||||
model_admin = AuditedAdmin(DomainInvitation, self.site)
|
model_admin = AuditedAdmin(DomainInvitation, self.site)
|
||||||
|
|
||||||
|
@ -1177,12 +1117,8 @@ class AuditedAdminTest(TestCase):
|
||||||
sorted_fields = ["name"]
|
sorted_fields = ["name"]
|
||||||
# We want both of these to be lists, as it is richer test wise.
|
# We want both of these to be lists, as it is richer test wise.
|
||||||
|
|
||||||
desired_order = self.order_by_desired_field_helper(
|
desired_order = self.order_by_desired_field_helper(model_admin, request, field.name, *sorted_fields)
|
||||||
model_admin, request, field.name, *sorted_fields
|
current_sort_order = list(model_admin.formfield_for_foreignkey(field, request).queryset)
|
||||||
)
|
|
||||||
current_sort_order = list(
|
|
||||||
model_admin.formfield_for_foreignkey(field, request).queryset
|
|
||||||
)
|
|
||||||
|
|
||||||
# Conforms to the same object structure as desired_order
|
# Conforms to the same object structure as desired_order
|
||||||
current_sort_order_coerced_type = []
|
current_sort_order_coerced_type = []
|
||||||
|
@ -1204,9 +1140,7 @@ class AuditedAdminTest(TestCase):
|
||||||
"{} is not ordered alphabetically".format(field.name),
|
"{} is not ordered alphabetically".format(field.name),
|
||||||
)
|
)
|
||||||
|
|
||||||
def coerced_fk_field_helper(
|
def coerced_fk_field_helper(self, first_name, last_name, field_name, queryset_shorthand):
|
||||||
self, first_name, last_name, field_name, queryset_shorthand
|
|
||||||
):
|
|
||||||
"""Handles edge cases for test cases"""
|
"""Handles edge cases for test cases"""
|
||||||
if first_name is None:
|
if first_name is None:
|
||||||
raise ValueError("Invalid value for first_name, must be defined")
|
raise ValueError("Invalid value for first_name, must be defined")
|
||||||
|
@ -1256,9 +1190,7 @@ class DomainSessionVariableTest(TestCase):
|
||||||
p = "adminpass"
|
p = "adminpass"
|
||||||
self.client.login(username="superuser", password=p)
|
self.client.login(username="superuser", password=p)
|
||||||
|
|
||||||
dummy_domain_information: Domain = generic_domain_object(
|
dummy_domain_information: Domain = generic_domain_object("information", "session")
|
||||||
"information", "session"
|
|
||||||
)
|
|
||||||
dummy_domain_information.domain.pk = 1
|
dummy_domain_information.domain.pk = 1
|
||||||
|
|
||||||
request = self.get_factory_post_edit_domain(dummy_domain_information.domain.pk)
|
request = self.get_factory_post_edit_domain(dummy_domain_information.domain.pk)
|
||||||
|
@ -1275,9 +1207,7 @@ class DomainSessionVariableTest(TestCase):
|
||||||
dummy_domain_information = generic_domain_object("information", "session")
|
dummy_domain_information = generic_domain_object("information", "session")
|
||||||
request = self.get_factory_post_edit_domain(dummy_domain_information.domain.pk)
|
request = self.get_factory_post_edit_domain(dummy_domain_information.domain.pk)
|
||||||
|
|
||||||
self.populate_session_values(
|
self.populate_session_values(request, dummy_domain_information.domain, preload_bad_data=True)
|
||||||
request, dummy_domain_information.domain, preload_bad_data=True
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertEqual(request.session["analyst_action"], "edit")
|
self.assertEqual(request.session["analyst_action"], "edit")
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
|
@ -1291,9 +1221,7 @@ class DomainSessionVariableTest(TestCase):
|
||||||
p = "adminpass"
|
p = "adminpass"
|
||||||
self.client.login(username="superuser", password=p)
|
self.client.login(username="superuser", password=p)
|
||||||
|
|
||||||
dummy_domain_information_list = multiple_unalphabetical_domain_objects(
|
dummy_domain_information_list = multiple_unalphabetical_domain_objects("information")
|
||||||
"information"
|
|
||||||
)
|
|
||||||
for item in dummy_domain_information_list:
|
for item in dummy_domain_information_list:
|
||||||
request = self.get_factory_post_edit_domain(item.domain.pk)
|
request = self.get_factory_post_edit_domain(item.domain.pk)
|
||||||
self.populate_session_values(request, item.domain)
|
self.populate_session_values(request, item.domain)
|
||||||
|
|
|
@ -42,10 +42,7 @@ class TestFormValidation(MockEppLib):
|
||||||
form = CurrentSitesForm(data={"website": "nah"})
|
form = CurrentSitesForm(data={"website": "nah"})
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
form.errors["website"],
|
form.errors["website"],
|
||||||
[
|
["Enter your organization's current website in the required format, like www.city.com."],
|
||||||
"Enter your organization's current website in the required format, like"
|
|
||||||
" www.city.com."
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_website_valid(self):
|
def test_website_valid(self):
|
||||||
|
@ -82,10 +79,7 @@ class TestFormValidation(MockEppLib):
|
||||||
form = DotGovDomainForm(data={"requested_domain": "underscores_forever"})
|
form = DotGovDomainForm(data={"requested_domain": "underscores_forever"})
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
form.errors["requested_domain"],
|
form.errors["requested_domain"],
|
||||||
[
|
["Enter a domain using only letters, numbers, or hyphens (though we don't recommend using hyphens)."],
|
||||||
"Enter a domain using only letters, numbers, or hyphens (though we"
|
|
||||||
" don't recommend using hyphens)."
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_authorizing_official_email_invalid(self):
|
def test_authorizing_official_email_invalid(self):
|
||||||
|
@ -187,9 +181,7 @@ class TestFormValidation(MockEppLib):
|
||||||
def test_authorizing_official_phone_invalid(self):
|
def test_authorizing_official_phone_invalid(self):
|
||||||
"""Must be a valid phone number."""
|
"""Must be a valid phone number."""
|
||||||
form = AuthorizingOfficialForm(data={"phone": "boss@boss"})
|
form = AuthorizingOfficialForm(data={"phone": "boss@boss"})
|
||||||
self.assertTrue(
|
self.assertTrue(form.errors["phone"][0].startswith("Enter a valid phone number "))
|
||||||
form.errors["phone"][0].startswith("Enter a valid phone number ")
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_your_contact_email_invalid(self):
|
def test_your_contact_email_invalid(self):
|
||||||
"""must be a valid email address."""
|
"""must be a valid email address."""
|
||||||
|
@ -202,9 +194,7 @@ class TestFormValidation(MockEppLib):
|
||||||
def test_your_contact_phone_invalid(self):
|
def test_your_contact_phone_invalid(self):
|
||||||
"""Must be a valid phone number."""
|
"""Must be a valid phone number."""
|
||||||
form = YourContactForm(data={"phone": "boss@boss"})
|
form = YourContactForm(data={"phone": "boss@boss"})
|
||||||
self.assertTrue(
|
self.assertTrue(form.errors["phone"][0].startswith("Enter a valid phone number "))
|
||||||
form.errors["phone"][0].startswith("Enter a valid phone number ")
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_other_contact_email_invalid(self):
|
def test_other_contact_email_invalid(self):
|
||||||
"""must be a valid email address."""
|
"""must be a valid email address."""
|
||||||
|
@ -217,19 +207,14 @@ class TestFormValidation(MockEppLib):
|
||||||
def test_other_contact_phone_invalid(self):
|
def test_other_contact_phone_invalid(self):
|
||||||
"""Must be a valid phone number."""
|
"""Must be a valid phone number."""
|
||||||
form = OtherContactsForm(data={"phone": "boss@boss"})
|
form = OtherContactsForm(data={"phone": "boss@boss"})
|
||||||
self.assertTrue(
|
self.assertTrue(form.errors["phone"][0].startswith("Enter a valid phone number "))
|
||||||
form.errors["phone"][0].startswith("Enter a valid phone number ")
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_requirements_form_blank(self):
|
def test_requirements_form_blank(self):
|
||||||
"""Requirements box unchecked is an error."""
|
"""Requirements box unchecked is an error."""
|
||||||
form = RequirementsForm(data={})
|
form = RequirementsForm(data={})
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
form.errors["is_policy_acknowledged"],
|
form.errors["is_policy_acknowledged"],
|
||||||
[
|
["Check the box if you read and agree to the requirements for operating .gov domains."],
|
||||||
"Check the box if you read and agree to the requirements for"
|
|
||||||
" operating .gov domains."
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_requirements_form_unchecked(self):
|
def test_requirements_form_unchecked(self):
|
||||||
|
@ -237,23 +222,13 @@ class TestFormValidation(MockEppLib):
|
||||||
form = RequirementsForm(data={"is_policy_acknowledged": False})
|
form = RequirementsForm(data={"is_policy_acknowledged": False})
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
form.errors["is_policy_acknowledged"],
|
form.errors["is_policy_acknowledged"],
|
||||||
[
|
["Check the box if you read and agree to the requirements for operating .gov domains."],
|
||||||
"Check the box if you read and agree to the requirements for"
|
|
||||||
" operating .gov domains."
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_tribal_government_unrecognized(self):
|
def test_tribal_government_unrecognized(self):
|
||||||
"""Not state or federally recognized is an error."""
|
"""Not state or federally recognized is an error."""
|
||||||
form = TribalGovernmentForm(
|
form = TribalGovernmentForm(data={"state_recognized": False, "federally_recognized": False})
|
||||||
data={"state_recognized": False, "federally_recognized": False}
|
self.assertTrue(any("tell us more about your tribe" in error for error in form.non_field_errors()))
|
||||||
)
|
|
||||||
self.assertTrue(
|
|
||||||
any(
|
|
||||||
"tell us more about your tribe" in error
|
|
||||||
for error in form.non_field_errors()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TestContactForm(TestCase):
|
class TestContactForm(TestCase):
|
||||||
|
|
|
@ -22,14 +22,10 @@ class TestGroups(TestCase):
|
||||||
full_access_group = UserGroup.objects.get(name="full_access_group")
|
full_access_group = UserGroup.objects.get(name="full_access_group")
|
||||||
|
|
||||||
# Assert that the cisa_analysts_group exists in the database
|
# Assert that the cisa_analysts_group exists in the database
|
||||||
self.assertQuerysetEqual(
|
self.assertQuerysetEqual(UserGroup.objects.filter(name="cisa_analysts_group"), [cisa_analysts_group])
|
||||||
UserGroup.objects.filter(name="cisa_analysts_group"), [cisa_analysts_group]
|
|
||||||
)
|
|
||||||
|
|
||||||
# Assert that the full_access_group exists in the database
|
# Assert that the full_access_group exists in the database
|
||||||
self.assertQuerysetEqual(
|
self.assertQuerysetEqual(UserGroup.objects.filter(name="full_access_group"), [full_access_group])
|
||||||
UserGroup.objects.filter(name="full_access_group"), [full_access_group]
|
|
||||||
)
|
|
||||||
|
|
||||||
# Test permissions for cisa_analysts_group
|
# Test permissions for cisa_analysts_group
|
||||||
# Verifies permission data migrations ran as expected.
|
# Verifies permission data migrations ran as expected.
|
||||||
|
|
|
@ -104,9 +104,7 @@ class TestDomainApplication(TestCase):
|
||||||
def test_status_fsm_submit_succeed(self):
|
def test_status_fsm_submit_succeed(self):
|
||||||
user, _ = User.objects.get_or_create()
|
user, _ = User.objects.get_or_create()
|
||||||
site = DraftDomain.objects.create(name="igorville.gov")
|
site = DraftDomain.objects.create(name="igorville.gov")
|
||||||
application = DomainApplication.objects.create(
|
application = DomainApplication.objects.create(creator=user, requested_domain=site)
|
||||||
creator=user, requested_domain=site
|
|
||||||
)
|
|
||||||
# no submitter email so this emits a log warning
|
# no submitter email so this emits a log warning
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
application.submit()
|
application.submit()
|
||||||
|
@ -543,9 +541,7 @@ class TestPermissions(TestCase):
|
||||||
def test_approval_creates_role(self):
|
def test_approval_creates_role(self):
|
||||||
draft_domain, _ = DraftDomain.objects.get_or_create(name="igorville.gov")
|
draft_domain, _ = DraftDomain.objects.get_or_create(name="igorville.gov")
|
||||||
user, _ = User.objects.get_or_create()
|
user, _ = User.objects.get_or_create()
|
||||||
application = DomainApplication.objects.create(
|
application = DomainApplication.objects.create(creator=user, requested_domain=draft_domain)
|
||||||
creator=user, requested_domain=draft_domain
|
|
||||||
)
|
|
||||||
# skip using the submit method
|
# skip using the submit method
|
||||||
application.status = DomainApplication.SUBMITTED
|
application.status = DomainApplication.SUBMITTED
|
||||||
application.approve()
|
application.approve()
|
||||||
|
@ -562,9 +558,7 @@ class TestDomainInfo(TestCase):
|
||||||
def test_approval_creates_info(self):
|
def test_approval_creates_info(self):
|
||||||
draft_domain, _ = DraftDomain.objects.get_or_create(name="igorville.gov")
|
draft_domain, _ = DraftDomain.objects.get_or_create(name="igorville.gov")
|
||||||
user, _ = User.objects.get_or_create()
|
user, _ = User.objects.get_or_create()
|
||||||
application = DomainApplication.objects.create(
|
application = DomainApplication.objects.create(creator=user, requested_domain=draft_domain)
|
||||||
creator=user, requested_domain=draft_domain
|
|
||||||
)
|
|
||||||
# skip using the submit method
|
# skip using the submit method
|
||||||
application.status = DomainApplication.SUBMITTED
|
application.status = DomainApplication.SUBMITTED
|
||||||
application.approve()
|
application.approve()
|
||||||
|
@ -581,9 +575,7 @@ class TestInvitations(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.domain, _ = Domain.objects.get_or_create(name="igorville.gov")
|
self.domain, _ = Domain.objects.get_or_create(name="igorville.gov")
|
||||||
self.email = "mayor@igorville.gov"
|
self.email = "mayor@igorville.gov"
|
||||||
self.invitation, _ = DomainInvitation.objects.get_or_create(
|
self.invitation, _ = DomainInvitation.objects.get_or_create(email=self.email, domain=self.domain)
|
||||||
email=self.email, domain=self.domain
|
|
||||||
)
|
|
||||||
self.user, _ = User.objects.get_or_create(email=self.email)
|
self.user, _ = User.objects.get_or_create(email=self.email)
|
||||||
|
|
||||||
# clean out the roles each time
|
# clean out the roles each time
|
||||||
|
@ -601,17 +593,15 @@ class TestInvitations(TestCase):
|
||||||
|
|
||||||
def test_retrieve_existing_role_no_error(self):
|
def test_retrieve_existing_role_no_error(self):
|
||||||
# make the overlapping role
|
# make the overlapping role
|
||||||
UserDomainRole.objects.get_or_create(
|
UserDomainRole.objects.get_or_create(user=self.user, domain=self.domain, role=UserDomainRole.Roles.MANAGER)
|
||||||
user=self.user, domain=self.domain, role=UserDomainRole.Roles.MANAGER
|
|
||||||
)
|
|
||||||
# this is not an error but does produce a console warning
|
# this is not an error but does produce a console warning
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
self.invitation.retrieve()
|
self.invitation.retrieve()
|
||||||
self.assertEqual(self.invitation.status, DomainInvitation.RETRIEVED)
|
self.assertEqual(self.invitation.status, DomainInvitation.RETRIEVED)
|
||||||
|
|
||||||
def test_retrieve_on_first_login(self):
|
def test_retrieve_on_each_login(self):
|
||||||
"""A new user's first_login callback retrieves their invitations."""
|
"""A user's authenticate on_each_login callback retrieves their invitations."""
|
||||||
self.user.first_login()
|
self.user.on_each_login()
|
||||||
self.assertTrue(UserDomainRole.objects.get(user=self.user, domain=self.domain))
|
self.assertTrue(UserDomainRole.objects.get(user=self.user, domain=self.domain))
|
||||||
|
|
||||||
|
|
||||||
|
@ -627,9 +617,7 @@ class TestUser(TestCase):
|
||||||
# clean out the roles each time
|
# clean out the roles each time
|
||||||
UserDomainRole.objects.all().delete()
|
UserDomainRole.objects.all().delete()
|
||||||
|
|
||||||
TransitionDomain.objects.get_or_create(
|
TransitionDomain.objects.get_or_create(username="mayor@igorville.gov", domain_name=self.domain_name)
|
||||||
username="mayor@igorville.gov", domain_name=self.domain_name
|
|
||||||
)
|
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
super().tearDown()
|
super().tearDown()
|
||||||
|
@ -640,19 +628,19 @@ class TestUser(TestCase):
|
||||||
User.objects.all().delete()
|
User.objects.all().delete()
|
||||||
|
|
||||||
def test_check_transition_domains_on_login(self):
|
def test_check_transition_domains_on_login(self):
|
||||||
"""A new user's first_login callback checks transition domains.
|
"""A user's on_each_login callback checks transition domains.
|
||||||
Makes DomainInformation object."""
|
Makes DomainInformation object."""
|
||||||
self.domain, _ = Domain.objects.get_or_create(name=self.domain_name)
|
self.domain, _ = Domain.objects.get_or_create(name=self.domain_name)
|
||||||
|
|
||||||
self.user.first_login()
|
self.user.on_each_login()
|
||||||
self.assertTrue(DomainInformation.objects.get(domain=self.domain))
|
self.assertTrue(DomainInformation.objects.get(domain=self.domain))
|
||||||
|
|
||||||
def test_check_transition_domains_without_domains_on_login(self):
|
def test_check_transition_domains_without_domains_on_login(self):
|
||||||
"""A new user's first_login callback checks transition domains.
|
"""A user's on_each_login callback checks transition domains.
|
||||||
This test makes sure that in the event a domain does not exist
|
This test makes sure that in the event a domain does not exist
|
||||||
for a given transition domain, both a domain and domain invitation
|
for a given transition domain, both a domain and domain invitation
|
||||||
are created."""
|
are created."""
|
||||||
self.user.first_login()
|
self.user.on_each_login()
|
||||||
self.assertTrue(Domain.objects.get(name=self.domain_name))
|
self.assertTrue(Domain.objects.get(name=self.domain_name))
|
||||||
|
|
||||||
domain = Domain.objects.get(name=self.domain_name)
|
domain = Domain.objects.get(name=self.domain_name)
|
||||||
|
|
|
@ -84,9 +84,7 @@ class TestDomainCache(MockEppLib):
|
||||||
|
|
||||||
# send was only called once & not on the second getter call
|
# send was only called once & not on the second getter call
|
||||||
expectedCalls = [
|
expectedCalls = [
|
||||||
call(
|
call(commands.InfoDomain(name="igorville.gov", auth_info=None), cleaned=True),
|
||||||
commands.InfoDomain(name="igorville.gov", auth_info=None), cleaned=True
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
self.mockedSendFunction.assert_has_calls(expectedCalls)
|
self.mockedSendFunction.assert_has_calls(expectedCalls)
|
||||||
|
@ -261,9 +259,7 @@ class TestDomainCreation(MockEppLib):
|
||||||
"""
|
"""
|
||||||
draft_domain, _ = DraftDomain.objects.get_or_create(name="igorville.gov")
|
draft_domain, _ = DraftDomain.objects.get_or_create(name="igorville.gov")
|
||||||
user, _ = User.objects.get_or_create()
|
user, _ = User.objects.get_or_create()
|
||||||
application = DomainApplication.objects.create(
|
application = DomainApplication.objects.create(creator=user, requested_domain=draft_domain)
|
||||||
creator=user, requested_domain=draft_domain
|
|
||||||
)
|
|
||||||
# skip using the submit method
|
# skip using the submit method
|
||||||
application.status = DomainApplication.SUBMITTED
|
application.status = DomainApplication.SUBMITTED
|
||||||
# transition to approve state
|
# transition to approve state
|
||||||
|
@ -412,11 +408,7 @@ class TestDomainAvailable(MockEppLib):
|
||||||
|
|
||||||
def side_effect(_request, cleaned):
|
def side_effect(_request, cleaned):
|
||||||
return MagicMock(
|
return MagicMock(
|
||||||
res_data=[
|
res_data=[responses.check.CheckDomainResultData(name="available.gov", avail=True, reason=None)],
|
||||||
responses.check.CheckDomainResultData(
|
|
||||||
name="available.gov", avail=True, reason=None
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
patcher = patch("registrar.models.domain.registry.send")
|
patcher = patch("registrar.models.domain.registry.send")
|
||||||
|
@ -449,11 +441,7 @@ class TestDomainAvailable(MockEppLib):
|
||||||
|
|
||||||
def side_effect(_request, cleaned):
|
def side_effect(_request, cleaned):
|
||||||
return MagicMock(
|
return MagicMock(
|
||||||
res_data=[
|
res_data=[responses.check.CheckDomainResultData(name="unavailable.gov", avail=False, reason="In Use")],
|
||||||
responses.check.CheckDomainResultData(
|
|
||||||
name="unavailable.gov", avail=False, reason="In Use"
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
patcher = patch("registrar.models.domain.registry.send")
|
patcher = patch("registrar.models.domain.registry.send")
|
||||||
|
@ -556,16 +544,10 @@ class TestRegistrantContacts(MockEppLib):
|
||||||
).registry_id
|
).registry_id
|
||||||
|
|
||||||
expectedSecContact.registry_id = id
|
expectedSecContact.registry_id = id
|
||||||
expectedCreateCommand = self._convertPublicContactToEpp(
|
expectedCreateCommand = self._convertPublicContactToEpp(expectedSecContact, disclose_email=False)
|
||||||
expectedSecContact, disclose_email=False
|
|
||||||
)
|
|
||||||
expectedUpdateDomain = commands.UpdateDomain(
|
expectedUpdateDomain = commands.UpdateDomain(
|
||||||
name=self.domain.name,
|
name=self.domain.name,
|
||||||
add=[
|
add=[common.DomainContact(contact=expectedSecContact.registry_id, type="security")],
|
||||||
common.DomainContact(
|
|
||||||
contact=expectedSecContact.registry_id, type="security"
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True)
|
self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True)
|
||||||
|
@ -594,17 +576,11 @@ class TestRegistrantContacts(MockEppLib):
|
||||||
expectedSecContact.save()
|
expectedSecContact.save()
|
||||||
|
|
||||||
# no longer the default email it should be disclosed
|
# no longer the default email it should be disclosed
|
||||||
expectedCreateCommand = self._convertPublicContactToEpp(
|
expectedCreateCommand = self._convertPublicContactToEpp(expectedSecContact, disclose_email=True)
|
||||||
expectedSecContact, disclose_email=True
|
|
||||||
)
|
|
||||||
|
|
||||||
expectedUpdateDomain = commands.UpdateDomain(
|
expectedUpdateDomain = commands.UpdateDomain(
|
||||||
name=self.domain.name,
|
name=self.domain.name,
|
||||||
add=[
|
add=[common.DomainContact(contact=expectedSecContact.registry_id, type="security")],
|
||||||
common.DomainContact(
|
|
||||||
contact=expectedSecContact.registry_id, type="security"
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# check that send has triggered the create command for the contact
|
# check that send has triggered the create command for the contact
|
||||||
|
@ -630,17 +606,11 @@ class TestRegistrantContacts(MockEppLib):
|
||||||
|
|
||||||
self.domain.security_contact = security_contact
|
self.domain.security_contact = security_contact
|
||||||
|
|
||||||
expectedCreateCommand = self._convertPublicContactToEpp(
|
expectedCreateCommand = self._convertPublicContactToEpp(security_contact, disclose_email=False)
|
||||||
security_contact, disclose_email=False
|
|
||||||
)
|
|
||||||
|
|
||||||
expectedUpdateDomain = commands.UpdateDomain(
|
expectedUpdateDomain = commands.UpdateDomain(
|
||||||
name=self.domain.name,
|
name=self.domain.name,
|
||||||
add=[
|
add=[common.DomainContact(contact=security_contact.registry_id, type="security")],
|
||||||
common.DomainContact(
|
|
||||||
contact=security_contact.registry_id, type="security"
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
expected_calls = [
|
expected_calls = [
|
||||||
call(expectedCreateCommand, cleaned=True),
|
call(expectedCreateCommand, cleaned=True),
|
||||||
|
@ -671,38 +641,26 @@ class TestRegistrantContacts(MockEppLib):
|
||||||
new_contact.email = ""
|
new_contact.email = ""
|
||||||
self.domain.security_contact = new_contact
|
self.domain.security_contact = new_contact
|
||||||
|
|
||||||
firstCreateContactCall = self._convertPublicContactToEpp(
|
firstCreateContactCall = self._convertPublicContactToEpp(old_contact, disclose_email=True)
|
||||||
old_contact, disclose_email=True
|
|
||||||
)
|
|
||||||
updateDomainAddCall = commands.UpdateDomain(
|
updateDomainAddCall = commands.UpdateDomain(
|
||||||
name=self.domain.name,
|
name=self.domain.name,
|
||||||
add=[
|
add=[common.DomainContact(contact=old_contact.registry_id, type="security")],
|
||||||
common.DomainContact(contact=old_contact.registry_id, type="security")
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
PublicContact.objects.filter(domain=self.domain).get().email,
|
PublicContact.objects.filter(domain=self.domain).get().email,
|
||||||
PublicContact.get_default_security().email,
|
PublicContact.get_default_security().email,
|
||||||
)
|
)
|
||||||
# this one triggers the fail
|
# this one triggers the fail
|
||||||
secondCreateContact = self._convertPublicContactToEpp(
|
secondCreateContact = self._convertPublicContactToEpp(new_contact, disclose_email=True)
|
||||||
new_contact, disclose_email=True
|
|
||||||
)
|
|
||||||
updateDomainRemCall = commands.UpdateDomain(
|
updateDomainRemCall = commands.UpdateDomain(
|
||||||
name=self.domain.name,
|
name=self.domain.name,
|
||||||
rem=[
|
rem=[common.DomainContact(contact=old_contact.registry_id, type="security")],
|
||||||
common.DomainContact(contact=old_contact.registry_id, type="security")
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
defaultSecID = (
|
defaultSecID = PublicContact.objects.filter(domain=self.domain).get().registry_id
|
||||||
PublicContact.objects.filter(domain=self.domain).get().registry_id
|
|
||||||
)
|
|
||||||
default_security = PublicContact.get_default_security()
|
default_security = PublicContact.get_default_security()
|
||||||
default_security.registry_id = defaultSecID
|
default_security.registry_id = defaultSecID
|
||||||
createDefaultContact = self._convertPublicContactToEpp(
|
createDefaultContact = self._convertPublicContactToEpp(default_security, disclose_email=False)
|
||||||
default_security, disclose_email=False
|
|
||||||
)
|
|
||||||
updateDomainWDefault = commands.UpdateDomain(
|
updateDomainWDefault = commands.UpdateDomain(
|
||||||
name=self.domain.name,
|
name=self.domain.name,
|
||||||
add=[common.DomainContact(contact=defaultSecID, type="security")],
|
add=[common.DomainContact(contact=defaultSecID, type="security")],
|
||||||
|
@ -731,26 +689,16 @@ class TestRegistrantContacts(MockEppLib):
|
||||||
security_contact.email = "originalUserEmail@gmail.com"
|
security_contact.email = "originalUserEmail@gmail.com"
|
||||||
security_contact.registry_id = "fail"
|
security_contact.registry_id = "fail"
|
||||||
security_contact.save()
|
security_contact.save()
|
||||||
expectedCreateCommand = self._convertPublicContactToEpp(
|
expectedCreateCommand = self._convertPublicContactToEpp(security_contact, disclose_email=True)
|
||||||
security_contact, disclose_email=True
|
|
||||||
)
|
|
||||||
|
|
||||||
expectedUpdateDomain = commands.UpdateDomain(
|
expectedUpdateDomain = commands.UpdateDomain(
|
||||||
name=self.domain.name,
|
name=self.domain.name,
|
||||||
add=[
|
add=[common.DomainContact(contact=security_contact.registry_id, type="security")],
|
||||||
common.DomainContact(
|
|
||||||
contact=security_contact.registry_id, type="security"
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
security_contact.email = "changedEmail@email.com"
|
security_contact.email = "changedEmail@email.com"
|
||||||
security_contact.save()
|
security_contact.save()
|
||||||
expectedSecondCreateCommand = self._convertPublicContactToEpp(
|
expectedSecondCreateCommand = self._convertPublicContactToEpp(security_contact, disclose_email=True)
|
||||||
security_contact, disclose_email=True
|
updateContact = self._convertPublicContactToEpp(security_contact, disclose_email=True, createContact=False)
|
||||||
)
|
|
||||||
updateContact = self._convertPublicContactToEpp(
|
|
||||||
security_contact, disclose_email=True, createContact=False
|
|
||||||
)
|
|
||||||
|
|
||||||
expected_calls = [
|
expected_calls = [
|
||||||
call(expectedCreateCommand, cleaned=True),
|
call(expectedCreateCommand, cleaned=True),
|
||||||
|
@ -804,9 +752,7 @@ class TestRegistrantContacts(MockEppLib):
|
||||||
actual_contact = contact[1]
|
actual_contact = contact[1]
|
||||||
is_security = expected_contact.contact_type == "security"
|
is_security = expected_contact.contact_type == "security"
|
||||||
|
|
||||||
expectedCreateCommand = self._convertPublicContactToEpp(
|
expectedCreateCommand = self._convertPublicContactToEpp(expected_contact, disclose_email=is_security)
|
||||||
expected_contact, disclose_email=is_security
|
|
||||||
)
|
|
||||||
|
|
||||||
# Should only be disclosed if the type is security, as the email is valid
|
# Should only be disclosed if the type is security, as the email is valid
|
||||||
self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True)
|
self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True)
|
||||||
|
@ -818,20 +764,14 @@ class TestRegistrantContacts(MockEppLib):
|
||||||
self.maxDiff = None
|
self.maxDiff = None
|
||||||
domain, _ = Domain.objects.get_or_create(name="freeman.gov")
|
domain, _ = Domain.objects.get_or_create(name="freeman.gov")
|
||||||
dummy_contact = domain.get_default_security_contact()
|
dummy_contact = domain.get_default_security_contact()
|
||||||
test_disclose = self._convertPublicContactToEpp(
|
test_disclose = self._convertPublicContactToEpp(dummy_contact, disclose_email=True).__dict__
|
||||||
dummy_contact, disclose_email=True
|
test_not_disclose = self._convertPublicContactToEpp(dummy_contact, disclose_email=False).__dict__
|
||||||
).__dict__
|
|
||||||
test_not_disclose = self._convertPublicContactToEpp(
|
|
||||||
dummy_contact, disclose_email=False
|
|
||||||
).__dict__
|
|
||||||
|
|
||||||
# Separated for linter
|
# Separated for linter
|
||||||
disclose_email_field = {common.DiscloseField.EMAIL}
|
disclose_email_field = {common.DiscloseField.EMAIL}
|
||||||
expected_disclose = {
|
expected_disclose = {
|
||||||
"auth_info": common.ContactAuthInfo(pw="2fooBAR123fooBaz"),
|
"auth_info": common.ContactAuthInfo(pw="2fooBAR123fooBaz"),
|
||||||
"disclose": common.Disclose(
|
"disclose": common.Disclose(flag=True, fields=disclose_email_field, types=None),
|
||||||
flag=True, fields=disclose_email_field, types=None
|
|
||||||
),
|
|
||||||
"email": "dotgov@cisa.dhs.gov",
|
"email": "dotgov@cisa.dhs.gov",
|
||||||
"extensions": [],
|
"extensions": [],
|
||||||
"fax": None,
|
"fax": None,
|
||||||
|
@ -857,9 +797,7 @@ class TestRegistrantContacts(MockEppLib):
|
||||||
# Separated for linter
|
# Separated for linter
|
||||||
expected_not_disclose = {
|
expected_not_disclose = {
|
||||||
"auth_info": common.ContactAuthInfo(pw="2fooBAR123fooBaz"),
|
"auth_info": common.ContactAuthInfo(pw="2fooBAR123fooBaz"),
|
||||||
"disclose": common.Disclose(
|
"disclose": common.Disclose(flag=False, fields=disclose_email_field, types=None),
|
||||||
flag=False, fields=disclose_email_field, types=None
|
|
||||||
),
|
|
||||||
"email": "dotgov@cisa.dhs.gov",
|
"email": "dotgov@cisa.dhs.gov",
|
||||||
"extensions": [],
|
"extensions": [],
|
||||||
"fax": None,
|
"fax": None,
|
||||||
|
@ -902,9 +840,7 @@ class TestRegistrantContacts(MockEppLib):
|
||||||
expectedSecContact.registry_id = "defaultSec"
|
expectedSecContact.registry_id = "defaultSec"
|
||||||
domain.security_contact = expectedSecContact
|
domain.security_contact = expectedSecContact
|
||||||
|
|
||||||
expectedCreateCommand = self._convertPublicContactToEpp(
|
expectedCreateCommand = self._convertPublicContactToEpp(expectedSecContact, disclose_email=False)
|
||||||
expectedSecContact, disclose_email=False
|
|
||||||
)
|
|
||||||
|
|
||||||
self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True)
|
self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True)
|
||||||
# Confirm that we are getting a default email
|
# Confirm that we are getting a default email
|
||||||
|
@ -923,9 +859,7 @@ class TestRegistrantContacts(MockEppLib):
|
||||||
expectedTechContact.registry_id = "defaultTech"
|
expectedTechContact.registry_id = "defaultTech"
|
||||||
domain.technical_contact = expectedTechContact
|
domain.technical_contact = expectedTechContact
|
||||||
|
|
||||||
expectedCreateCommand = self._convertPublicContactToEpp(
|
expectedCreateCommand = self._convertPublicContactToEpp(expectedTechContact, disclose_email=False)
|
||||||
expectedTechContact, disclose_email=False
|
|
||||||
)
|
|
||||||
|
|
||||||
self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True)
|
self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True)
|
||||||
# Confirm that we are getting a default email
|
# Confirm that we are getting a default email
|
||||||
|
@ -945,9 +879,7 @@ class TestRegistrantContacts(MockEppLib):
|
||||||
expectedSecContact.email = "123@mail.gov"
|
expectedSecContact.email = "123@mail.gov"
|
||||||
domain.security_contact = expectedSecContact
|
domain.security_contact = expectedSecContact
|
||||||
|
|
||||||
expectedCreateCommand = self._convertPublicContactToEpp(
|
expectedCreateCommand = self._convertPublicContactToEpp(expectedSecContact, disclose_email=True)
|
||||||
expectedSecContact, disclose_email=True
|
|
||||||
)
|
|
||||||
|
|
||||||
self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True)
|
self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True)
|
||||||
# Confirm that we are getting the desired email
|
# Confirm that we are getting the desired email
|
||||||
|
@ -972,9 +904,7 @@ class TestRegistrantContacts(MockEppLib):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Checks if we grabbed the correct PublicContact
|
# Checks if we grabbed the correct PublicContact
|
||||||
self.assertEqual(
|
self.assertEqual(self.domain_contact.security_contact.email, expected_contact.email)
|
||||||
self.domain_contact.security_contact.email, expected_contact.email
|
|
||||||
)
|
|
||||||
|
|
||||||
expected_contact_db = PublicContact.objects.filter(
|
expected_contact_db = PublicContact.objects.filter(
|
||||||
registry_id=self.domain_contact.security_contact.registry_id,
|
registry_id=self.domain_contact.security_contact.registry_id,
|
||||||
|
@ -1003,9 +933,7 @@ class TestRegistrantContacts(MockEppLib):
|
||||||
contact_type=technical,
|
contact_type=technical,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(self.domain_contact.technical_contact.email, expected_contact.email)
|
||||||
self.domain_contact.technical_contact.email, expected_contact.email
|
|
||||||
)
|
|
||||||
|
|
||||||
# Checks if we grab the correct PublicContact
|
# Checks if we grab the correct PublicContact
|
||||||
expected_contact_db = PublicContact.objects.filter(
|
expected_contact_db = PublicContact.objects.filter(
|
||||||
|
@ -1035,9 +963,7 @@ class TestRegistrantContacts(MockEppLib):
|
||||||
contact_type=administrative,
|
contact_type=administrative,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(self.domain_contact.administrative_contact.email, expected_contact.email)
|
||||||
self.domain_contact.administrative_contact.email, expected_contact.email
|
|
||||||
)
|
|
||||||
|
|
||||||
expected_contact_db = PublicContact.objects.filter(
|
expected_contact_db = PublicContact.objects.filter(
|
||||||
registry_id=self.domain_contact.administrative_contact.registry_id,
|
registry_id=self.domain_contact.administrative_contact.registry_id,
|
||||||
|
@ -1045,9 +971,7 @@ class TestRegistrantContacts(MockEppLib):
|
||||||
).get()
|
).get()
|
||||||
|
|
||||||
# Checks if we grab the correct PublicContact
|
# Checks if we grab the correct PublicContact
|
||||||
self.assertEqual(
|
self.assertEqual(self.domain_contact.administrative_contact, expected_contact_db)
|
||||||
self.domain_contact.administrative_contact, expected_contact_db
|
|
||||||
)
|
|
||||||
self.mockedSendFunction.assert_has_calls(
|
self.mockedSendFunction.assert_has_calls(
|
||||||
[
|
[
|
||||||
call(
|
call(
|
||||||
|
@ -1067,9 +991,7 @@ class TestRegistrantContacts(MockEppLib):
|
||||||
contact_type=PublicContact.ContactTypeChoices.REGISTRANT,
|
contact_type=PublicContact.ContactTypeChoices.REGISTRANT,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(self.domain_contact.registrant_contact.email, expected_contact.email)
|
||||||
self.domain_contact.registrant_contact.email, expected_contact.email
|
|
||||||
)
|
|
||||||
|
|
||||||
expected_contact_db = PublicContact.objects.filter(
|
expected_contact_db = PublicContact.objects.filter(
|
||||||
registry_id=self.domain_contact.registrant_contact.registry_id,
|
registry_id=self.domain_contact.registrant_contact.registry_id,
|
||||||
|
@ -1104,9 +1026,7 @@ class TestRegistrantNameservers(MockEppLib):
|
||||||
self.nameserver2 = "ns1.my-nameserver-2.com"
|
self.nameserver2 = "ns1.my-nameserver-2.com"
|
||||||
self.nameserver3 = "ns1.cats-are-superior3.com"
|
self.nameserver3 = "ns1.cats-are-superior3.com"
|
||||||
|
|
||||||
self.domain, _ = Domain.objects.get_or_create(
|
self.domain, _ = Domain.objects.get_or_create(name="my-nameserver.gov", state=Domain.State.DNS_NEEDED)
|
||||||
name="my-nameserver.gov", state=Domain.State.DNS_NEEDED
|
|
||||||
)
|
|
||||||
self.domainWithThreeNS, _ = Domain.objects.get_or_create(
|
self.domainWithThreeNS, _ = Domain.objects.get_or_create(
|
||||||
name="threenameserversDomain.gov", state=Domain.State.READY
|
name="threenameserversDomain.gov", state=Domain.State.READY
|
||||||
)
|
)
|
||||||
|
@ -1476,9 +1396,7 @@ class TestRegistrantNameservers(MockEppLib):
|
||||||
with a different IP address(es)
|
with a different IP address(es)
|
||||||
Then `commands.UpdateHost` is sent to the registry
|
Then `commands.UpdateHost` is sent to the registry
|
||||||
"""
|
"""
|
||||||
domain, _ = Domain.objects.get_or_create(
|
domain, _ = Domain.objects.get_or_create(name="nameserverwithip.gov", state=Domain.State.READY)
|
||||||
name="nameserverwithip.gov", state=Domain.State.READY
|
|
||||||
)
|
|
||||||
domain.nameservers = [
|
domain.nameservers = [
|
||||||
("ns1.nameserverwithip.gov", ["2.3.4.5", "1.2.3.4"]),
|
("ns1.nameserverwithip.gov", ["2.3.4.5", "1.2.3.4"]),
|
||||||
(
|
(
|
||||||
|
@ -1499,11 +1417,7 @@ class TestRegistrantNameservers(MockEppLib):
|
||||||
call(
|
call(
|
||||||
commands.UpdateHost(
|
commands.UpdateHost(
|
||||||
name="ns2.nameserverwithip.gov",
|
name="ns2.nameserverwithip.gov",
|
||||||
add=[
|
add=[common.Ip(addr="2001:0db8:85a3:0000:0000:8a2e:0370:7334", ip="v6")],
|
||||||
common.Ip(
|
|
||||||
addr="2001:0db8:85a3:0000:0000:8a2e:0370:7334", ip="v6"
|
|
||||||
)
|
|
||||||
],
|
|
||||||
rem=[],
|
rem=[],
|
||||||
chg=None,
|
chg=None,
|
||||||
),
|
),
|
||||||
|
@ -1565,9 +1479,7 @@ class TestRegistrantNameservers(MockEppLib):
|
||||||
self.assertEqual(self.mockedSendFunction.call_count, 4)
|
self.assertEqual(self.mockedSendFunction.call_count, 4)
|
||||||
|
|
||||||
def test_is_subdomain_with_no_ip(self):
|
def test_is_subdomain_with_no_ip(self):
|
||||||
domain, _ = Domain.objects.get_or_create(
|
domain, _ = Domain.objects.get_or_create(name="nameserversubdomain.gov", state=Domain.State.READY)
|
||||||
name="nameserversubdomain.gov", state=Domain.State.READY
|
|
||||||
)
|
|
||||||
|
|
||||||
with self.assertRaises(NameserverError):
|
with self.assertRaises(NameserverError):
|
||||||
domain.nameservers = [
|
domain.nameservers = [
|
||||||
|
@ -1576,9 +1488,7 @@ class TestRegistrantNameservers(MockEppLib):
|
||||||
]
|
]
|
||||||
|
|
||||||
def test_not_subdomain_but_has_ip(self):
|
def test_not_subdomain_but_has_ip(self):
|
||||||
domain, _ = Domain.objects.get_or_create(
|
domain, _ = Domain.objects.get_or_create(name="nameserversubdomain.gov", state=Domain.State.READY)
|
||||||
name="nameserversubdomain.gov", state=Domain.State.READY
|
|
||||||
)
|
|
||||||
|
|
||||||
with self.assertRaises(NameserverError):
|
with self.assertRaises(NameserverError):
|
||||||
domain.nameservers = [
|
domain.nameservers = [
|
||||||
|
@ -1587,9 +1497,7 @@ class TestRegistrantNameservers(MockEppLib):
|
||||||
]
|
]
|
||||||
|
|
||||||
def test_is_subdomain_but_ip_addr_not_valid(self):
|
def test_is_subdomain_but_ip_addr_not_valid(self):
|
||||||
domain, _ = Domain.objects.get_or_create(
|
domain, _ = Domain.objects.get_or_create(name="nameserversubdomain.gov", state=Domain.State.READY)
|
||||||
name="nameserversubdomain.gov", state=Domain.State.READY
|
|
||||||
)
|
|
||||||
|
|
||||||
with self.assertRaises(NameserverError):
|
with self.assertRaises(NameserverError):
|
||||||
domain.nameservers = [
|
domain.nameservers = [
|
||||||
|
@ -1600,9 +1508,7 @@ class TestRegistrantNameservers(MockEppLib):
|
||||||
def test_setting_not_allowed(self):
|
def test_setting_not_allowed(self):
|
||||||
"""Scenario: A domain state is not Ready or DNS Needed
|
"""Scenario: A domain state is not Ready or DNS Needed
|
||||||
then setting nameservers is not allowed"""
|
then setting nameservers is not allowed"""
|
||||||
domain, _ = Domain.objects.get_or_create(
|
domain, _ = Domain.objects.get_or_create(name="onholdDomain.gov", state=Domain.State.ON_HOLD)
|
||||||
name="onholdDomain.gov", state=Domain.State.ON_HOLD
|
|
||||||
)
|
|
||||||
with self.assertRaises(ActionNotAllowed):
|
with self.assertRaises(ActionNotAllowed):
|
||||||
domain.nameservers = [self.nameserver1, self.nameserver2]
|
domain.nameservers = [self.nameserver1, self.nameserver2]
|
||||||
|
|
||||||
|
@ -1618,9 +1524,7 @@ class TestRegistrantNameservers(MockEppLib):
|
||||||
don't want to lose user info (and exit out too early)
|
don't want to lose user info (and exit out too early)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
domain, _ = Domain.objects.get_or_create(
|
domain, _ = Domain.objects.get_or_create(name="failednameserver.gov", state=Domain.State.READY)
|
||||||
name="failednameserver.gov", state=Domain.State.READY
|
|
||||||
)
|
|
||||||
|
|
||||||
with self.assertRaises(RegistryError):
|
with self.assertRaises(RegistryError):
|
||||||
domain.nameservers = [("ns1.failednameserver.gov", ["4.5.6"])]
|
domain.nameservers = [("ns1.failednameserver.gov", ["4.5.6"])]
|
||||||
|
@ -1647,9 +1551,7 @@ class TestNameserverValidation(TestCase):
|
||||||
|
|
||||||
def test_64_char_label_too_long(self):
|
def test_64_char_label_too_long(self):
|
||||||
"""Test that label of 64 characters or longer is invalid"""
|
"""Test that label of 64 characters or longer is invalid"""
|
||||||
label_too_long = (
|
label_too_long = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
|
||||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
|
|
||||||
)
|
|
||||||
domain_label_too_long = "www." + label_too_long + ".gov"
|
domain_label_too_long = "www." + label_too_long + ".gov"
|
||||||
self.assertFalse(Domain.isValidHost(domain_label_too_long))
|
self.assertFalse(Domain.isValidHost(domain_label_too_long))
|
||||||
|
|
||||||
|
@ -1684,9 +1586,7 @@ class TestRegistrantDNSSEC(MockEppLib):
|
||||||
"""Rule: Registrants may modify their secure DNS data"""
|
"""Rule: Registrants may modify their secure DNS data"""
|
||||||
|
|
||||||
# helper function to create UpdateDomainDNSSECExtention object for verification
|
# helper function to create UpdateDomainDNSSECExtention object for verification
|
||||||
def createUpdateExtension(
|
def createUpdateExtension(self, dnssecdata: extensions.DNSSECExtension, remove=False):
|
||||||
self, dnssecdata: extensions.DNSSECExtension, remove=False
|
|
||||||
):
|
|
||||||
if not remove:
|
if not remove:
|
||||||
return commands.UpdateDomainDNSSECExtension(
|
return commands.UpdateDomainDNSSECExtension(
|
||||||
maxSigLife=dnssecdata.maxSigLife,
|
maxSigLife=dnssecdata.maxSigLife,
|
||||||
|
@ -1946,9 +1846,7 @@ class TestRegistrantDNSSEC(MockEppLib):
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEquals(
|
self.assertEquals(dnssecdata_get.dsData, self.dnssecExtensionWithMultDsData.dsData)
|
||||||
dnssecdata_get.dsData, self.dnssecExtensionWithMultDsData.dsData
|
|
||||||
)
|
|
||||||
|
|
||||||
patcher.stop()
|
patcher.stop()
|
||||||
|
|
||||||
|
@ -2052,9 +1950,7 @@ class TestRegistrantDNSSEC(MockEppLib):
|
||||||
|
|
||||||
with self.assertRaises(RegistryError) as err:
|
with self.assertRaises(RegistryError) as err:
|
||||||
domain.dnssecdata = self.dnssecExtensionWithDsData
|
domain.dnssecdata = self.dnssecExtensionWithDsData
|
||||||
self.assertTrue(
|
self.assertTrue(err.is_client_error() or err.is_session_error() or err.is_server_error())
|
||||||
err.is_client_error() or err.is_session_error() or err.is_server_error()
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TestAnalystClientHold(MockEppLib):
|
class TestAnalystClientHold(MockEppLib):
|
||||||
|
@ -2068,13 +1964,9 @@ class TestAnalystClientHold(MockEppLib):
|
||||||
"""
|
"""
|
||||||
super().setUp()
|
super().setUp()
|
||||||
# for the tests, need a domain in the ready state
|
# for the tests, need a domain in the ready state
|
||||||
self.domain, _ = Domain.objects.get_or_create(
|
self.domain, _ = Domain.objects.get_or_create(name="fake.gov", state=Domain.State.READY)
|
||||||
name="fake.gov", state=Domain.State.READY
|
|
||||||
)
|
|
||||||
# for the tests, need a domain in the on_hold state
|
# for the tests, need a domain in the on_hold state
|
||||||
self.domain_on_hold, _ = Domain.objects.get_or_create(
|
self.domain_on_hold, _ = Domain.objects.get_or_create(name="fake-on-hold.gov", state=Domain.State.ON_HOLD)
|
||||||
name="fake-on-hold.gov", state=Domain.State.ON_HOLD
|
|
||||||
)
|
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
Domain.objects.all().delete()
|
Domain.objects.all().delete()
|
||||||
|
@ -2222,9 +2114,7 @@ class TestAnalystClientHold(MockEppLib):
|
||||||
# is_server_error; so test for those conditions
|
# is_server_error; so test for those conditions
|
||||||
with self.assertRaises(RegistryError) as err:
|
with self.assertRaises(RegistryError) as err:
|
||||||
self.domain.place_client_hold()
|
self.domain.place_client_hold()
|
||||||
self.assertTrue(
|
self.assertTrue(err.is_client_error() or err.is_session_error() or err.is_server_error())
|
||||||
err.is_client_error() or err.is_session_error() or err.is_server_error()
|
|
||||||
)
|
|
||||||
|
|
||||||
patcher.stop()
|
patcher.stop()
|
||||||
|
|
||||||
|
@ -2303,12 +2193,8 @@ class TestAnalystDelete(MockEppLib):
|
||||||
And a domain exists in the registry
|
And a domain exists in the registry
|
||||||
"""
|
"""
|
||||||
super().setUp()
|
super().setUp()
|
||||||
self.domain, _ = Domain.objects.get_or_create(
|
self.domain, _ = Domain.objects.get_or_create(name="fake.gov", state=Domain.State.READY)
|
||||||
name="fake.gov", state=Domain.State.READY
|
self.domain_on_hold, _ = Domain.objects.get_or_create(name="fake-on-hold.gov", state=Domain.State.ON_HOLD)
|
||||||
)
|
|
||||||
self.domain_on_hold, _ = Domain.objects.get_or_create(
|
|
||||||
name="fake-on-hold.gov", state=Domain.State.ON_HOLD
|
|
||||||
)
|
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
Domain.objects.all().delete()
|
Domain.objects.all().delete()
|
||||||
|
@ -2351,19 +2237,14 @@ class TestAnalystDelete(MockEppLib):
|
||||||
And `state` is not set to `DELETED`
|
And `state` is not set to `DELETED`
|
||||||
"""
|
"""
|
||||||
# Desired domain
|
# Desired domain
|
||||||
domain, _ = Domain.objects.get_or_create(
|
domain, _ = Domain.objects.get_or_create(name="failDelete.gov", state=Domain.State.ON_HOLD)
|
||||||
name="failDelete.gov", state=Domain.State.ON_HOLD
|
|
||||||
)
|
|
||||||
# Put the domain in client hold
|
# Put the domain in client hold
|
||||||
domain.place_client_hold()
|
domain.place_client_hold()
|
||||||
|
|
||||||
# Delete it
|
# Delete it
|
||||||
with self.assertRaises(RegistryError) as err:
|
with self.assertRaises(RegistryError) as err:
|
||||||
domain.deletedInEpp()
|
domain.deletedInEpp()
|
||||||
self.assertTrue(
|
self.assertTrue(err.is_client_error() and err.code == ErrorCode.OBJECT_ASSOCIATION_PROHIBITS_OPERATION)
|
||||||
err.is_client_error()
|
|
||||||
and err.code == ErrorCode.OBJECT_ASSOCIATION_PROHIBITS_OPERATION
|
|
||||||
)
|
|
||||||
self.mockedSendFunction.assert_has_calls(
|
self.mockedSendFunction.assert_has_calls(
|
||||||
[
|
[
|
||||||
call(
|
call(
|
||||||
|
@ -2390,10 +2271,7 @@ class TestAnalystDelete(MockEppLib):
|
||||||
self.assertEqual(self.domain.state, Domain.State.READY)
|
self.assertEqual(self.domain.state, Domain.State.READY)
|
||||||
with self.assertRaises(TransitionNotAllowed) as err:
|
with self.assertRaises(TransitionNotAllowed) as err:
|
||||||
self.domain.deletedInEpp()
|
self.domain.deletedInEpp()
|
||||||
self.assertTrue(
|
self.assertTrue(err.is_client_error() and err.code == ErrorCode.OBJECT_STATUS_PROHIBITS_OPERATION)
|
||||||
err.is_client_error()
|
|
||||||
and err.code == ErrorCode.OBJECT_STATUS_PROHIBITS_OPERATION
|
|
||||||
)
|
|
||||||
# Domain should not be deleted
|
# Domain should not be deleted
|
||||||
self.assertNotEqual(self.domain, None)
|
self.assertNotEqual(self.domain, None)
|
||||||
# Domain should have the right state
|
# Domain should have the right state
|
||||||
|
|
|
@ -12,9 +12,7 @@ class TestNameserverError(TestCase):
|
||||||
nameserver = "nameserver val"
|
nameserver = "nameserver val"
|
||||||
expected = "Using your domain for a name server requires an IP address"
|
expected = "Using your domain for a name server requires an IP address"
|
||||||
|
|
||||||
nsException = NameserverError(
|
nsException = NameserverError(code=nsErrorCodes.MISSING_IP, nameserver=nameserver)
|
||||||
code=nsErrorCodes.MISSING_IP, nameserver=nameserver
|
|
||||||
)
|
|
||||||
self.assertEqual(nsException.message, expected)
|
self.assertEqual(nsException.message, expected)
|
||||||
self.assertEqual(nsException.code, nsErrorCodes.MISSING_IP)
|
self.assertEqual(nsException.code, nsErrorCodes.MISSING_IP)
|
||||||
|
|
||||||
|
@ -24,9 +22,7 @@ class TestNameserverError(TestCase):
|
||||||
nameserver = "nameserver val"
|
nameserver = "nameserver val"
|
||||||
expected = "Too many hosts provided, you may not have more than 13 nameservers."
|
expected = "Too many hosts provided, you may not have more than 13 nameservers."
|
||||||
|
|
||||||
nsException = NameserverError(
|
nsException = NameserverError(code=nsErrorCodes.TOO_MANY_HOSTS, nameserver=nameserver)
|
||||||
code=nsErrorCodes.TOO_MANY_HOSTS, nameserver=nameserver
|
|
||||||
)
|
|
||||||
self.assertEqual(nsException.message, expected)
|
self.assertEqual(nsException.message, expected)
|
||||||
self.assertEqual(nsException.code, nsErrorCodes.TOO_MANY_HOSTS)
|
self.assertEqual(nsException.code, nsErrorCodes.TOO_MANY_HOSTS)
|
||||||
|
|
||||||
|
@ -36,8 +32,6 @@ class TestNameserverError(TestCase):
|
||||||
nameserver = "nameserver val"
|
nameserver = "nameserver val"
|
||||||
|
|
||||||
expected = f"{nameserver}: Enter an IP address in the required format."
|
expected = f"{nameserver}: Enter an IP address in the required format."
|
||||||
nsException = NameserverError(
|
nsException = NameserverError(code=nsErrorCodes.INVALID_IP, nameserver=nameserver, ip=ip)
|
||||||
code=nsErrorCodes.INVALID_IP, nameserver=nameserver, ip=ip
|
|
||||||
)
|
|
||||||
self.assertEqual(nsException.message, expected)
|
self.assertEqual(nsException.message, expected)
|
||||||
self.assertEqual(nsException.code, nsErrorCodes.INVALID_IP)
|
self.assertEqual(nsException.code, nsErrorCodes.INVALID_IP)
|
||||||
|
|
|
@ -18,21 +18,11 @@ class ExportDataTest(TestCase):
|
||||||
username=username, first_name=first_name, last_name=last_name, email=email
|
username=username, first_name=first_name, last_name=last_name, email=email
|
||||||
)
|
)
|
||||||
|
|
||||||
self.domain_1, _ = Domain.objects.get_or_create(
|
self.domain_1, _ = Domain.objects.get_or_create(name="cdomain1.gov", state=Domain.State.READY)
|
||||||
name="cdomain1.gov", state=Domain.State.READY
|
self.domain_2, _ = Domain.objects.get_or_create(name="adomain2.gov", state=Domain.State.DNS_NEEDED)
|
||||||
)
|
self.domain_3, _ = Domain.objects.get_or_create(name="ddomain3.gov", state=Domain.State.ON_HOLD)
|
||||||
self.domain_2, _ = Domain.objects.get_or_create(
|
self.domain_4, _ = Domain.objects.get_or_create(name="bdomain4.gov", state=Domain.State.UNKNOWN)
|
||||||
name="adomain2.gov", state=Domain.State.DNS_NEEDED
|
self.domain_4, _ = Domain.objects.get_or_create(name="bdomain4.gov", state=Domain.State.UNKNOWN)
|
||||||
)
|
|
||||||
self.domain_3, _ = Domain.objects.get_or_create(
|
|
||||||
name="ddomain3.gov", state=Domain.State.ON_HOLD
|
|
||||||
)
|
|
||||||
self.domain_4, _ = Domain.objects.get_or_create(
|
|
||||||
name="bdomain4.gov", state=Domain.State.UNKNOWN
|
|
||||||
)
|
|
||||||
self.domain_4, _ = Domain.objects.get_or_create(
|
|
||||||
name="bdomain4.gov", state=Domain.State.UNKNOWN
|
|
||||||
)
|
|
||||||
|
|
||||||
self.domain_information_1, _ = DomainInformation.objects.get_or_create(
|
self.domain_information_1, _ = DomainInformation.objects.get_or_create(
|
||||||
creator=self.user,
|
creator=self.user,
|
||||||
|
@ -121,16 +111,8 @@ class ExportDataTest(TestCase):
|
||||||
|
|
||||||
# Normalize line endings and remove commas,
|
# Normalize line endings and remove commas,
|
||||||
# spaces and leading/trailing whitespace
|
# spaces and leading/trailing whitespace
|
||||||
csv_content = (
|
csv_content = csv_content.replace(",,", "").replace(",", "").replace(" ", "").replace("\r\n", "\n").strip()
|
||||||
csv_content.replace(",,", "")
|
expected_content = expected_content.replace(",,", "").replace(",", "").replace(" ", "").strip()
|
||||||
.replace(",", "")
|
|
||||||
.replace(" ", "")
|
|
||||||
.replace("\r\n", "\n")
|
|
||||||
.strip()
|
|
||||||
)
|
|
||||||
expected_content = (
|
|
||||||
expected_content.replace(",,", "").replace(",", "").replace(" ", "").strip()
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertEqual(csv_content, expected_content)
|
self.assertEqual(csv_content, expected_content)
|
||||||
|
|
||||||
|
@ -181,15 +163,7 @@ class ExportDataTest(TestCase):
|
||||||
|
|
||||||
# Normalize line endings and remove commas,
|
# Normalize line endings and remove commas,
|
||||||
# spaces and leading/trailing whitespace
|
# spaces and leading/trailing whitespace
|
||||||
csv_content = (
|
csv_content = csv_content.replace(",,", "").replace(",", "").replace(" ", "").replace("\r\n", "\n").strip()
|
||||||
csv_content.replace(",,", "")
|
expected_content = expected_content.replace(",,", "").replace(",", "").replace(" ", "").strip()
|
||||||
.replace(",", "")
|
|
||||||
.replace(" ", "")
|
|
||||||
.replace("\r\n", "\n")
|
|
||||||
.strip()
|
|
||||||
)
|
|
||||||
expected_content = (
|
|
||||||
expected_content.replace(",,", "").replace(",", "").replace(" ", "").strip()
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertEqual(csv_content, expected_content)
|
self.assertEqual(csv_content, expected_content)
|
||||||
|
|
|
@ -58,9 +58,7 @@ class TestUserPostSave(TestCase):
|
||||||
"""Expect 1 Contact containing data copied from User."""
|
"""Expect 1 Contact containing data copied from User."""
|
||||||
# create the user
|
# create the user
|
||||||
self.assertEqual(len(Contact.objects.all()), 0)
|
self.assertEqual(len(Contact.objects.all()), 0)
|
||||||
user = get_user_model().objects.create(
|
user = get_user_model().objects.create(username=self.username, first_name="", last_name="", email="", phone="")
|
||||||
username=self.username, first_name="", last_name="", email="", phone=""
|
|
||||||
)
|
|
||||||
# delete the contact
|
# delete the contact
|
||||||
Contact.objects.all().delete()
|
Contact.objects.all().delete()
|
||||||
self.assertEqual(len(Contact.objects.all()), 0)
|
self.assertEqual(len(Contact.objects.all()), 0)
|
||||||
|
|
|
@ -23,16 +23,12 @@ class TestTemplateTags(TestCase):
|
||||||
return Template(string).render(context)
|
return Template(string).render(context)
|
||||||
|
|
||||||
def test_public_site_url(self):
|
def test_public_site_url(self):
|
||||||
result = self._render_template(
|
result = self._render_template("{% load url_helpers %}{% public_site_url 'directory/page' %}")
|
||||||
"{% load url_helpers %}{% public_site_url 'directory/page' %}"
|
|
||||||
)
|
|
||||||
self.assertTrue(result.startswith(settings.GETGOV_PUBLIC_SITE_URL))
|
self.assertTrue(result.startswith(settings.GETGOV_PUBLIC_SITE_URL))
|
||||||
self.assertTrue(result.endswith("/directory/page"))
|
self.assertTrue(result.endswith("/directory/page"))
|
||||||
|
|
||||||
def test_public_site_url_leading_slash(self):
|
def test_public_site_url_leading_slash(self):
|
||||||
result = self._render_template(
|
result = self._render_template("{% load url_helpers %}{% public_site_url '/directory/page' %}")
|
||||||
"{% load url_helpers %}{% public_site_url '/directory/page' %}"
|
|
||||||
)
|
|
||||||
self.assertTrue(result.startswith(settings.GETGOV_PUBLIC_SITE_URL))
|
self.assertTrue(result.startswith(settings.GETGOV_PUBLIC_SITE_URL))
|
||||||
# slash-slash host slash directory slash page
|
# slash-slash host slash directory slash page
|
||||||
self.assertEqual(result.count("/"), 4)
|
self.assertEqual(result.count("/"), 4)
|
||||||
|
@ -40,17 +36,11 @@ class TestTemplateTags(TestCase):
|
||||||
|
|
||||||
class CustomFiltersTestCase(TestCase):
|
class CustomFiltersTestCase(TestCase):
|
||||||
def test_extract_value_filter(self):
|
def test_extract_value_filter(self):
|
||||||
html_input = (
|
html_input = '<input type="checkbox" name="_selected_action" value="123" id="label_123" class="action-select">'
|
||||||
'<input type="checkbox" name="_selected_action" value="123" '
|
|
||||||
'id="label_123" class="action-select">'
|
|
||||||
)
|
|
||||||
result = extract_value(html_input)
|
result = extract_value(html_input)
|
||||||
self.assertEqual(result, "123")
|
self.assertEqual(result, "123")
|
||||||
|
|
||||||
html_input = (
|
html_input = '<input type="checkbox" name="_selected_action" value="abc" id="label_123" class="action-select">'
|
||||||
'<input type="checkbox" name="_selected_action" value="abc" '
|
|
||||||
'id="label_123" class="action-select">'
|
|
||||||
)
|
|
||||||
result = extract_value(html_input)
|
result = extract_value(html_input)
|
||||||
self.assertEqual(result, "abc")
|
self.assertEqual(result, "abc")
|
||||||
|
|
||||||
|
@ -81,9 +71,7 @@ class CustomFiltersTestCase(TestCase):
|
||||||
|
|
||||||
substring = "XYZ"
|
substring = "XYZ"
|
||||||
result = slice_after(value, substring)
|
result = slice_after(value, substring)
|
||||||
self.assertEqual(
|
self.assertEqual(result, value) # Should return the original value if substring not found
|
||||||
result, value
|
|
||||||
) # Should return the original value if substring not found
|
|
||||||
|
|
||||||
def test_contains_checkbox_with_checkbox(self):
|
def test_contains_checkbox_with_checkbox(self):
|
||||||
# Test the filter when HTML list contains a checkbox
|
# Test the filter when HTML list contains a checkbox
|
||||||
|
|
|
@ -89,9 +89,7 @@ class TestLogins(TestCase):
|
||||||
# Check Domain table
|
# Check Domain table
|
||||||
matching_domains = Domain.objects.filter(name=transition_domain_name)
|
matching_domains = Domain.objects.filter(name=transition_domain_name)
|
||||||
# Check Domain Information table
|
# Check Domain Information table
|
||||||
matching_domain_informations = DomainInformation.objects.filter(
|
matching_domain_informations = DomainInformation.objects.filter(domain__name=transition_domain_name)
|
||||||
domain__name=transition_domain_name
|
|
||||||
)
|
|
||||||
# Check Domain Invitation table
|
# Check Domain Invitation table
|
||||||
matching_domain_invitations = DomainInvitation.objects.filter(
|
matching_domain_invitations = DomainInvitation.objects.filter(
|
||||||
email=transition_domain_email.lower(),
|
email=transition_domain_email.lower(),
|
||||||
|
@ -133,12 +131,8 @@ class TestLogins(TestCase):
|
||||||
|
|
||||||
self.assertTrue(total_missing_domains == expected_missing_domains)
|
self.assertTrue(total_missing_domains == expected_missing_domains)
|
||||||
self.assertTrue(total_duplicate_domains == expected_duplicate_domains)
|
self.assertTrue(total_duplicate_domains == expected_duplicate_domains)
|
||||||
self.assertTrue(
|
self.assertTrue(total_missing_domain_informations == expected_missing_domain_informations)
|
||||||
total_missing_domain_informations == expected_missing_domain_informations
|
self.assertTrue(total_missing_domain_invitations == expected_missing_domain_invitations)
|
||||||
)
|
|
||||||
self.assertTrue(
|
|
||||||
total_missing_domain_invitations == expected_missing_domain_invitations
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertTrue(total_transition_domains == expected_total_transition_domains)
|
self.assertTrue(total_transition_domains == expected_total_transition_domains)
|
||||||
self.assertTrue(total_domains == expected_total_domains)
|
self.assertTrue(total_domains == expected_total_domains)
|
||||||
|
@ -238,10 +232,8 @@ class TestLogins(TestCase):
|
||||||
# Simluate Logins
|
# Simluate Logins
|
||||||
for invite in DomainInvitation.objects.all():
|
for invite in DomainInvitation.objects.all():
|
||||||
# get a user with this email address
|
# get a user with this email address
|
||||||
user, user_created = User.objects.get_or_create(
|
user, user_created = User.objects.get_or_create(email=invite.email, username=invite.email)
|
||||||
email=invite.email, username=invite.email
|
user.on_each_login()
|
||||||
)
|
|
||||||
user.first_login()
|
|
||||||
|
|
||||||
# Analyze the tables
|
# Analyze the tables
|
||||||
expected_total_transition_domains = 8
|
expected_total_transition_domains = 8
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue