mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-05-20 03:19:24 +02:00
Merge branch 'main' into za/1530-warning-messages-changing-statuses
This commit is contained in:
commit
20d39bcb69
112 changed files with 2975 additions and 2925 deletions
6
.github/ISSUE_TEMPLATE/bug.yml
vendored
6
.github/ISSUE_TEMPLATE/bug.yml
vendored
|
@ -22,7 +22,7 @@ body:
|
||||||
attributes:
|
attributes:
|
||||||
label: Expected Behavior
|
label: Expected Behavior
|
||||||
description: "Please add a concise description of the behavior you would expect if this issue were not occurring"
|
description: "Please add a concise description of the behavior you would expect if this issue were not occurring"
|
||||||
placeholder: "Example: When submitting a new domain application, the request should be successful, OR if there is a problem with the user's application preventing submission, errors should be enumerated to the user"
|
placeholder: "Example: When submitting a new domain request, the request should be successful, OR if there is a problem with the user's application preventing submission, errors should be enumerated to the user"
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
|
@ -33,8 +33,8 @@ body:
|
||||||
How can the issue be reliably reproduced? Feel free to include screenshots or other supporting artifacts
|
How can the issue be reliably reproduced? Feel free to include screenshots or other supporting artifacts
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
1. In the test environment, fill out the application for a new domain
|
1. In the test environment, fill out the domain request for a new domain
|
||||||
2. Click the button to trigger a save/submit on the final page and complete the application
|
2. Click the button to trigger a save/submit on the final page and complete the domain request
|
||||||
3. See the error
|
3. See the error
|
||||||
value: |
|
value: |
|
||||||
1.
|
1.
|
||||||
|
|
8
.github/ISSUE_TEMPLATE/story.yml
vendored
8
.github/ISSUE_TEMPLATE/story.yml
vendored
|
@ -19,7 +19,7 @@ body:
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
As an analyst
|
As an analyst
|
||||||
I want the ability to approve a domain application
|
I want the ability to approve a domain request
|
||||||
so that a request can be fulfilled and a new .gov domain can be provisioned
|
so that a request can be fulfilled and a new .gov domain can be provisioned
|
||||||
value: |
|
value: |
|
||||||
As a
|
As a
|
||||||
|
@ -36,11 +36,11 @@ body:
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
- Application sends an email when analysts approve domain requests
|
- Application sends an email when analysts approve domain requests
|
||||||
- Domain application status is "approved"
|
- Domain request status is "approved"
|
||||||
|
|
||||||
Example ("given, when, then" format):
|
Example ("given, when, then" format):
|
||||||
Given that I am an analyst who has finished reviewing a domain application
|
Given that I am an analyst who has finished reviewing a domain request
|
||||||
When I click to approve a domain application
|
When I click to approve a domain request
|
||||||
Then the domain provisioning process should be initiated, and the applicant should receive an email update.
|
Then the domain provisioning process should be initiated, and the applicant should receive an email update.
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
46
.github/workflows/createcachetable.yaml
vendored
Normal file
46
.github/workflows/createcachetable.yaml
vendored
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
# This workflow can be run from the CLI for any environment
|
||||||
|
# gh workflow run createcachetable.yaml -f environment=ENVIRONMENT
|
||||||
|
# OR
|
||||||
|
# cf run-task getgov-ENVIRONMENT --command 'python manage.py createcachetable' --name createcachetable
|
||||||
|
|
||||||
|
name: Create cache table
|
||||||
|
run-name: Create cache table for ${{ github.event.inputs.environment }}
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
environment:
|
||||||
|
type: choice
|
||||||
|
description: Which environment should we create cache table for?
|
||||||
|
options:
|
||||||
|
- stable
|
||||||
|
- staging
|
||||||
|
- development
|
||||||
|
- backup
|
||||||
|
- ky
|
||||||
|
- es
|
||||||
|
- nl
|
||||||
|
- rh
|
||||||
|
- za
|
||||||
|
- gd
|
||||||
|
- rb
|
||||||
|
- ko
|
||||||
|
- ab
|
||||||
|
- rjm
|
||||||
|
- dk
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
createcachetable:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
CF_USERNAME: CF_${{ github.event.inputs.environment }}_USERNAME
|
||||||
|
CF_PASSWORD: CF_${{ github.event.inputs.environment }}_PASSWORD
|
||||||
|
steps:
|
||||||
|
- name: Create cache table for ${{ github.event.inputs.environment }}
|
||||||
|
uses: cloud-gov/cg-cli-tools@main
|
||||||
|
with:
|
||||||
|
cf_username: ${{ secrets[env.CF_USERNAME] }}
|
||||||
|
cf_password: ${{ secrets[env.CF_PASSWORD] }}
|
||||||
|
cf_org: cisa-dotgov
|
||||||
|
cf_space: ${{ github.event.inputs.environment }}
|
||||||
|
cf_command: "run-task getgov-${{ github.event.inputs.environment }} --command 'python manage.py createcachetable' --name createcachetable"
|
41
.github/workflows/test-deploy.yaml
vendored
Normal file
41
.github/workflows/test-deploy.yaml
vendored
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
# This workflow is to for testing a change to our deploy structure and will be deleted when testing finishes
|
||||||
|
|
||||||
|
name: Deploy Main
|
||||||
|
run-name: Run deploy for ${{ github.event.inputs.environment }}
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
environment:
|
||||||
|
type: choice
|
||||||
|
description: Which environment should we run deploy for?
|
||||||
|
options:
|
||||||
|
- development
|
||||||
|
- backup
|
||||||
|
- ky
|
||||||
|
- es
|
||||||
|
- nl
|
||||||
|
- rh
|
||||||
|
- za
|
||||||
|
- gd
|
||||||
|
- rb
|
||||||
|
- ko
|
||||||
|
- ab
|
||||||
|
- rjm
|
||||||
|
- dk
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
CF_USERNAME: CF_${{ github.event.inputs.environment }}_USERNAME
|
||||||
|
CF_PASSWORD: CF_${{ github.event.inputs.environment }}_PASSWORD
|
||||||
|
steps:
|
||||||
|
- name: Deploy to cloud.gov sandbox
|
||||||
|
uses: cloud-gov/cg-cli-tools@main
|
||||||
|
with:
|
||||||
|
cf_username: ${{ secrets[env.CF_USERNAME] }}
|
||||||
|
cf_password: ${{ secrets[env.CF_PASSWORD] }}
|
||||||
|
cf_org: cisa-dotgov
|
||||||
|
cf_space: ${{ github.event.inputs.environment }}
|
||||||
|
cf_command: "push -f ops/manifests/manifest-${{ github.event.inputs.environment }}.yaml --strategy rolling"
|
|
@ -1,4 +1,4 @@
|
||||||
# 15. Use Django-FSM library for domain application state
|
# 15. Use Django-FSM library for domain request state
|
||||||
|
|
||||||
Date: 2022-11-03
|
Date: 2022-11-03
|
||||||
|
|
||||||
|
@ -10,12 +10,12 @@ Accepted
|
||||||
|
|
||||||
The applications that registrants submit for domains move through a variety of
|
The applications that registrants submit for domains move through a variety of
|
||||||
different states or stages as they are processed by CISA staff. Traditionally,
|
different states or stages as they are processed by CISA staff. Traditionally,
|
||||||
there would be a “domain application” data model with a “status” field. The
|
there would be a “domain request” data model with a “status” field. The
|
||||||
rules in the application code that control what changes are permitted to the
|
rules in the application code that control what changes are permitted to the
|
||||||
statuses are called “domain logic”.
|
statuses are called “domain logic”.
|
||||||
|
|
||||||
In a large piece of software, domain logic often spreads around the code base
|
In a large piece of software, domain logic often spreads around the code base
|
||||||
because while handling a single request like “mark this application as
|
because while handling a single request like “mark this domain request as
|
||||||
approved”, requirements can be enforced at many different points during the
|
approved”, requirements can be enforced at many different points during the
|
||||||
process.
|
process.
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ states and can change states (or “transition”) according to fixed rules.
|
||||||
We will use the django-fsm library to represent the status of our domain
|
We will use the django-fsm library to represent the status of our domain
|
||||||
registration applications as a finite state machine. The library allows us to
|
registration applications as a finite state machine. The library allows us to
|
||||||
list what statuses are possible and describe which state transitions are
|
list what statuses are possible and describe which state transitions are
|
||||||
possible (e.g. Can an approved application ever be marked as “in-process”?).
|
possible (e.g. Can an approved domain request ever be marked as “in-process”?).
|
||||||
|
|
||||||
## Consequences
|
## Consequences
|
||||||
|
|
||||||
|
|
|
@ -8,17 +8,17 @@ Accepted
|
||||||
|
|
||||||
## Context
|
## Context
|
||||||
|
|
||||||
The application form by which registrants apply for a .gov domain is presented over many pages.
|
The domain request form by which registrants apply for a .gov domain is presented over many pages.
|
||||||
|
|
||||||
Because we use server-side rendering, each page of the application is a unique HTML page with form fields surrounded by a form tag.
|
Because we use server-side rendering, each page of the domain request is a unique HTML page with form fields surrounded by a form tag.
|
||||||
|
|
||||||
Needing a way to coordinate state between the pages as a user fills in their application, we initially used the Form wizard from [django-formtools](https://django-formtools.readthedocs.io/en/latest/wizard.html). This eventually proved unworkable due to the lack of native ability to have more than one Django form object displayed on a single HTML page.
|
Needing a way to coordinate state between the pages as a user fills in their domain request, we initially used the Form wizard from [django-formtools](https://django-formtools.readthedocs.io/en/latest/wizard.html). This eventually proved unworkable due to the lack of native ability to have more than one Django form object displayed on a single HTML page.
|
||||||
|
|
||||||
However, a significant portion of the user workflow had already been coded, so it seemed prudent to port some of the formtools logic into our codebase.
|
However, a significant portion of the user workflow had already been coded, so it seemed prudent to port some of the formtools logic into our codebase.
|
||||||
|
|
||||||
## Decision
|
## Decision
|
||||||
|
|
||||||
To maintain each page of the domain application as its own Django view class, inheriting common code from a parent class.
|
To maintain each page of the domain request as its own Django view class, inheriting common code from a parent class.
|
||||||
|
|
||||||
To maintain Django form and formset class in accordance with the Django models whose data they collect, independently of the pages on which they appear.
|
To maintain Django form and formset class in accordance with the Django models whose data they collect, independently of the pages on which they appear.
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ Approved
|
||||||
## Context
|
## Context
|
||||||
|
|
||||||
Our application needs to be able to send email to applicants for various
|
Our application needs to be able to send email to applicants for various
|
||||||
purposes including notifying them that their application has been submitted.
|
purposes including notifying them that their domain request has been submitted.
|
||||||
We need infrastructure for programmatically sending email. Amazon Web Services
|
We need infrastructure for programmatically sending email. Amazon Web Services
|
||||||
(AWS) provides the Simple Email Service (SES) that can do that. CISA can
|
(AWS) provides the Simple Email Service (SES) that can do that. CISA can
|
||||||
provide access to AWS SES for our application.
|
provide access to AWS SES for our application.
|
||||||
|
|
|
@ -8,8 +8,7 @@ Accepted
|
||||||
|
|
||||||
## Context
|
## Context
|
||||||
|
|
||||||
CISA needs a way to perform administrative actions to manage the new get.gov application as well as the .gov domain
|
CISA needs a way to perform administrative actions to manage the new get.gov application as well as the .gov domain requests submitted. Analysts need to be able to view, review, and approve domain requests. Other
|
||||||
application requests submitted. Analysts need to be able to view, review, and approve domain applications. Other
|
|
||||||
dashboard views, reports, searches (with filters and sorting) are also highly desired.
|
dashboard views, reports, searches (with filters and sorting) are also highly desired.
|
||||||
|
|
||||||
## Decision
|
## Decision
|
||||||
|
|
|
@ -8,13 +8,13 @@ Accepted
|
||||||
|
|
||||||
## Context
|
## Context
|
||||||
|
|
||||||
Historically, the .gov vendor managed initial identity verification and organizational affiliation for users that request a .gov domain. With the new registrar, _any user with a valid Login.gov account_ will be able to make a request. As a primary layer of abuse prevention (i.e., DDoSing the registry program with illegitimate requests), we need a way to stop new users from submitting multiple domain requests before they are known to the .gov registry. In this case, "known" means they have at least one approved domain application or existing domain.
|
Historically, the .gov vendor managed initial identity verification and organizational affiliation for users that request a .gov domain. With the new registrar, _any user with a valid Login.gov account_ will be able to make a request. As a primary layer of abuse prevention (i.e., DDoSing the registry program with illegitimate requests), we need a way to stop new users from submitting multiple domain requests before they are known to the .gov registry. In this case, "known" means they have at least one approved domain request or existing domain.
|
||||||
|
|
||||||
## Considered Options
|
## Considered Options
|
||||||
|
|
||||||
**Option 1:** Users will not be able to submit any new applications if they have 0 prior approved applications OR prior registered .gov domains. We would add a page alert informing the user that they cannot submit their application because they have an application in one of these "3" statuses (Submitted, In Review or Action Needed). They would still be able to create and edit new applications, just not submit them. The benefits of this option are that it would allow users to have multiple applications essentially in "draft mode" that are queued up and ready for submission after they are permitted to submit.
|
**Option 1:** Users will not be able to submit any new applications if they have 0 prior approved applications OR prior registered .gov domains. We would add a page alert informing the user that they cannot submit their application because they have a domain request in one of these "3" statuses (Submitted, In Review or Action Needed). They would still be able to create and edit new applications, just not submit them. The benefits of this option are that it would allow users to have multiple applications essentially in "draft mode" that are queued up and ready for submission after they are permitted to submit.
|
||||||
|
|
||||||
**Option 2:** Users will not be able to submit any new applications if they have 0 prior approved applications OR prior registered .gov domains. Additionally, we would remove the ability to edit any application with the started/withdrawn/rejected status, or start a new application. The benefit of this option is that a user would not be able to begin an action (submitting an application) that they are not allowed to complete.
|
**Option 2:** Users will not be able to submit any new applications if they have 0 prior approved applications OR prior registered .gov domains. Additionally, we would remove the ability to edit any application with the started/withdrawn/rejected status, or start a new application. The benefit of this option is that a user would not be able to begin an action (submitting a domain request) that they are not allowed to complete.
|
||||||
|
|
||||||
## Decision
|
## Decision
|
||||||
|
|
||||||
|
|
74
docs/architecture/decisions/0025-caching.md
Normal file
74
docs/architecture/decisions/0025-caching.md
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
# 24. Production Release Cadence
|
||||||
|
|
||||||
|
Date: 2024-14-02
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
In Review
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
We experienced problems with our Cloudfront caching infrastructure early in our November launch. In response, we turned off caching across the application. We would like to utilize caching again without incurring the same issues.
|
||||||
|
|
||||||
|
Details:
|
||||||
|
Originally, Cloudfront was utilized to provide caching capabilities in our application. All incoming HTTP requests first go through a Cloudfront endpoint, which has a caching infrastructure enabled by default. Cloudfront then decides whether to pass each request to our running Django app inside cloud.gov or if it will respond to with cached data. The big problem with this feature is Cloudfront's caching has a default timeout of 24-hours, which we cannot control. This led to issues on our November launch; Incidents reported include the following...
|
||||||
|
- Users couldn't utilize login.gov properly and had to wait a day before they would be able to login. This was traced back to the 24-hour cache timeout.
|
||||||
|
- Changes made by admins would not be reflected in the app (due to the cached data not updating)
|
||||||
|
|
||||||
|
To resolve these issues, we added "no cache" headers throughout our application. Currently, every single HTTP response that comes from Django says "Cache control: no cache" in the headers, which instructs Cloudfront not to cache the associated data. This effectively removes Cloudfront caching for us.
|
||||||
|
|
||||||
|
Although we could leave our architecture as-is, we decided to investigate options for improving our use of caching (instead of just disabling it completely).
|
||||||
|
|
||||||
|
## Considered Options
|
||||||
|
|
||||||
|
**Option 1:** Cache static resources using Whitenoise
|
||||||
|
|
||||||
|
Caching static resources should pose little risk to our application's functionality. Currently, every static resource from /public/... is hitting our Django application inside of Cloud.gov. We already use a Django plugin called whitenoise that can do hash-based linking to static assets so that they can be cached forever by Cloudfront. (If the content changes, then the hash changes, then it results in a different filename.)
|
||||||
|
|
||||||
|
See ticket [#1371](https://github.com/cisagov/manage.get.gov/issues/1371) for more information.
|
||||||
|
|
||||||
|
**Option 2:** Leave things as-is (we had some discussion on whether or not caching static pages will make enough of a difference to be worth the effort)
|
||||||
|
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
We decided on Option 2 - leave things as-is (for now).
|
||||||
|
|
||||||
|
Preliminary analysis suggest that implementing caching on static pages will result in negligible improvements to our application load time. A quick look at Kibana logs suggests most of these resources take less than 10ms to load...
|
||||||
|

|
||||||
|
|
||||||
|
If we look at average load times in Kibana (here is [the Kibana page with preloaded query](https://logs.fr.cloud.gov/app/visualize#/create?_a=(filters:!(('$state':(store:appState),meta:(alias:!n,disabled:!f,index:'logs-app*',key:'@cf.app',negate:!f,params:(query:getgov-stable),type:phrase),query:(match_phrase:('@cf.app':getgov-stable)))),linked:!f,query:(language:lucene,query:''),uiState:(),vis:(aggs:!((enabled:!t,id:'1',params:(customLabel:'Average%20Response%20Time%20in%20ms',field:rtr.response_time_ms),schema:metric,type:avg),(enabled:!t,id:'2',params:(drop_partials:!f,extended_bounds:(),field:'@timestamp',interval:d,min_doc_count:1,scaleMetricValues:!f,timeRange:(from:now-20d,to:now),useNormalizedEsInterval:!t),schema:segment,type:date_histogram)),params:(addLegend:!t,addTimeMarker:!f,addTooltip:!t,categoryAxes:!((id:CategoryAxis-1,labels:(filter:!t,show:!t,truncate:100),position:bottom,scale:(type:linear),show:!t,style:(),title:(),type:category)),grid:(categoryLines:!f),labels:(show:!f),legendPosition:right,seriesParams:!((data:(id:'1',label:'Average%20Response%20Time%20in%20ms'),drawLinesBetweenPoints:!t,lineWidth:2,mode:stacked,show:!t,showCircles:!t,type:histogram,valueAxis:ValueAxis-1)),thresholdLine:(color:%23E7664C,show:!f,style:full,value:10,width:1),times:!(),type:histogram,valueAxes:!((id:ValueAxis-1,labels:(filter:!f,rotate:0,show:!t,truncate:100),name:LeftAxis-1,position:left,scale:(mode:normal,type:linear),show:!t,style:(),title:(text:'Average%20Response%20Time%20in%20ms'),type:value))),title:'',type:histogram))&_g=(filters:!(),refreshInterval:(pause:!t,value:0),time:(from:now-2w,to:now))&indexPattern=logs-app*&type=histogram)), it looks like we are doing great for load times in stable (using the rtr.response_time_ms metric), staying under 200ms (in the last 4 weeks) and usually hovering around 40-80ms. Some google searching suggests that "an ideal page load time is between 0-2 seconds, but 3 seconds is also considered to be an acceptable score. Anything above 3 seconds increases the likelihood of visitors leaving your site." (Quote shamelessly copied from Sematex)
|
||||||
|

|
||||||
|
|
||||||
|
NOTE: While we considered implementing caching in a sandbox (See footnote) in order to examine risks and benefits of OPTION 1 in more detail, this incurred more overhead than expected (mainly due to poor documentation). Therefore, we decided it was not worth the investment.
|
||||||
|
|
||||||
|
Therefore, implementing caching using Whitenoise is not currently worth it for the following reasons;
|
||||||
|
- Minimal gains: We would only be caching static files which would not result in a large performance boost
|
||||||
|
- Risks: Incurs risk of unforeseen loading issues (we can’t entirely rule out that we won’t run into issues like we did in our November launch incident). Although we don’t think static files should pose a problem, due diligence would call us to monitor for any unforeseen issues that might arise, which adds cost to this project that doesn’t seem proportional to the gains.
|
||||||
|
- Maintenance: We would have to provide custom settings in cloudfront (coordinated through Cameron) for any sandboxes and other environments where caching is enabled. If we move down the route of utilizing CDN, it would be good for every environment to have this service enabled so our dev environments reflect stable settings. This could possibly introduce some overhead and maintenance issues. (Although further investigation might reveal these to be negligible.)
|
||||||
|
|
||||||
|
Overall, it is recommended that we SHELVE this caching endeavor for a future scenario where we have exhausted other (likely more lucrative) options for performance improvements. If we then still need to make improvements to our load times, perhaps we can revisit this and examine caching not only static files, but other resources as well (with caution).
|
||||||
|
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
We will forgo (negligible) load-time improvements by leaving caching off.
|
||||||
|
|
||||||
|
## (Footnote - How to implement caching)
|
||||||
|
Here are notes for implementing caching using whitenoise should we decide to pick this up again in the future;
|
||||||
|
|
||||||
|
1 - Add caching capability to a sandbox using the following steps (or [following documentation for command line](https://cloud.gov/docs/services/external-domain-service/))
|
||||||
|
- Log-in to the cloud.gov website
|
||||||
|
- [Navigate to "Services"](https://dashboard.fr.cloud.gov/services). Click "Add Service"...
|
||||||
|
- Choose "Marketplace Service"
|
||||||
|
- For the fields, select Cloud Foundry, Organization = "cisa-dotgov", Space = "[your sandbox. eg. "nl"]". Click "Next"
|
||||||
|
- For the Service, select "External Domain". Click "Next"
|
||||||
|
- For the Plan, select "domain-with-cdn" (here is [documentation on CDN](https://cloud.gov/docs/management/custom-domains/))
|
||||||
|
- If you choose to bind the app, a JSON string will be required (we believe this should do it: {"domains": "example.gov"}, where "example.gov" is replaced with the domain name you want to use for this application)
|
||||||
|
Before you can continue, work with Cameron to setup the DNS in AWS (use the following documentation linked below):
|
||||||
|
https://cloud.gov/docs/services/external-domain-service/
|
||||||
|
- Once the DNS is setup, you *should* be able to continue. We did not test this.
|
||||||
|
|
||||||
|
2- Enable caching in the code with Whitenoise (see [documentation on Whitenoise Caching](https://whitenoise.readthedocs.io/en/latest/djangohtml#add-compression-and-caching-support))
|
||||||
|
|
||||||
|
3- Take performance measurements before/after caching is enabled to determine cost-benefits of implementing caching. (NOTE: [lighthouse](https://developer.chrome.com/blog/lighthouse-load-performance) might be useful for this step)
|
|
@ -18,13 +18,13 @@ Deployment_Node(aws, "AWS GovCloud", "Amazon Web Services Region") {
|
||||||
Deployment_Node(organization, "get.gov organization") {
|
Deployment_Node(organization, "get.gov organization") {
|
||||||
Deployment_Node(sandbox, "sandbox space") {
|
Deployment_Node(sandbox, "sandbox space") {
|
||||||
System_Boundary(dashboard_sandbox, "get.gov registrar") {
|
System_Boundary(dashboard_sandbox, "get.gov registrar") {
|
||||||
Container(getgov_app_sandbox, "Registrar Application", "Python, Django", "Delivers static HTML/CSS and forms")
|
Container(getgov_app_sandbox, "Registrar Domain Request", "Python, Django", "Delivers static HTML/CSS and forms")
|
||||||
ContainerDb(dashboard_db_sandbox, "sandbox PostgreSQL Database", "AWS RDS", "Stores agency information and reports")
|
ContainerDb(dashboard_db_sandbox, "sandbox PostgreSQL Database", "AWS RDS", "Stores agency information and reports")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Deployment_Node(stable, "stable space") {
|
Deployment_Node(stable, "stable space") {
|
||||||
System_Boundary(dashboard_stable, "get.gov registrar") {
|
System_Boundary(dashboard_stable, "get.gov registrar") {
|
||||||
Container(getgov_app_stable, "Registrar Application", "Python, Django", "Delivers static HTML/CSS and forms")
|
Container(getgov_app_stable, "Registrar Domain Request", "Python, Django", "Delivers static HTML/CSS and forms")
|
||||||
ContainerDb(dashboard_db_stable, "stable PostgreSQL Database", "AWS RDS", "Stores agency information and reports")
|
ContainerDb(dashboard_db_stable, "stable PostgreSQL Database", "AWS RDS", "Stores agency information and reports")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,9 @@
|
||||||
This diagram connects the data models along with various workflow stages.
|
This diagram connects the data models along with various workflow stages.
|
||||||
|
|
||||||
1. The applicant starts the process at `/request` interacting with the
|
1. The applicant starts the process at `/request` interacting with the
|
||||||
`DomainApplication` object.
|
`DomainRequest` object.
|
||||||
|
|
||||||
2. The analyst approves the application using the `DomainApplication`'s
|
2. The analyst approves the domain request using the `DomainRequest`'s
|
||||||
`approve()` method which creates many related objects: `UserDomainRole`,
|
`approve()` method which creates many related objects: `UserDomainRole`,
|
||||||
`Domain`, and `DomainInformation`.
|
`Domain`, and `DomainInformation`.
|
||||||
|
|
||||||
|
@ -36,8 +36,8 @@ $ docker run -v $(pwd):$(pwd) -w $(pwd) -it plantuml/plantuml -tsvg model_timeli
|
||||||
allowmixing
|
allowmixing
|
||||||
left to right direction
|
left to right direction
|
||||||
|
|
||||||
class DomainApplication {
|
class DomainRequest {
|
||||||
Application for a domain
|
Request for a domain
|
||||||
--
|
--
|
||||||
creator (User)
|
creator (User)
|
||||||
investigator (User)
|
investigator (User)
|
||||||
|
@ -66,7 +66,7 @@ note left of User
|
||||||
<b>username</b> is the Login UUID
|
<b>username</b> is the Login UUID
|
||||||
end note
|
end note
|
||||||
|
|
||||||
DomainApplication -l- User : creator, investigator
|
DomainRequest -l- User : creator, investigator
|
||||||
|
|
||||||
class Contact {
|
class Contact {
|
||||||
Contact info for a person
|
Contact info for a person
|
||||||
|
@ -80,7 +80,7 @@ class Contact {
|
||||||
--
|
--
|
||||||
}
|
}
|
||||||
|
|
||||||
DomainApplication *-r-* Contact : authorizing_official, submitter, other_contacts
|
DomainRequest *-r-* Contact : authorizing_official, submitter, other_contacts
|
||||||
|
|
||||||
class DraftDomain {
|
class DraftDomain {
|
||||||
Requested domain
|
Requested domain
|
||||||
|
@ -89,7 +89,7 @@ class DraftDomain {
|
||||||
--
|
--
|
||||||
}
|
}
|
||||||
|
|
||||||
DomainApplication -l- DraftDomain : requested_domain
|
DomainRequest -l- DraftDomain : requested_domain
|
||||||
|
|
||||||
class Domain {
|
class Domain {
|
||||||
Approved domain
|
Approved domain
|
||||||
|
@ -99,21 +99,21 @@ class Domain {
|
||||||
<b>EPP methods</b>
|
<b>EPP methods</b>
|
||||||
}
|
}
|
||||||
|
|
||||||
DomainApplication .right[#blue].> Domain : approve()
|
DomainRequest .right[#blue].> Domain : approve()
|
||||||
|
|
||||||
class DomainInformation {
|
class DomainInformation {
|
||||||
Registrar information on a domain
|
Registrar information on a domain
|
||||||
--
|
--
|
||||||
domain (Domain)
|
domain (Domain)
|
||||||
domain_application (DomainApplication)
|
domain_request (DomainRequest)
|
||||||
security_email
|
security_email
|
||||||
--
|
--
|
||||||
Request information...
|
Request information...
|
||||||
}
|
}
|
||||||
|
|
||||||
DomainInformation -- Domain
|
DomainInformation -- Domain
|
||||||
DomainInformation -- DomainApplication
|
DomainInformation -- DomainRequest
|
||||||
DomainApplication .[#blue].> DomainInformation : approve()
|
DomainRequest .[#blue].> DomainInformation : approve()
|
||||||
|
|
||||||
class UserDomainRole {
|
class UserDomainRole {
|
||||||
Permissions
|
Permissions
|
||||||
|
@ -125,7 +125,7 @@ class UserDomainRole {
|
||||||
}
|
}
|
||||||
UserDomainRole -- User
|
UserDomainRole -- User
|
||||||
UserDomainRole -- Domain
|
UserDomainRole -- Domain
|
||||||
DomainApplication .[#blue].> UserDomainRole : approve()
|
DomainRequest .[#blue].> UserDomainRole : approve()
|
||||||
|
|
||||||
class DomainInvitation {
|
class DomainInvitation {
|
||||||
Email invitations sent
|
Email invitations sent
|
||||||
|
@ -139,10 +139,10 @@ DomainInvitation -- Domain
|
||||||
DomainInvitation .[#green].> UserDomainRole : User.on_each_login()
|
DomainInvitation .[#green].> UserDomainRole : User.on_each_login()
|
||||||
|
|
||||||
actor applicant #Red
|
actor applicant #Red
|
||||||
applicant -d-> DomainApplication : **/request**
|
applicant -d-> DomainRequest : **/request**
|
||||||
|
|
||||||
actor analyst #Blue
|
actor analyst #Blue
|
||||||
analyst -[#blue]-> DomainApplication : **approve()**
|
analyst -[#blue]-> DomainRequest : **approve()**
|
||||||
|
|
||||||
actor user1 #Green
|
actor user1 #Green
|
||||||
user1 -[#green]-> Domain : **/domain/<id>/nameservers**
|
user1 -[#green]-> Domain : **/domain/<id>/nameservers**
|
||||||
|
|
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
@ -39,8 +39,8 @@ class "registrar.Contact <Registrar>" as registrar.Contact #d6f4e9 {
|
||||||
registrar.Contact -- registrar.User
|
registrar.Contact -- registrar.User
|
||||||
|
|
||||||
|
|
||||||
class "registrar.DomainApplication <Registrar>" as registrar.DomainApplication #d6f4e9 {
|
class "registrar.DomainRequest <Registrar>" as registrar.DomainRequest #d6f4e9 {
|
||||||
domain application
|
domain request
|
||||||
--
|
--
|
||||||
+ id (BigAutoField)
|
+ id (BigAutoField)
|
||||||
+ created_at (DateTimeField)
|
+ created_at (DateTimeField)
|
||||||
|
@ -77,15 +77,15 @@ class "registrar.DomainApplication <Registrar>" as registrar.DomainApplication #
|
||||||
# other_contacts (ManyToManyField)
|
# other_contacts (ManyToManyField)
|
||||||
--
|
--
|
||||||
}
|
}
|
||||||
registrar.DomainApplication -- registrar.User
|
registrar.DomainRequest -- registrar.User
|
||||||
registrar.DomainApplication -- registrar.User
|
registrar.DomainRequest -- registrar.User
|
||||||
registrar.DomainApplication -- registrar.Contact
|
registrar.DomainRequest -- registrar.Contact
|
||||||
registrar.DomainApplication -- registrar.DraftDomain
|
registrar.DomainRequest -- registrar.DraftDomain
|
||||||
registrar.DomainApplication -- registrar.Domain
|
registrar.DomainRequest -- registrar.Domain
|
||||||
registrar.DomainApplication -- registrar.Contact
|
registrar.DomainRequest -- registrar.Contact
|
||||||
registrar.DomainApplication *--* registrar.Website
|
registrar.DomainRequest *--* registrar.Website
|
||||||
registrar.DomainApplication *--* registrar.Website
|
registrar.DomainRequest *--* registrar.Website
|
||||||
registrar.DomainApplication *--* registrar.Contact
|
registrar.DomainRequest *--* registrar.Contact
|
||||||
|
|
||||||
|
|
||||||
class "registrar.DomainInformation <Registrar>" as registrar.DomainInformation #d6f4e9 {
|
class "registrar.DomainInformation <Registrar>" as registrar.DomainInformation #d6f4e9 {
|
||||||
|
@ -95,7 +95,7 @@ class "registrar.DomainInformation <Registrar>" as registrar.DomainInformation #
|
||||||
+ created_at (DateTimeField)
|
+ created_at (DateTimeField)
|
||||||
+ updated_at (DateTimeField)
|
+ updated_at (DateTimeField)
|
||||||
~ creator (ForeignKey)
|
~ creator (ForeignKey)
|
||||||
~ domain_application (OneToOneField)
|
~ domain_request (OneToOneField)
|
||||||
+ organization_type (CharField)
|
+ organization_type (CharField)
|
||||||
+ federally_recognized_tribe (BooleanField)
|
+ federally_recognized_tribe (BooleanField)
|
||||||
+ state_recognized_tribe (BooleanField)
|
+ state_recognized_tribe (BooleanField)
|
||||||
|
@ -124,7 +124,7 @@ class "registrar.DomainInformation <Registrar>" as registrar.DomainInformation #
|
||||||
--
|
--
|
||||||
}
|
}
|
||||||
registrar.DomainInformation -- registrar.User
|
registrar.DomainInformation -- registrar.User
|
||||||
registrar.DomainInformation -- registrar.DomainApplication
|
registrar.DomainInformation -- registrar.DomainRequest
|
||||||
registrar.DomainInformation -- registrar.Contact
|
registrar.DomainInformation -- registrar.Contact
|
||||||
registrar.DomainInformation -- registrar.Domain
|
registrar.DomainInformation -- registrar.Domain
|
||||||
registrar.DomainInformation -- registrar.Contact
|
registrar.DomainInformation -- registrar.Contact
|
||||||
|
|
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 72 KiB |
BIN
docs/architecture/doc-images/caching-average-load-times.png
Normal file
BIN
docs/architecture/doc-images/caching-average-load-times.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 434 KiB |
BIN
docs/architecture/doc-images/caching-rtr-logs.png
Normal file
BIN
docs/architecture/doc-images/caching-rtr-logs.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 200 KiB |
|
@ -78,7 +78,7 @@ Get the secrets from Cloud.gov by running `cf env getgov-YOURSANDBOX`. More info
|
||||||
The endpoint /admin can be used to view and manage site content, including but not limited to user information and the list of current applications in the database. To be able to view and use /admin locally:
|
The endpoint /admin can be used to view and manage site content, including but not limited to user information and the list of current applications in the database. To be able to view and use /admin locally:
|
||||||
|
|
||||||
1. Login via login.gov
|
1. Login via login.gov
|
||||||
2. Go to the home page and make sure you can see the part where you can submit an application
|
2. Go to the home page and make sure you can see the part where you can submit a domain request
|
||||||
3. Go to /admin and it will tell you that UUID is not authorized, copy that UUID for use in 4
|
3. Go to /admin and it will tell you that UUID is not authorized, copy that UUID for use in 4
|
||||||
4. in src/registrar/fixtures_users.py add to the `ADMINS` list in that file by adding your UUID as your username along with your first and last name. See below:
|
4. in src/registrar/fixtures_users.py add to the `ADMINS` list in that file by adding your UUID as your username along with your first and last name. See below:
|
||||||
|
|
||||||
|
@ -93,14 +93,14 @@ The endpoint /admin can be used to view and manage site content, including but n
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
5. In the browser, navigate to /admin. To verify that all is working correctly, under "domain applications" you should see fake domains with various fake statuses.
|
5. In the browser, navigate to /admin. To verify that all is working correctly, under "domain requests" you should see fake domains with various fake statuses.
|
||||||
6. Add an optional email key/value pair
|
6. Add an optional email key/value pair
|
||||||
|
|
||||||
### Adding an Analyst to /admin
|
### Adding an Analyst to /admin
|
||||||
Analysts are a variant of the admin role with limited permissions. The process for adding an Analyst is much the same as adding an admin:
|
Analysts are a variant of the admin role with limited permissions. The process for adding an Analyst is much the same as adding an admin:
|
||||||
|
|
||||||
1. Login via login.gov (if you already exist as an admin, you will need to create a separate login.gov account for this: i.e. first.last+1@email.com)
|
1. Login via login.gov (if you already exist as an admin, you will need to create a separate login.gov account for this: i.e. first.last+1@email.com)
|
||||||
2. Go to the home page and make sure you can see the part where you can submit an application
|
2. Go to the home page and make sure you can see the part where you can submit a domain request
|
||||||
3. Go to /admin and it will tell you that UUID is not authorized, copy that UUID for use in 4 (this will be a different UUID than the one obtained from creating an admin)
|
3. Go to /admin and it will tell you that UUID is not authorized, copy that UUID for use in 4 (this will be a different UUID than the one obtained from creating an admin)
|
||||||
4. in src/registrar/fixtures_users.py add to the `STAFF` list in that file by adding your UUID as your username along with your first and last name. See below:
|
4. in src/registrar/fixtures_users.py add to the `STAFF` list in that file by adding your UUID as your username along with your first and last name. See below:
|
||||||
|
|
||||||
|
@ -145,7 +145,7 @@ You can change the logging verbosity, if needed. Do a web search for "django log
|
||||||
|
|
||||||
## Mock data
|
## Mock data
|
||||||
|
|
||||||
[load.py](../../src/registrar/management/commands/load.py) called from docker-compose (locally) and reset-db.yml (upper) loads the fixtures from [fixtures_user.py](../../src/registrar/fixtures_users.py) and [fixtures_applications.py](../../src/registrar/fixtures_applications.py), giving you some test data to play with while developing.
|
[load.py](../../src/registrar/management/commands/load.py) called from docker-compose (locally) and reset-db.yml (upper) loads the fixtures from [fixtures_user.py](../../src/registrar/fixtures_users.py) and [fixtures_domain_requests.py](../../src/registrar/fixtures_domain_requests.py), giving you some test data to play with while developing.
|
||||||
|
|
||||||
See the [database-access README](./database-access.md) for information on how to pull data to update these fixtures.
|
See the [database-access README](./database-access.md) for information on how to pull data to update these fixtures.
|
||||||
|
|
||||||
|
|
|
@ -24,8 +24,8 @@
|
||||||
## Status Change Approved
|
## Status Change Approved
|
||||||
- Starting Location: Django Admin
|
- Starting Location: Django Admin
|
||||||
- Workflow: Analyst Admin
|
- Workflow: Analyst Admin
|
||||||
- Workflow Step: Click "Domain applications" -> Click an application in a status of "submitted", "In review", "rejected", or "ineligible" -> Click status dropdown -> (select "approved") -> click "Save"
|
- Workflow Step: Click "domain requests" -> Click a domain request in a status of "submitted", "In review", "rejected", or "ineligible" -> Click status dropdown -> (select "approved") -> click "Save"
|
||||||
- Notes: Note that this will send an email to the submitter (email listed on Your Contact Information). To test this with your own email, you need to create an application, then set the status to "approved". This will send you an email.
|
- Notes: Note that this will send an email to the submitter (email listed on Your Contact Information). To test this with your own email, you need to create a domain request, then set the status to "approved". This will send you an email.
|
||||||
- [Email Content](https://github.com/cisagov/manage.get.gov/blob/main/src/registrar/templates/emails/status_change_approved.txt)
|
- [Email Content](https://github.com/cisagov/manage.get.gov/blob/main/src/registrar/templates/emails/status_change_approved.txt)
|
||||||
|
|
||||||
### Status Change Approved Subject
|
### Status Change Approved Subject
|
||||||
|
@ -35,8 +35,8 @@
|
||||||
## Status Change Rejected
|
## Status Change Rejected
|
||||||
- Starting Location: Django Admin
|
- Starting Location: Django Admin
|
||||||
- Workflow: Analyst Admin
|
- Workflow: Analyst Admin
|
||||||
- Workflow Step: Click "Domain applications" -> Click an application in a status of "In review", or "approved" -> Click status dropdown -> (select "rejected") -> click "Save"
|
- Workflow Step: Click "domain requests" -> Click a domain request in a status of "In review", or "approved" -> Click status dropdown -> (select "rejected") -> click "Save"
|
||||||
- Notes: Note that this will send an email to the submitter (email listed on Your Contact Information). To test this with your own email, you need to create an application, then set the status to "in review" (and click save). Then, go back to the same application and set the status to "rejected". This will send you an email.
|
- Notes: Note that this will send an email to the submitter (email listed on Your Contact Information). To test this with your own email, you need to create a domain request, then set the status to "in review" (and click save). Then, go back to the same application and set the status to "rejected". This will send you an email.
|
||||||
- [Email Content](https://github.com/cisagov/manage.get.gov/blob/main/src/registrar/templates/emails/status_change_rejected.txt)
|
- [Email Content](https://github.com/cisagov/manage.get.gov/blob/main/src/registrar/templates/emails/status_change_rejected.txt)
|
||||||
|
|
||||||
### Status Change Rejected Subject
|
### Status Change Rejected Subject
|
||||||
|
|
|
@ -114,7 +114,7 @@ that can be used for specific tasks.
|
||||||
## Cloud.gov dashboard
|
## Cloud.gov dashboard
|
||||||
|
|
||||||
At <https://dashboard.fr.cloud.gov/applications> there is a list for all of the
|
At <https://dashboard.fr.cloud.gov/applications> there is a list for all of the
|
||||||
applications that a Cloud.gov user has access to. Clicking on an application
|
applications that a Cloud.gov user has access to. Clicking on a domain request
|
||||||
goes to a screen for that individual application, e.g.
|
goes to a screen for that individual application, e.g.
|
||||||
<https://dashboard.fr.cloud.gov/applications/2oBn9LBurIXUNpfmtZCQTCHnxUM/53b88024-1492-46aa-8fb6-1429bdb35f95/summary>.
|
<https://dashboard.fr.cloud.gov/applications/2oBn9LBurIXUNpfmtZCQTCHnxUM/53b88024-1492-46aa-8fb6-1429bdb35f95/summary>.
|
||||||
On that page is a left-hand link for "Log Stream" e.g.
|
On that page is a left-hand link for "Log Stream" e.g.
|
||||||
|
|
30
docs/operations/runbooks/downtime_incident_management.md
Normal file
30
docs/operations/runbooks/downtime_incident_management.md
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
# Downtime Incident Management Runbook
|
||||||
|
|
||||||
|
Our team has agreed upon steps for handling incidents that cause our site to go offline or become unusable for users. For this document, an incident refers to one in which manage.get.gov is offline or displaying error 400/500 HTTP errors on all pages. However, for this document to apply the cause of the problem must be a critical bug in our code or one of our providers having an outage, not to be confused with a cyber security incident. This document should not be used in response to any type of cyber security incident.
|
||||||
|
|
||||||
|
## Response management rules
|
||||||
|
|
||||||
|
The following set of rules should be followed while an incident is in progress.
|
||||||
|
|
||||||
|
- The person who first notices that the site is down is responsible for using @here and notifying in #dotgov-announce that production is down.
|
||||||
|
- This applies to any team member, including new team members and non-developers.
|
||||||
|
- If no engineer has acknowledged the announcement within 10 minutes, whoever discovered the site was down should call each developer via the Slack DM huddle feature. If there is no response, this should escalate to a phone call.
|
||||||
|
- When calling, go down the [phone call list](https://docs.google.com/document/d/1k4r-1MNCfW8EXSXa-tqJQzOvJxQv0ARvHnOjjAH0LII/edit) from top to bottom until someone answers who is available to help.
|
||||||
|
- If this incident occurs outside of regular working hours, choosing to help is on a volunteer basis, and answering a call doesn't mean an individual is truly available to assist.
|
||||||
|
- Once an engineer is online, they should immediately start a huddle in the #dotgov-redalert channel to begin troubleshooting.
|
||||||
|
- All available engineers should join the huddle once they see it.
|
||||||
|
- If downtime occurs outside of working hours, team members who are off for the day may still be pinged and called but are not required to join if unavailable to do so.
|
||||||
|
- Uncomment the [banner on get.gov](https://github.com/cisagov/get.gov/blob/0365d3d34b041cc9353497b2b5f81b6ab7fe75a9/_includes/header.html#L9), so it is transparent to users that we know about the issue on manage.get.gov.
|
||||||
|
- Designers or Developers should be able to make this change; if designers are online and can help with this task, that will allow developers to focus on fixing the bug.
|
||||||
|
|
||||||
|
## Post Incident
|
||||||
|
|
||||||
|
The following checklist should be followed after the site is back up and running.
|
||||||
|
|
||||||
|
- [ ] Message in #dotgov-announce with an @here saying the issue is resolved
|
||||||
|
- [ ] Remove the [banner on get.gov](https://github.com/cisagov/get.gov/blob/0365d3d34b041cc9353497b2b5f81b6ab7fe75a9/_includes/header.html#L9) by commenting it out.
|
||||||
|
- [ ] Write up what happened and when; if the cause is already known, write that as well. This is a draft for internal communications and not for any public facing site and can be as simple as using bullet points.
|
||||||
|
- [ ] If the cause is not known yet, developers should investigate the issue as the highest priority task.
|
||||||
|
- [ ] As close to the event as possible, such as the next day, perform a team incident retro that is an hour long. The goal of this meeting should be to inform all team members what happened and what is being done now and to collect feedback on what could have been done better. This is where the draft write up of what happened will be useful.
|
||||||
|
- [ ] After the retro and once the bug is fully identified, an engineer should assist in writing an incident report and may be as detailed as possible for future team members to refer to. That document should be places in the [Incidents folder](https://drive.google.com/drive/folders/1LPVICVpI4Xb5KGdrNkSwhX2OAJ6hYTyu).
|
||||||
|
- [ ] After creating the document above, the lead engineer make a draft of content that will go in the get.gov Incidents section. This Word document should be shared and reviewed by the product team before a developer adds it to get.gov.
|
|
@ -4,7 +4,7 @@ applications:
|
||||||
buildpacks:
|
buildpacks:
|
||||||
- python_buildpack
|
- python_buildpack
|
||||||
path: ../../src
|
path: ../../src
|
||||||
instances: 1
|
instances: 2
|
||||||
memory: 512M
|
memory: 512M
|
||||||
stack: cflinuxfs4
|
stack: cflinuxfs4
|
||||||
timeout: 180
|
timeout: 180
|
||||||
|
|
|
@ -4,7 +4,7 @@ applications:
|
||||||
buildpacks:
|
buildpacks:
|
||||||
- python_buildpack
|
- python_buildpack
|
||||||
path: ../../src
|
path: ../../src
|
||||||
instances: 1
|
instances: 2
|
||||||
memory: 512M
|
memory: 512M
|
||||||
stack: cflinuxfs4
|
stack: cflinuxfs4
|
||||||
timeout: 180
|
timeout: 180
|
||||||
|
|
|
@ -90,6 +90,9 @@ cd src/
|
||||||
cd ..
|
cd ..
|
||||||
cf push getgov-$1 -f ops/manifests/manifest-$1.yaml
|
cf push getgov-$1 -f ops/manifests/manifest-$1.yaml
|
||||||
|
|
||||||
|
echo "Creating cache table..."
|
||||||
|
cf run-task getgov-$1 --command 'python manage.py createcachetable' --name createcachetable
|
||||||
|
|
||||||
read -p "Please provide the email of the space developer: " -r
|
read -p "Please provide the email of the space developer: " -r
|
||||||
cf set-space-role $REPLY cisa-dotgov $1 SpaceDeveloper
|
cf set-space-role $REPLY cisa-dotgov $1 SpaceDeveloper
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,7 @@ django-login-required-middleware = "*"
|
||||||
greenlet = "*"
|
greenlet = "*"
|
||||||
gevent = "*"
|
gevent = "*"
|
||||||
fred-epplib = {git = "https://github.com/cisagov/epplib.git", ref = "master"}
|
fred-epplib = {git = "https://github.com/cisagov/epplib.git", ref = "master"}
|
||||||
geventconnpool = {git = "https://github.com/rasky/geventconnpool.git", ref = "1bbb93a714a331a069adf27265fe582d9ba7ecd4"}
|
tblib = "*"
|
||||||
|
|
||||||
[dev-packages]
|
[dev-packages]
|
||||||
django-debug-toolbar = "*"
|
django-debug-toolbar = "*"
|
||||||
|
|
583
src/Pipfile.lock
generated
583
src/Pipfile.lock
generated
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"hash": {
|
"hash": {
|
||||||
"sha256": "a672aeb8951fd850e90ad87c6f03cf71e2fc2b387d56fd3942361cb0b45bb449"
|
"sha256": "b5d93b1b9ccafc37019276a222957544bab3f1f46b5dab8a0f2ffc2e5c9e1678"
|
||||||
},
|
},
|
||||||
"pipfile-spec": 6,
|
"pipfile-spec": 6,
|
||||||
"requires": {},
|
"requires": {},
|
||||||
|
@ -32,29 +32,29 @@
|
||||||
},
|
},
|
||||||
"boto3": {
|
"boto3": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:65acfe7f1cf2a9b7df3d4edb87c8022e02685825bd1957e7bb678cc0d09f5e5f",
|
"sha256:8b3f5cc7fbedcbb22271c328039df8a6ab343001e746e0cdb24774c426cadcf8",
|
||||||
"sha256:73f5ec89cb3ddb3ed577317889fd2f2df783f66b6502a9a4239979607e33bf74"
|
"sha256:f201b6a416f809283d554c652211eecec9fe3a52ed4063dab3f3e7aea7571d9c"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"markers": "python_version >= '3.8'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==1.34.37"
|
"version": "==1.34.54"
|
||||||
},
|
},
|
||||||
"botocore": {
|
"botocore": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:2a5bf33aacd2d970afd3d492e179e06ea98a5469030d5cfe7a2ad9995f7bb2ef",
|
"sha256:4061ff4be3efcf53547ebadf2c94d419dfc8be7beec24e9fa1819599ffd936fa",
|
||||||
"sha256:3c46ddb1679e6ef45ca78b48665398636bda532a07cd476e4b500697d13d9a99"
|
"sha256:bf215d93e9d5544c593962780d194e74c6ee40b883d0b885e62ef35fc0ec01e5"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.8'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==1.34.37"
|
"version": "==1.34.54"
|
||||||
},
|
},
|
||||||
"cachetools": {
|
"cachetools": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:086ee420196f7b2ab9ca2db2520aca326318b68fe5ba8bc4d49cca91add450f2",
|
"sha256:0abad1021d3f8325b2fc1d2e9c8b9c9d57b04c3932657a72465447332c24d945",
|
||||||
"sha256:861f35a13a451f94e301ce2bec7cac63e881232ccce7ed67fab9b5df4d3beaa1"
|
"sha256:ba29e2dfa0b8b556606f097407ed1aa62080ee108ab0dc5ec9d6a723a007d105"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"markers": "python_version >= '3.7'",
|
"markers": "python_version >= '3.7'",
|
||||||
"version": "==5.3.2"
|
"version": "==5.3.3"
|
||||||
},
|
},
|
||||||
"certifi": {
|
"certifi": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -228,41 +228,41 @@
|
||||||
},
|
},
|
||||||
"cryptography": {
|
"cryptography": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:087887e55e0b9c8724cf05361357875adb5c20dec27e5816b653492980d20380",
|
"sha256:0270572b8bd2c833c3981724b8ee9747b3ec96f699a9665470018594301439ee",
|
||||||
"sha256:09a77e5b2e8ca732a19a90c5bca2d124621a1edb5438c5daa2d2738bfeb02589",
|
"sha256:111a0d8553afcf8eb02a4fea6ca4f59d48ddb34497aa8706a6cf536f1a5ec576",
|
||||||
"sha256:130c0f77022b2b9c99d8cebcdd834d81705f61c68e91ddd614ce74c657f8b3ea",
|
"sha256:16a48c23a62a2f4a285699dba2e4ff2d1cff3115b9df052cdd976a18856d8e3d",
|
||||||
"sha256:141e2aa5ba100d3788c0ad7919b288f89d1fe015878b9659b307c9ef867d3a65",
|
"sha256:1b95b98b0d2af784078fa69f637135e3c317091b615cd0905f8b8a087e86fa30",
|
||||||
"sha256:28cb2c41f131a5758d6ba6a0504150d644054fd9f3203a1e8e8d7ac3aea7f73a",
|
"sha256:1f71c10d1e88467126f0efd484bd44bca5e14c664ec2ede64c32f20875c0d413",
|
||||||
"sha256:2f9f14185962e6a04ab32d1abe34eae8a9001569ee4edb64d2304bf0d65c53f3",
|
"sha256:2424ff4c4ac7f6b8177b53c17ed5d8fa74ae5955656867f5a8affaca36a27abb",
|
||||||
"sha256:320948ab49883557a256eab46149df79435a22d2fefd6a66fe6946f1b9d9d008",
|
"sha256:2bce03af1ce5a5567ab89bd90d11e7bbdff56b8af3acbbec1faded8f44cb06da",
|
||||||
"sha256:36d4b7c4be6411f58f60d9ce555a73df8406d484ba12a63549c88bd64f7967f1",
|
"sha256:329906dcc7b20ff3cad13c069a78124ed8247adcac44b10bea1130e36caae0b4",
|
||||||
"sha256:3b15c678f27d66d247132cbf13df2f75255627bcc9b6a570f7d2fd08e8c081d2",
|
"sha256:37dd623507659e08be98eec89323469e8c7b4c1407c85112634ae3dbdb926fdd",
|
||||||
"sha256:3dbd37e14ce795b4af61b89b037d4bc157f2cb23e676fa16932185a04dfbf635",
|
"sha256:3eaafe47ec0d0ffcc9349e1708be2aaea4c6dd4978d76bf6eb0cb2c13636c6fc",
|
||||||
"sha256:4383b47f45b14459cab66048d384614019965ba6c1a1a141f11b5a551cace1b2",
|
"sha256:5e6275c09d2badf57aea3afa80d975444f4be8d3bc58f7f80d2a484c6f9485c8",
|
||||||
"sha256:44c95c0e96b3cb628e8452ec060413a49002a247b2b9938989e23a2c8291fc90",
|
"sha256:6fe07eec95dfd477eb9530aef5bead34fec819b3aaf6c5bd6d20565da607bfe1",
|
||||||
"sha256:4b063d3413f853e056161eb0c7724822a9740ad3caa24b8424d776cebf98e7ee",
|
"sha256:7367d7b2eca6513681127ebad53b2582911d1736dc2ffc19f2c3ae49997496bc",
|
||||||
"sha256:52ed9ebf8ac602385126c9a2fe951db36f2cb0c2538d22971487f89d0de4065a",
|
"sha256:7cde5f38e614f55e28d831754e8a3bacf9ace5d1566235e39d91b35502d6936e",
|
||||||
"sha256:55d1580e2d7e17f45d19d3b12098e352f3a37fe86d380bf45846ef257054b242",
|
"sha256:9481ffe3cf013b71b2428b905c4f7a9a4f76ec03065b05ff499bb5682a8d9ad8",
|
||||||
"sha256:5ef9bc3d046ce83c4bbf4c25e1e0547b9c441c01d30922d812e887dc5f125c12",
|
"sha256:98d8dc6d012b82287f2c3d26ce1d2dd130ec200c8679b6213b3c73c08b2b7940",
|
||||||
"sha256:5fa82a26f92871eca593b53359c12ad7949772462f887c35edaf36f87953c0e2",
|
"sha256:a011a644f6d7d03736214d38832e030d8268bcff4a41f728e6030325fea3e400",
|
||||||
"sha256:61321672b3ac7aade25c40449ccedbc6db72c7f5f0fdf34def5e2f8b51ca530d",
|
"sha256:a2913c5375154b6ef2e91c10b5720ea6e21007412f6437504ffea2109b5a33d7",
|
||||||
"sha256:701171f825dcab90969596ce2af253143b93b08f1a716d4b2a9d2db5084ef7be",
|
"sha256:a30596bae9403a342c978fb47d9b0ee277699fa53bbafad14706af51fe543d16",
|
||||||
"sha256:841ec8af7a8491ac76ec5a9522226e287187a3107e12b7d686ad354bb78facee",
|
"sha256:b03c2ae5d2f0fc05f9a2c0c997e1bc18c8229f392234e8a0194f202169ccd278",
|
||||||
"sha256:8a06641fb07d4e8f6c7dda4fc3f8871d327803ab6542e33831c7ccfdcb4d0ad6",
|
"sha256:b6cd2203306b63e41acdf39aa93b86fb566049aeb6dc489b70e34bcd07adca74",
|
||||||
"sha256:8e88bb9eafbf6a4014d55fb222e7360eef53e613215085e65a13290577394529",
|
"sha256:b7ffe927ee6531c78f81aa17e684e2ff617daeba7f189f911065b2ea2d526dec",
|
||||||
"sha256:a00aee5d1b6c20620161984f8ab2ab69134466c51f58c052c11b076715e72929",
|
"sha256:b8cac287fafc4ad485b8a9b67d0ee80c66bf3574f655d3b97ef2e1082360faf1",
|
||||||
"sha256:a047682d324ba56e61b7ea7c7299d51e61fd3bca7dad2ccc39b72bd0118d60a1",
|
"sha256:ba334e6e4b1d92442b75ddacc615c5476d4ad55cc29b15d590cc6b86efa487e2",
|
||||||
"sha256:a7ef8dd0bf2e1d0a27042b231a3baac6883cdd5557036f5e8df7139255feaac6",
|
"sha256:ba3e4a42397c25b7ff88cdec6e2a16c2be18720f317506ee25210f6d31925f9c",
|
||||||
"sha256:ad28cff53f60d99a928dfcf1e861e0b2ceb2bc1f08a074fdd601b314e1cc9e0a",
|
"sha256:c41fb5e6a5fe9ebcd58ca3abfeb51dffb5d83d6775405305bfa8715b76521922",
|
||||||
"sha256:b9097a208875fc7bbeb1286d0125d90bdfed961f61f214d3f5be62cd4ed8a446",
|
"sha256:cd2030f6650c089aeb304cf093f3244d34745ce0cfcc39f20c6fbfe030102e2a",
|
||||||
"sha256:b97fe7d7991c25e6a31e5d5e795986b18fbbb3107b873d5f3ae6dc9a103278e9",
|
"sha256:cd65d75953847815962c84a4654a84850b2bb4aed3f26fadcc1c13892e1e29f6",
|
||||||
"sha256:e0ec52ba3c7f1b7d813cd52649a5b3ef1fc0d433219dc8c93827c57eab6cf888",
|
"sha256:e4985a790f921508f36f81831817cbc03b102d643b5fcb81cd33df3fa291a1a1",
|
||||||
"sha256:ea2c3ffb662fec8bbbfce5602e2c159ff097a4631d96235fcf0fb00e59e3ece4",
|
"sha256:e807b3188f9eb0eaa7bbb579b462c5ace579f1cedb28107ce8b48a9f7ad3679e",
|
||||||
"sha256:fa3dec4ba8fb6e662770b74f62f1a0c7d4e37e25b58b2bf2c1be4c95372b4a33",
|
"sha256:f12764b8fffc7a123f641d7d049d382b73f96a34117e0b637b80643169cec8ac",
|
||||||
"sha256:fbeb725c9dc799a574518109336acccaf1303c30d45c075c665c0793c2f79a7f"
|
"sha256:f8837fe1d6ac4a8052a9a8ddab256bc006242696f03368a4009be7ee3075cdb7"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.7'",
|
"markers": "python_version >= '3.7'",
|
||||||
"version": "==42.0.2"
|
"version": "==42.0.5"
|
||||||
},
|
},
|
||||||
"defusedxml": {
|
"defusedxml": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -330,11 +330,11 @@
|
||||||
},
|
},
|
||||||
"django-csp": {
|
"django-csp": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:01443a07723f9a479d498bd7bb63571aaa771e690f64bde515db6cdb76e8041a",
|
"sha256:19b2978b03fcd73517d7d67acbc04fbbcaec0facc3e83baa502965892d1e0719",
|
||||||
"sha256:01eda02ad3f10261c74131cdc0b5a6a62b7c7ad4fd017fbefb7a14776e0a9727"
|
"sha256:ef0f1a9f7d8da68ae6e169c02e9ac661c0ecf04db70e0d1d85640512a68471c0"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==3.7"
|
"version": "==3.8"
|
||||||
},
|
},
|
||||||
"django-fsm": {
|
"django-fsm": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -384,12 +384,12 @@
|
||||||
},
|
},
|
||||||
"faker": {
|
"faker": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:60e89e5c0b584e285a7db05eceba35011a241954afdab2853cb246c8a56700a2",
|
"sha256:117ce1a2805c1bc5ca753b3dc6f9d567732893b2294b827d3164261ee8f20267",
|
||||||
"sha256:b7f76bb1b2ac4cdc54442d955e36e477c387000f31ce46887fb9722a041be60b"
|
"sha256:458d93580de34403a8dec1e8d5e6be2fee96c4deca63b95d71df7a6a80a690de"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"markers": "python_version >= '3.8'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==23.1.0"
|
"version": "==23.3.0"
|
||||||
},
|
},
|
||||||
"fred-epplib": {
|
"fred-epplib": {
|
||||||
"git": "https://github.com/cisagov/epplib.git",
|
"git": "https://github.com/cisagov/epplib.git",
|
||||||
|
@ -404,61 +404,59 @@
|
||||||
},
|
},
|
||||||
"future": {
|
"future": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:34a17436ed1e96697a86f9de3d15a3b0be01d8bc8de9c1dffd59fb8234ed5307"
|
"sha256:929292d34f5872e70396626ef385ec22355a1fae8ad29e1a734c3e43f9fbc216",
|
||||||
|
"sha256:bd2968309307861edae1458a4f8a4f3598c03be43b97521076aebf5d94c07b05"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||||
"version": "==0.18.3"
|
"version": "==1.0.0"
|
||||||
},
|
},
|
||||||
"gevent": {
|
"gevent": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:272cffdf535978d59c38ed837916dfd2b5d193be1e9e5dcc60a5f4d5025dd98a",
|
"sha256:03aa5879acd6b7076f6a2a307410fb1e0d288b84b03cdfd8c74db8b4bc882fc5",
|
||||||
"sha256:2c7b5c9912378e5f5ccf180d1fdb1e83f42b71823483066eddbe10ef1a2fcaa2",
|
"sha256:117e5837bc74a1673605fb53f8bfe22feb6e5afa411f524c835b2ddf768db0de",
|
||||||
"sha256:36a549d632c14684bcbbd3014a6ce2666c5f2a500f34d58d32df6c9ea38b6535",
|
"sha256:141a2b24ad14f7b9576965c0c84927fc85f824a9bb19f6ec1e61e845d87c9cd8",
|
||||||
"sha256:4368f341a5f51611411ec3fc62426f52ac3d6d42eaee9ed0f9eebe715c80184e",
|
"sha256:14532a67f7cb29fb055a0e9b39f16b88ed22c66b96641df8c04bdc38c26b9ea5",
|
||||||
"sha256:43daf68496c03a35287b8b617f9f91e0e7c0d042aebcc060cadc3f049aadd653",
|
"sha256:1dffb395e500613e0452b9503153f8f7ba587c67dd4a85fc7cd7aa7430cb02cc",
|
||||||
"sha256:455e5ee8103f722b503fa45dedb04f3ffdec978c1524647f8ba72b4f08490af1",
|
"sha256:2955eea9c44c842c626feebf4459c42ce168685aa99594e049d03bedf53c2800",
|
||||||
"sha256:45792c45d60f6ce3d19651d7fde0bc13e01b56bb4db60d3f32ab7d9ec467374c",
|
"sha256:2ae3a25ecce0a5b0cd0808ab716bfca180230112bb4bc89b46ae0061d62d4afe",
|
||||||
"sha256:4e24c2af9638d6c989caffc691a039d7c7022a31c0363da367c0d32ceb4a0648",
|
"sha256:2e9ac06f225b696cdedbb22f9e805e2dd87bf82e8fa5e17756f94e88a9d37cf7",
|
||||||
"sha256:52b4abf28e837f1865a9bdeef58ff6afd07d1d888b70b6804557e7908032e599",
|
"sha256:368a277bd9278ddb0fde308e6a43f544222d76ed0c4166e0d9f6b036586819d9",
|
||||||
"sha256:52e9f12cd1cda96603ce6b113d934f1aafb873e2c13182cf8e86d2c5c41982ea",
|
"sha256:3adfb96637f44010be8abd1b5e73b5070f851b817a0b182e601202f20fa06533",
|
||||||
"sha256:5f3c781c84794926d853d6fb58554dc0dcc800ba25c41d42f6959c344b4db5a6",
|
"sha256:3d5325ccfadfd3dcf72ff88a92fb8fc0b56cacc7225f0f4b6dcf186c1a6eeabc",
|
||||||
"sha256:62d121344f7465e3739989ad6b91f53a6ca9110518231553fe5846dbe1b4518f",
|
"sha256:432fc76f680acf7cf188c2ee0f5d3ab73b63c1f03114c7cd8a34cebbe5aa2056",
|
||||||
"sha256:65883ac026731ac112184680d1f0f1e39fa6f4389fd1fc0bf46cc1388e2599f9",
|
"sha256:44098038d5e2749b0784aabb27f1fcbb3f43edebedf64d0af0d26955611be8d6",
|
||||||
"sha256:707904027d7130ff3e59ea387dddceedb133cc742b00b3ffe696d567147a9c9e",
|
"sha256:5a1df555431f5cd5cc189a6ee3544d24f8c52f2529134685f1e878c4972ab026",
|
||||||
"sha256:72c002235390d46f94938a96920d8856d4ffd9ddf62a303a0d7c118894097e34",
|
"sha256:6c47ae7d1174617b3509f5d884935e788f325eb8f1a7efc95d295c68d83cce40",
|
||||||
"sha256:7532c17bc6c1cbac265e751b95000961715adef35a25d2b0b1813aa7263fb397",
|
"sha256:6f947a9abc1a129858391b3d9334c45041c08a0f23d14333d5b844b6e5c17a07",
|
||||||
"sha256:78eebaf5e73ff91d34df48f4e35581ab4c84e22dd5338ef32714264063c57507",
|
"sha256:782a771424fe74bc7e75c228a1da671578c2ba4ddb2ca09b8f959abdf787331e",
|
||||||
"sha256:7c1abc6f25f475adc33e5fc2dbcc26a732608ac5375d0d306228738a9ae14d3b",
|
"sha256:7899a38d0ae7e817e99adb217f586d0a4620e315e4de577444ebeeed2c5729be",
|
||||||
"sha256:7c28e38dcde327c217fdafb9d5d17d3e772f636f35df15ffae2d933a5587addd",
|
"sha256:7b00f8c9065de3ad226f7979154a7b27f3b9151c8055c162332369262fc025d8",
|
||||||
"sha256:7ccf0fd378257cb77d91c116e15c99e533374a8153632c48a3ecae7f7f4f09fe",
|
"sha256:8f4b8e777d39013595a7740b4463e61b1cfe5f462f1b609b28fbc1e4c4ff01e5",
|
||||||
"sha256:921dda1c0b84e3d3b1778efa362d61ed29e2b215b90f81d498eb4d8eafcd0b7a",
|
"sha256:90cbac1ec05b305a1b90ede61ef73126afdeb5a804ae04480d6da12c56378df1",
|
||||||
"sha256:a2898b7048771917d85a1d548fd378e8a7b2ca963db8e17c6d90c76b495e0e2b",
|
"sha256:918cdf8751b24986f915d743225ad6b702f83e1106e08a63b736e3a4c6ead789",
|
||||||
"sha256:a3c5e9b1f766a7a64833334a18539a362fb563f6c4682f9634dea72cbe24f771",
|
"sha256:9202f22ef811053077d01f43cc02b4aaf4472792f9fd0f5081b0b05c926cca19",
|
||||||
"sha256:ada07076b380918829250201df1d016bdafb3acf352f35e5693b59dceee8dd2e",
|
"sha256:94138682e68ec197db42ad7442d3cf9b328069c3ad8e4e5022e6b5cd3e7ffae5",
|
||||||
"sha256:b101086f109168b23fa3586fccd1133494bdb97f86920a24dc0b23984dc30b69",
|
"sha256:968581d1717bbcf170758580f5f97a2925854943c45a19be4d47299507db2eb7",
|
||||||
"sha256:bf456bd6b992eb0e1e869e2fd0caf817f0253e55ca7977fd0e72d0336a8c1c6a",
|
"sha256:9d8d0642c63d453179058abc4143e30718b19a85cbf58c2744c9a63f06a1d388",
|
||||||
"sha256:bf7af500da05363e66f122896012acb6e101a552682f2352b618e541c941a011",
|
"sha256:a7ceb59986456ce851160867ce4929edaffbd2f069ae25717150199f8e1548b8",
|
||||||
"sha256:c3e5d2fa532e4d3450595244de8ccf51f5721a05088813c1abd93ad274fe15e7",
|
"sha256:b9913c45d1be52d7a5db0c63977eebb51f68a2d5e6fd922d1d9b5e5fd758cc98",
|
||||||
"sha256:c84d34256c243b0a53d4335ef0bc76c735873986d478c53073861a92566a8d71",
|
"sha256:bde283313daf0b34a8d1bab30325f5cb0f4e11b5869dbe5bc61f8fe09a8f66f3",
|
||||||
"sha256:d163d59f1be5a4c4efcdd13c2177baaf24aadf721fdf2e1af9ee54a998d160f5",
|
"sha256:bf5b9c72b884c6f0c4ed26ef204ee1f768b9437330422492c319470954bc4cc7",
|
||||||
"sha256:d57737860bfc332b9b5aa438963986afe90f49645f6e053140cfa0fa1bdae1ae",
|
"sha256:ca80b121bbec76d7794fcb45e65a7eca660a76cc1a104ed439cdbd7df5f0b060",
|
||||||
"sha256:dbb22a9bbd6a13e925815ce70b940d1578dbe5d4013f20d23e8a11eddf8d14a7",
|
"sha256:cdf66977a976d6a3cfb006afdf825d1482f84f7b81179db33941f2fc9673bb1d",
|
||||||
"sha256:dcb8612787a7f4626aa881ff15ff25439561a429f5b303048f0fca8a1c781c39",
|
"sha256:d4faf846ed132fd7ebfbbf4fde588a62d21faa0faa06e6f468b7faa6f436b661",
|
||||||
"sha256:dd6c32ab977ecf7c7b8c2611ed95fa4aaebd69b74bf08f4b4960ad516861517d",
|
"sha256:d7f87c2c02e03d99b95cfa6f7a776409083a9e4d468912e18c7680437b29222c",
|
||||||
"sha256:de350fde10efa87ea60d742901e1053eb2127ebd8b59a7d3b90597eb4e586599",
|
"sha256:dd23df885318391856415e20acfd51a985cba6919f0be78ed89f5db9ff3a31cb",
|
||||||
"sha256:e1ead6863e596a8cc2a03e26a7a0981f84b6b3e956101135ff6d02df4d9a6b07",
|
"sha256:f5de3c676e57177b38857f6e3cdfbe8f38d1cd754b63200c0615eaa31f514b4f",
|
||||||
"sha256:ed7a048d3e526a5c1d55c44cb3bc06cfdc1947d06d45006cc4cf60dedc628904",
|
"sha256:f5e8e8d60e18d5f7fd49983f0c4696deeddaf6e608fbab33397671e2fcc6cc91",
|
||||||
"sha256:f632487c87866094546a74eefbca2c74c1d03638b715b6feb12e80120960185a",
|
"sha256:f7cac622e11b4253ac4536a654fe221249065d9a69feb6cdcd4d9af3503602e0",
|
||||||
"sha256:fae8d5b5b8fa2a8f63b39f5447168b02db10c888a3e387ed7af2bd1b8612e543",
|
"sha256:f8a04cf0c5b7139bc6368b461257d4a757ea2fe89b3773e494d235b7dd51119f",
|
||||||
"sha256:fde6402c5432b835fbb7698f1c7f2809c8d6b2bd9d047ac1f5a7c1d5aa569303"
|
"sha256:f8bb35ce57a63c9a6896c71a285818a3922d8ca05d150fd1fe49a7f57287b836",
|
||||||
|
"sha256:fbfdce91239fe306772faab57597186710d5699213f4df099d1612da7320d682"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"markers": "python_version >= '3.8'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==23.9.1"
|
"version": "==24.2.1"
|
||||||
},
|
|
||||||
"geventconnpool": {
|
|
||||||
"git": "https://github.com/rasky/geventconnpool.git",
|
|
||||||
"ref": "1bbb93a714a331a069adf27265fe582d9ba7ecd4"
|
|
||||||
},
|
},
|
||||||
"greenlet": {
|
"greenlet": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -710,11 +708,11 @@
|
||||||
},
|
},
|
||||||
"marshmallow": {
|
"marshmallow": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:4c1daff273513dc5eb24b219a8035559dc573c8f322558ef85f5438ddd1236dd",
|
"sha256:20f53be28c6e374a711a16165fb22a8dc6003e3f7cda1285e3ca777b9193885b",
|
||||||
"sha256:c21d4b98fee747c130e6bc8f45c4b3199ea66bc00c12ee1f639f0aeca034d5e9"
|
"sha256:e7997f83571c7fd476042c2c188e4ee8a78900ca5e74bd9c8097afa56624e9bd"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.8'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==3.20.2"
|
"version": "==3.21.0"
|
||||||
},
|
},
|
||||||
"oic": {
|
"oic": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -742,10 +740,10 @@
|
||||||
},
|
},
|
||||||
"phonenumberslite": {
|
"phonenumberslite": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:2b04a53401d01ab42564c1abc762fc9808ad398e71dacfa3b38d4321e112ecb3",
|
"sha256:137d53d5d78dca30bc2becf81a3e2ac74deb8f0997e9bbe44de515ece4bd92bd",
|
||||||
"sha256:74e3ee63dfa2bb562ce2e6ce74ce76ae74a2f81472005b80343235fb43426db4"
|
"sha256:e1f4359bff90c86d1b52db0e726d3334df00cc7d9c9c2ef66561d5f7a774d4ba"
|
||||||
],
|
],
|
||||||
"version": "==8.13.29"
|
"version": "==8.13.31"
|
||||||
},
|
},
|
||||||
"psycopg2-binary": {
|
"psycopg2-binary": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -874,104 +872,104 @@
|
||||||
},
|
},
|
||||||
"pydantic": {
|
"pydantic": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:0b6a909df3192245cb736509a92ff69e4fef76116feffec68e93a567347bae6f",
|
"sha256:72c6034df47f46ccdf81869fddb81aade68056003900a8724a4f160700016a2a",
|
||||||
"sha256:4fd5c182a2488dc63e6d32737ff19937888001e2a6d86e94b3f233104a5d1fa9"
|
"sha256:e07805c4c7f5c6826e33a1d4c9d47950d7eaf34868e2690f8594d2e30241f11f"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.8'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==2.6.1"
|
"version": "==2.6.3"
|
||||||
},
|
},
|
||||||
"pydantic-core": {
|
"pydantic-core": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:02906e7306cb8c5901a1feb61f9ab5e5c690dbbeaa04d84c1b9ae2a01ebe9379",
|
"sha256:00ee1c97b5364b84cb0bd82e9bbf645d5e2871fb8c58059d158412fee2d33d8a",
|
||||||
"sha256:0ba503850d8b8dcc18391f10de896ae51d37fe5fe43dbfb6a35c5c5cad271a06",
|
"sha256:0d32576b1de5a30d9a97f300cc6a3f4694c428d956adbc7e6e2f9cad279e45ed",
|
||||||
"sha256:16aa02e7a0f539098e215fc193c8926c897175d64c7926d00a36188917717a05",
|
"sha256:0df446663464884297c793874573549229f9eca73b59360878f382a0fc085979",
|
||||||
"sha256:18de31781cdc7e7b28678df7c2d7882f9692ad060bc6ee3c94eb15a5d733f8f7",
|
"sha256:0f56ae86b60ea987ae8bcd6654a887238fd53d1384f9b222ac457070b7ac4cff",
|
||||||
"sha256:22c5f022799f3cd6741e24f0443ead92ef42be93ffda0d29b2597208c94c3753",
|
"sha256:13dcc4802961b5f843a9385fc821a0b0135e8c07fc3d9949fd49627c1a5e6ae5",
|
||||||
"sha256:2924b89b16420712e9bb8192396026a8fbd6d8726224f918353ac19c4c043d2a",
|
"sha256:162e498303d2b1c036b957a1278fa0899d02b2842f1ff901b6395104c5554a45",
|
||||||
"sha256:308974fdf98046db28440eb3377abba274808bf66262e042c412eb2adf852731",
|
"sha256:1b662180108c55dfbf1280d865b2d116633d436cfc0bba82323554873967b340",
|
||||||
"sha256:396fdf88b1b503c9c59c84a08b6833ec0c3b5ad1a83230252a9e17b7dfb4cffc",
|
"sha256:1cac689f80a3abab2d3c0048b29eea5751114054f032a941a32de4c852c59cad",
|
||||||
"sha256:3ac426704840877a285d03a445e162eb258924f014e2f074e209d9b4ff7bf380",
|
"sha256:21b888c973e4f26b7a96491c0965a8a312e13be108022ee510248fe379a5fa23",
|
||||||
"sha256:3b052c753c4babf2d1edc034c97851f867c87d6f3ea63a12e2700f159f5c41c3",
|
"sha256:287073c66748f624be4cef893ef9174e3eb88fe0b8a78dc22e88eca4bc357ca6",
|
||||||
"sha256:3fab4e75b8c525a4776e7630b9ee48aea50107fea6ca9f593c98da3f4d11bf7c",
|
"sha256:2a1ef6a36fdbf71538142ed604ad19b82f67b05749512e47f247a6ddd06afdc7",
|
||||||
"sha256:406fac1d09edc613020ce9cf3f2ccf1a1b2f57ab00552b4c18e3d5276c67eb11",
|
"sha256:2a72fb9963cba4cd5793854fd12f4cfee731e86df140f59ff52a49b3552db241",
|
||||||
"sha256:40a0bd0bed96dae5712dab2aba7d334a6c67cbcac2ddfca7dbcc4a8176445990",
|
"sha256:2acca2be4bb2f2147ada8cac612f8a98fc09f41c89f87add7256ad27332c2fda",
|
||||||
"sha256:41dac3b9fce187a25c6253ec79a3f9e2a7e761eb08690e90415069ea4a68ff7a",
|
"sha256:2f583bd01bbfbff4eaee0868e6fc607efdfcc2b03c1c766b06a707abbc856187",
|
||||||
"sha256:459c0d338cc55d099798618f714b21b7ece17eb1a87879f2da20a3ff4c7628e2",
|
"sha256:33809aebac276089b78db106ee692bdc9044710e26f24a9a2eaa35a0f9fa70ba",
|
||||||
"sha256:459d6be6134ce3b38e0ef76f8a672924460c455d45f1ad8fdade36796df1ddc8",
|
"sha256:36fa178aacbc277bc6b62a2c3da95226520da4f4e9e206fdf076484363895d2c",
|
||||||
"sha256:46b0d5520dbcafea9a8645a8164658777686c5c524d381d983317d29687cce97",
|
"sha256:4204e773b4b408062960e65468d5346bdfe139247ee5f1ca2a378983e11388a2",
|
||||||
"sha256:47924039e785a04d4a4fa49455e51b4eb3422d6eaacfde9fc9abf8fdef164e8a",
|
"sha256:4384a8f68ddb31a0b0c3deae88765f5868a1b9148939c3f4121233314ad5532c",
|
||||||
"sha256:4bfcbde6e06c56b30668a0c872d75a7ef3025dc3c1823a13cf29a0e9b33f67e8",
|
"sha256:456855f57b413f077dff513a5a28ed838dbbb15082ba00f80750377eed23d132",
|
||||||
"sha256:4f9ee4febb249c591d07b2d4dd36ebcad0ccd128962aaa1801508320896575ef",
|
"sha256:49d5d58abd4b83fb8ce763be7794d09b2f50f10aa65c0f0c1696c677edeb7cbf",
|
||||||
"sha256:55749f745ebf154c0d63d46c8c58594d8894b161928aa41adbb0709c1fe78b77",
|
"sha256:4ac6b4ce1e7283d715c4b729d8f9dab9627586dafce81d9eaa009dd7f25dd972",
|
||||||
"sha256:5864b0242f74b9dd0b78fd39db1768bc3f00d1ffc14e596fd3e3f2ce43436a33",
|
"sha256:4df8a199d9f6afc5ae9a65f8f95ee52cae389a8c6b20163762bde0426275b7db",
|
||||||
"sha256:5f60f920691a620b03082692c378661947d09415743e437a7478c309eb0e4f82",
|
"sha256:500960cb3a0543a724a81ba859da816e8cf01b0e6aaeedf2c3775d12ee49cade",
|
||||||
"sha256:60eb8ceaa40a41540b9acae6ae7c1f0a67d233c40dc4359c256ad2ad85bdf5e5",
|
"sha256:519ae0312616026bf4cedc0fe459e982734f3ca82ee8c7246c19b650b60a5ee4",
|
||||||
"sha256:69a7b96b59322a81c2203be537957313b07dd333105b73db0b69212c7d867b4b",
|
"sha256:578114bc803a4c1ff9946d977c221e4376620a46cf78da267d946397dc9514a8",
|
||||||
"sha256:6ad84731a26bcfb299f9eab56c7932d46f9cad51c52768cace09e92a19e4cf55",
|
"sha256:5c5cbc703168d1b7a838668998308018a2718c2130595e8e190220238addc96f",
|
||||||
"sha256:6db58c22ac6c81aeac33912fb1af0e930bc9774166cdd56eade913d5f2fff35e",
|
"sha256:6162f8d2dc27ba21027f261e4fa26f8bcb3cf9784b7f9499466a311ac284b5b9",
|
||||||
"sha256:70651ff6e663428cea902dac297066d5c6e5423fda345a4ca62430575364d62b",
|
"sha256:704d35ecc7e9c31d48926150afada60401c55efa3b46cd1ded5a01bdffaf1d48",
|
||||||
"sha256:72f7919af5de5ecfaf1eba47bf9a5d8aa089a3340277276e5636d16ee97614d7",
|
"sha256:716b542728d4c742353448765aa7cdaa519a7b82f9564130e2b3f6766018c9ec",
|
||||||
"sha256:732bd062c9e5d9582a30e8751461c1917dd1ccbdd6cafb032f02c86b20d2e7ec",
|
"sha256:72282ad4892a9fb2da25defeac8c2e84352c108705c972db82ab121d15f14e6d",
|
||||||
"sha256:7924e54f7ce5d253d6160090ddc6df25ed2feea25bfb3339b424a9dd591688bc",
|
"sha256:7233d65d9d651242a68801159763d09e9ec96e8a158dbf118dc090cd77a104c9",
|
||||||
"sha256:7afb844041e707ac9ad9acad2188a90bffce2c770e6dc2318be0c9916aef1469",
|
"sha256:732da3243e1b8d3eab8c6ae23ae6a58548849d2e4a4e03a1924c8ddf71a387cb",
|
||||||
"sha256:7b883af50eaa6bb3299780651e5be921e88050ccf00e3e583b1e92020333304b",
|
"sha256:75b81e678d1c1ede0785c7f46690621e4c6e63ccd9192af1f0bd9d504bbb6bf4",
|
||||||
"sha256:7beec26729d496a12fd23cf8da9944ee338c8b8a17035a560b585c36fe81af20",
|
"sha256:75f76ee558751746d6a38f89d60b6228fa174e5172d143886af0f85aa306fd89",
|
||||||
"sha256:7bf26c2e2ea59d32807081ad51968133af3025c4ba5753e6a794683d2c91bf6e",
|
"sha256:7ee8d5f878dccb6d499ba4d30d757111847b6849ae07acdd1205fffa1fc1253c",
|
||||||
"sha256:7c31669e0c8cc68400ef0c730c3a1e11317ba76b892deeefaf52dcb41d56ed5d",
|
"sha256:7f752826b5b8361193df55afcdf8ca6a57d0232653494ba473630a83ba50d8c9",
|
||||||
"sha256:7e6231aa5bdacda78e96ad7b07d0c312f34ba35d717115f4b4bff6cb87224f0f",
|
"sha256:86b3d0033580bd6bbe07590152007275bd7af95f98eaa5bd36f3da219dcd93da",
|
||||||
"sha256:870dbfa94de9b8866b37b867a2cb37a60c401d9deb4a9ea392abf11a1f98037b",
|
"sha256:8d62da299c6ecb04df729e4b5c52dc0d53f4f8430b4492b93aa8de1f541c4aac",
|
||||||
"sha256:88646cae28eb1dd5cd1e09605680c2b043b64d7481cdad7f5003ebef401a3039",
|
"sha256:8e47755d8152c1ab5b55928ab422a76e2e7b22b5ed8e90a7d584268dd49e9c6b",
|
||||||
"sha256:8aafeedb6597a163a9c9727d8a8bd363a93277701b7bfd2749fbefee2396469e",
|
"sha256:9091632a25b8b87b9a605ec0e61f241c456e9248bfdcf7abdf344fdb169c81cf",
|
||||||
"sha256:8bde5b48c65b8e807409e6f20baee5d2cd880e0fad00b1a811ebc43e39a00ab2",
|
"sha256:936e5db01dd49476fa8f4383c259b8b1303d5dd5fb34c97de194560698cc2c5e",
|
||||||
"sha256:8f9142a6ed83d90c94a3efd7af8873bf7cefed2d3d44387bf848888482e2d25f",
|
"sha256:99b6add4c0b39a513d323d3b93bc173dac663c27b99860dd5bf491b240d26137",
|
||||||
"sha256:936a787f83db1f2115ee829dd615c4f684ee48ac4de5779ab4300994d8af325b",
|
"sha256:9c865a7ee6f93783bd5d781af5a4c43dadc37053a5b42f7d18dc019f8c9d2bd1",
|
||||||
"sha256:98dc6f4f2095fc7ad277782a7c2c88296badcad92316b5a6e530930b1d475ebc",
|
"sha256:a425479ee40ff021f8216c9d07a6a3b54b31c8267c6e17aa88b70d7ebd0e5e5b",
|
||||||
"sha256:9957433c3a1b67bdd4c63717eaf174ebb749510d5ea612cd4e83f2d9142f3fc8",
|
"sha256:a4b2bf78342c40b3dc830880106f54328928ff03e357935ad26c7128bbd66ce8",
|
||||||
"sha256:99af961d72ac731aae2a1b55ccbdae0733d816f8bfb97b41909e143de735f522",
|
"sha256:a6b1bb0827f56654b4437955555dc3aeeebeddc47c2d7ed575477f082622c49e",
|
||||||
"sha256:9b5f13857da99325dcabe1cc4e9e6a3d7b2e2c726248ba5dd4be3e8e4a0b6d0e",
|
"sha256:aaf09e615a0bf98d406657e0008e4a8701b11481840be7d31755dc9f97c44053",
|
||||||
"sha256:9d776d30cde7e541b8180103c3f294ef7c1862fd45d81738d156d00551005784",
|
"sha256:b1f6f5938d63c6139860f044e2538baeee6f0b251a1816e7adb6cbce106a1f01",
|
||||||
"sha256:9da90d393a8227d717c19f5397688a38635afec89f2e2d7af0df037f3249c39a",
|
"sha256:b29eeb887aa931c2fcef5aa515d9d176d25006794610c264ddc114c053bf96fe",
|
||||||
"sha256:a3b7352b48fbc8b446b75f3069124e87f599d25afb8baa96a550256c031bb890",
|
"sha256:b3992a322a5617ded0a9f23fd06dbc1e4bd7cf39bc4ccf344b10f80af58beacd",
|
||||||
"sha256:a477932664d9611d7a0816cc3c0eb1f8856f8a42435488280dfbf4395e141485",
|
"sha256:b5b6079cc452a7c53dd378c6f881ac528246b3ac9aae0f8eef98498a75657805",
|
||||||
"sha256:a7e41e3ada4cca5f22b478c08e973c930e5e6c7ba3588fb8e35f2398cdcc1545",
|
"sha256:b60cc1a081f80a2105a59385b92d82278b15d80ebb3adb200542ae165cd7d183",
|
||||||
"sha256:a90fec23b4b05a09ad988e7a4f4e081711a90eb2a55b9c984d8b74597599180f",
|
"sha256:b926dd38db1519ed3043a4de50214e0d600d404099c3392f098a7f9d75029ff8",
|
||||||
"sha256:a9e523474998fb33f7c1a4d55f5504c908d57add624599e095c20fa575b8d943",
|
"sha256:bd87f48924f360e5d1c5f770d6155ce0e7d83f7b4e10c2f9ec001c73cf475c99",
|
||||||
"sha256:aa057095f621dad24a1e906747179a69780ef45cc8f69e97463692adbcdae878",
|
"sha256:bda1ee3e08252b8d41fa5537413ffdddd58fa73107171a126d3b9ff001b9b820",
|
||||||
"sha256:aa6c8c582036275997a733427b88031a32ffa5dfc3124dc25a730658c47a572f",
|
"sha256:be0ec334369316fa73448cc8c982c01e5d2a81c95969d58b8f6e272884df0074",
|
||||||
"sha256:ae34418b6b389d601b31153b84dce480351a352e0bb763684a1b993d6be30f17",
|
"sha256:c6119dc90483a5cb50a1306adb8d52c66e447da88ea44f323e0ae1a5fcb14256",
|
||||||
"sha256:b0d7a9165167269758145756db43a133608a531b1e5bb6a626b9ee24bc38a8f7",
|
"sha256:c9803edf8e29bd825f43481f19c37f50d2b01899448273b3a7758441b512acf8",
|
||||||
"sha256:b30b0dd58a4509c3bd7eefddf6338565c4905406aee0c6e4a5293841411a1286",
|
"sha256:c9bd22a2a639e26171068f8ebb5400ce2c1bc7d17959f60a3b753ae13c632975",
|
||||||
"sha256:b8f9186ca45aee030dc8234118b9c0784ad91a0bb27fc4e7d9d6608a5e3d386c",
|
"sha256:cbcc558401de90a746d02ef330c528f2e668c83350f045833543cd57ecead1ad",
|
||||||
"sha256:b94cbda27267423411c928208e89adddf2ea5dd5f74b9528513f0358bba019cb",
|
"sha256:cf6204fe865da605285c34cf1172879d0314ff267b1c35ff59de7154f35fdc2e",
|
||||||
"sha256:cc6f6c9be0ab6da37bc77c2dda5f14b1d532d5dbef00311ee6e13357a418e646",
|
"sha256:d33dd21f572545649f90c38c227cc8631268ba25c460b5569abebdd0ec5974ca",
|
||||||
"sha256:ce232a6170dd6532096cadbf6185271e4e8c70fc9217ebe105923ac105da9978",
|
"sha256:d89ca19cdd0dd5f31606a9329e309d4fcbb3df860960acec32630297d61820df",
|
||||||
"sha256:cf903310a34e14651c9de056fcc12ce090560864d5a2bb0174b971685684e1d8",
|
"sha256:d8f99b147ff3fcf6b3cc60cb0c39ea443884d5559a30b1481e92495f2310ff2b",
|
||||||
"sha256:d5362d099c244a2d2f9659fb3c9db7c735f0004765bbe06b99be69fbd87c3f15",
|
"sha256:d937653a696465677ed583124b94a4b2d79f5e30b2c46115a68e482c6a591c8a",
|
||||||
"sha256:dffaf740fe2e147fedcb6b561353a16243e654f7fe8e701b1b9db148242e1272",
|
"sha256:dcca5d2bf65c6fb591fff92da03f94cd4f315972f97c21975398bd4bd046854a",
|
||||||
"sha256:e0f686549e32ccdb02ae6f25eee40cc33900910085de6aa3790effd391ae10c2",
|
"sha256:ded1c35f15c9dea16ead9bffcde9bb5c7c031bff076355dc58dcb1cb436c4721",
|
||||||
"sha256:e4b52776a2e3230f4854907a1e0946eec04d41b1fc64069ee774876bbe0eab55",
|
"sha256:e3e70c94a0c3841e6aa831edab1619ad5c511199be94d0c11ba75fe06efe107a",
|
||||||
"sha256:e4ba0884a91f1aecce75202473ab138724aa4fb26d7707f2e1fa6c3e68c84fbf",
|
"sha256:e56f8186d6210ac7ece503193ec84104da7ceb98f68ce18c07282fcc2452e76f",
|
||||||
"sha256:e6294e76b0380bb7a61eb8a39273c40b20beb35e8c87ee101062834ced19c545",
|
"sha256:e7774b570e61cb998490c5235740d475413a1f6de823169b4cf94e2fe9e9f6b2",
|
||||||
"sha256:ebb892ed8599b23fa8f1799e13a12c87a97a6c9d0f497525ce9858564c4575a4",
|
"sha256:e7c6ed0dc9d8e65f24f5824291550139fe6f37fac03788d4580da0d33bc00c97",
|
||||||
"sha256:eca58e319f4fd6df004762419612122b2c7e7d95ffafc37e890252f869f3fb2a",
|
"sha256:ec08be75bb268473677edb83ba71e7e74b43c008e4a7b1907c6d57e940bf34b6",
|
||||||
"sha256:ed957db4c33bc99895f3a1672eca7e80e8cda8bd1e29a80536b4ec2153fa9804",
|
"sha256:ecdf6bf5f578615f2e985a5e1f6572e23aa632c4bd1dc67f8f406d445ac115ed",
|
||||||
"sha256:ef551c053692b1e39e3f7950ce2296536728871110e7d75c4e7753fb30ca87f4",
|
"sha256:ed25e1835c00a332cb10c683cd39da96a719ab1dfc08427d476bce41b92531fc",
|
||||||
"sha256:ef6113cd31411eaf9b39fc5a8848e71c72656fd418882488598758b2c8c6dfa0",
|
"sha256:f4cb85f693044e0f71f394ff76c98ddc1bc0953e48c061725e540396d5c8a2e1",
|
||||||
"sha256:f685dbc1fdadb1dcd5b5e51e0a378d4685a891b2ddaf8e2bba89bd3a7144e44a",
|
"sha256:f53aace168a2a10582e570b7736cc5bef12cae9cf21775e3eafac597e8551fbe",
|
||||||
"sha256:f8ed79883b4328b7f0bd142733d99c8e6b22703e908ec63d930b06be3a0e7113",
|
"sha256:f651dd19363c632f4abe3480a7c87a9773be27cfe1341aef06e8759599454120",
|
||||||
"sha256:fe56851c3f1d6f5384b3051c536cc81b3a93a73faf931f404fef95217cf1e10d",
|
"sha256:fc4ad7f7ee1a13d9cb49d8198cd7d7e3aa93e425f371a68235f784e99741561f",
|
||||||
"sha256:ff7c97eb7a29aba230389a2661edf2e9e06ce616c7e35aa764879b6894a44b25"
|
"sha256:fee427241c2d9fb7192b658190f9f5fd6dfe41e02f3c1489d2ec1e6a5ab1e04a"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.8'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==2.16.2"
|
"version": "==2.16.3"
|
||||||
},
|
},
|
||||||
"pydantic-settings": {
|
"pydantic-settings": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:26b1492e0a24755626ac5e6d715e9077ab7ad4fb5f19a8b7ed7011d52f36141c",
|
"sha256:00b9f6a5e95553590434c0fa01ead0b216c3e10bc54ae02e37f359948643c5ed",
|
||||||
"sha256:7621c0cb5d90d1140d2f0ef557bdf03573aac7035948109adf2574770b77605a"
|
"sha256:0235391d26db4d2190cb9b31051c4b46882d28a51533f97440867f012d4da091"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.8'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==2.1.0"
|
"version": "==2.2.1"
|
||||||
},
|
},
|
||||||
"pyjwkest": {
|
"pyjwkest": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -982,11 +980,11 @@
|
||||||
},
|
},
|
||||||
"python-dateutil": {
|
"python-dateutil": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86",
|
"sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3",
|
||||||
"sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"
|
"sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||||
"version": "==2.8.2"
|
"version": "==2.9.0.post0"
|
||||||
},
|
},
|
||||||
"python-dotenv": {
|
"python-dotenv": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -1015,11 +1013,11 @@
|
||||||
},
|
},
|
||||||
"setuptools": {
|
"setuptools": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:385eb4edd9c9d5c17540511303e39a147ce2fc04bc55289c322b9e5904fe2c05",
|
"sha256:02fa291a0471b3a18b2b2481ed902af520c69e8ae0919c13da936542754b4c56",
|
||||||
"sha256:be1af57fc409f93647f2e8e4573a142ed38724b8cdd389706a867bb4efcf1e78"
|
"sha256:5c0806c7d9af348e6dd3777b4f4dbb42c7ad85b190104837488eab9a7c945cf8"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.8'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==69.0.3"
|
"version": "==69.1.1"
|
||||||
},
|
},
|
||||||
"six": {
|
"six": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -1037,14 +1035,23 @@
|
||||||
"markers": "python_version >= '3.5'",
|
"markers": "python_version >= '3.5'",
|
||||||
"version": "==0.4.4"
|
"version": "==0.4.4"
|
||||||
},
|
},
|
||||||
"typing-extensions": {
|
"tblib": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783",
|
"sha256:80a6c77e59b55e83911e1e607c649836a69c103963c5f28a46cbeef44acf8129",
|
||||||
"sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"
|
"sha256:93622790a0a29e04f0346458face1e144dc4d32f493714c6c3dff82a4adb77e6"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"markers": "python_version >= '3.8'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==4.9.0"
|
"version": "==3.0.0"
|
||||||
|
},
|
||||||
|
"typing-extensions": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475",
|
||||||
|
"sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"markers": "python_version >= '3.8'",
|
||||||
|
"version": "==4.10.0"
|
||||||
},
|
},
|
||||||
"urllib3": {
|
"urllib3": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -1073,45 +1080,45 @@
|
||||||
},
|
},
|
||||||
"zope.interface": {
|
"zope.interface": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:0c8cf55261e15590065039696607f6c9c1aeda700ceee40c70478552d323b3ff",
|
"sha256:02adbab560683c4eca3789cc0ac487dcc5f5a81cc48695ec247f00803cafe2fe",
|
||||||
"sha256:13b7d0f2a67eb83c385880489dbb80145e9d344427b4262c49fbf2581677c11c",
|
"sha256:14e02a6fc1772b458ebb6be1c276528b362041217b9ca37e52ecea2cbdce9fac",
|
||||||
"sha256:1f294a15f7723fc0d3b40701ca9b446133ec713eafc1cc6afa7b3d98666ee1ac",
|
"sha256:25e0af9663eeac6b61b231b43c52293c2cb7f0c232d914bdcbfd3e3bd5c182ad",
|
||||||
"sha256:239a4a08525c080ff833560171d23b249f7f4d17fcbf9316ef4159f44997616f",
|
"sha256:2606955a06c6852a6cff4abeca38346ed01e83f11e960caa9a821b3626a4467b",
|
||||||
"sha256:2f8d89721834524a813f37fa174bac074ec3d179858e4ad1b7efd4401f8ac45d",
|
"sha256:396f5c94654301819a7f3a702c5830f0ea7468d7b154d124ceac823e2419d000",
|
||||||
"sha256:2fdc7ccbd6eb6b7df5353012fbed6c3c5d04ceaca0038f75e601060e95345309",
|
"sha256:3b240883fb43160574f8f738e6d09ddbdbf8fa3e8cea051603d9edfd947d9328",
|
||||||
"sha256:34c15ca9248f2e095ef2e93af2d633358c5f048c49fbfddf5fdfc47d5e263736",
|
"sha256:3b6c62813c63c543a06394a636978b22dffa8c5410affc9331ce6cdb5bfa8565",
|
||||||
"sha256:387545206c56b0315fbadb0431d5129c797f92dc59e276b3ce82db07ac1c6179",
|
"sha256:4ae9793f114cee5c464cc0b821ae4d36e1eba961542c6086f391a61aee167b6f",
|
||||||
"sha256:43b576c34ef0c1f5a4981163b551a8781896f2a37f71b8655fd20b5af0386abb",
|
"sha256:4bce517b85f5debe07b186fc7102b332676760f2e0c92b7185dd49c138734b70",
|
||||||
"sha256:57d0a8ce40ce440f96a2c77824ee94bf0d0925e6089df7366c2272ccefcb7941",
|
"sha256:4d45d2ba8195850e3e829f1f0016066a122bfa362cc9dc212527fc3d51369037",
|
||||||
"sha256:5a804abc126b33824a44a7aa94f06cd211a18bbf31898ba04bd0924fbe9d282d",
|
"sha256:4dd374927c00764fcd6fe1046bea243ebdf403fba97a937493ae4be2c8912c2b",
|
||||||
"sha256:67be3ca75012c6e9b109860820a8b6c9a84bfb036fbd1076246b98e56951ca92",
|
"sha256:506f5410b36e5ba494136d9fa04c548eaf1a0d9c442b0b0e7a0944db7620e0ab",
|
||||||
"sha256:6af47f10cfc54c2ba2d825220f180cc1e2d4914d783d6fc0cd93d43d7bc1c78b",
|
"sha256:59f7374769b326a217d0b2366f1c176a45a4ff21e8f7cebb3b4a3537077eff85",
|
||||||
"sha256:6dc998f6de015723196a904045e5a2217f3590b62ea31990672e31fbc5370b41",
|
"sha256:5ee9789a20b0081dc469f65ff6c5007e67a940d5541419ca03ef20c6213dd099",
|
||||||
"sha256:70d2cef1bf529bff41559be2de9d44d47b002f65e17f43c73ddefc92f32bf00f",
|
"sha256:6fc711acc4a1c702ca931fdbf7bf7c86f2a27d564c85c4964772dadf0e3c52f5",
|
||||||
"sha256:7ebc4d34e7620c4f0da7bf162c81978fce0ea820e4fa1e8fc40ee763839805f3",
|
"sha256:75d2ec3d9b401df759b87bc9e19d1b24db73083147089b43ae748aefa63067ef",
|
||||||
"sha256:964a7af27379ff4357dad1256d9f215047e70e93009e532d36dcb8909036033d",
|
"sha256:76e0531d86523be7a46e15d379b0e975a9db84316617c0efe4af8338dc45b80c",
|
||||||
"sha256:97806e9ca3651588c1baaebb8d0c5ee3db95430b612db354c199b57378312ee8",
|
"sha256:8af82afc5998e1f307d5e72712526dba07403c73a9e287d906a8aa2b1f2e33dd",
|
||||||
"sha256:9b9bc671626281f6045ad61d93a60f52fd5e8209b1610972cf0ef1bbe6d808e3",
|
"sha256:8f5d2c39f3283e461de3655e03faf10e4742bb87387113f787a7724f32db1e48",
|
||||||
"sha256:9ffdaa5290422ac0f1688cb8adb1b94ca56cee3ad11f29f2ae301df8aecba7d1",
|
"sha256:97785604824981ec8c81850dd25c8071d5ce04717a34296eeac771231fbdd5cd",
|
||||||
"sha256:a0da79117952a9a41253696ed3e8b560a425197d4e41634a23b1507efe3273f1",
|
"sha256:a3046e8ab29b590d723821d0785598e0b2e32b636a0272a38409be43e3ae0550",
|
||||||
"sha256:a41f87bb93b8048fe866fa9e3d0c51e27fe55149035dcf5f43da4b56732c0a40",
|
"sha256:abb0b3f2cb606981c7432f690db23506b1db5899620ad274e29dbbbdd740e797",
|
||||||
"sha256:aa6fd016e9644406d0a61313e50348c706e911dca29736a3266fc9e28ec4ca6d",
|
"sha256:ac7c2046d907e3b4e2605a130d162b1b783c170292a11216479bb1deb7cadebe",
|
||||||
"sha256:ad54ed57bdfa3254d23ae04a4b1ce405954969c1b0550cc2d1d2990e8b439de1",
|
"sha256:af27b3fe5b6bf9cd01b8e1c5ddea0a0d0a1b8c37dc1c7452f1e90bf817539c6d",
|
||||||
"sha256:b012d023b4fb59183909b45d7f97fb493ef7a46d2838a5e716e3155081894605",
|
"sha256:b386b8b9d2b6a5e1e4eadd4e62335571244cb9193b7328c2b6e38b64cfda4f0e",
|
||||||
"sha256:b51b64432eed4c0744241e9ce5c70dcfecac866dff720e746d0a9c82f371dfa7",
|
"sha256:b66335bbdbb4c004c25ae01cc4a54fd199afbc1fd164233813c6d3c2293bb7e1",
|
||||||
"sha256:bbe81def9cf3e46f16ce01d9bfd8bea595e06505e51b7baf45115c77352675fd",
|
"sha256:d54f66c511ea01b9ef1d1a57420a93fbb9d48a08ec239f7d9c581092033156d0",
|
||||||
"sha256:c9559138690e1bd4ea6cd0954d22d1e9251e8025ce9ede5d0af0ceae4a401e43",
|
"sha256:de125151a53ecdb39df3cb3deb9951ed834dd6a110a9e795d985b10bb6db4532",
|
||||||
"sha256:e30506bcb03de8983f78884807e4fd95d8db6e65b69257eea05d13d519b83ac0",
|
"sha256:de7916380abaef4bb4891740879b1afcba2045aee51799dfd6d6ca9bdc71f35f",
|
||||||
"sha256:e33e86fd65f369f10608b08729c8f1c92ec7e0e485964670b4d2633a4812d36b",
|
"sha256:e2fefad268ff5c5b314794e27e359e48aeb9c8bb2cbb5748a071757a56f6bb8f",
|
||||||
"sha256:e441e8b7d587af0414d25e8d05e27040d78581388eed4c54c30c0c91aad3a379",
|
"sha256:e7b2bed4eea047a949296e618552d3fed00632dc1b795ee430289bdd0e3717f3",
|
||||||
"sha256:e8bb9c990ca9027b4214fa543fd4025818dc95f8b7abce79d61dc8a2112b561a",
|
"sha256:e87698e2fea5ca2f0a99dff0a64ce8110ea857b640de536c76d92aaa2a91ff3a",
|
||||||
"sha256:ef43ee91c193f827e49599e824385ec7c7f3cd152d74cb1dfe02cb135f264d83",
|
"sha256:ede888382882f07b9e4cd942255921ffd9f2901684198b88e247c7eabd27a000",
|
||||||
"sha256:ef467d86d3cfde8b39ea1b35090208b0447caaabd38405420830f7fd85fbdd56",
|
"sha256:f444de0565db46d26c9fa931ca14f497900a295bd5eba480fc3fad25af8c763e",
|
||||||
"sha256:f89b28772fc2562ed9ad871c865f5320ef761a7fcc188a935e21fe8b31a38ca9",
|
"sha256:fa994e8937e8ccc7e87395b7b35092818905cf27c651e3ff3e7f29729f5ce3ce",
|
||||||
"sha256:fddbab55a2473f1d3b8833ec6b7ac31e8211b0aa608df5ab09ce07f3727326de"
|
"sha256:febceb04ee7dd2aef08c2ff3d6f8a07de3052fc90137c507b0ede3ea80c21440"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.7'",
|
"markers": "python_version >= '3.7'",
|
||||||
"version": "==6.1"
|
"version": "==6.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"develop": {
|
"develop": {
|
||||||
|
@ -1142,32 +1149,32 @@
|
||||||
},
|
},
|
||||||
"black": {
|
"black": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:0269dfdea12442022e88043d2910429bed717b2d04523867a85dacce535916b8",
|
"sha256:057c3dc602eaa6fdc451069bd027a1b2635028b575a6c3acfd63193ced20d9c8",
|
||||||
"sha256:07204d078e25327aad9ed2c64790d681238686bce254c910de640c7cc4fc3aa6",
|
"sha256:08654d0797e65f2423f850fc8e16a0ce50925f9337fb4a4a176a7aa4026e63f8",
|
||||||
"sha256:08b34e85170d368c37ca7bf81cf67ac863c9d1963b2c1780c39102187ec8dd62",
|
"sha256:163baf4ef40e6897a2a9b83890e59141cc8c2a98f2dda5080dc15c00ee1e62cd",
|
||||||
"sha256:1a95915c98d6e32ca43809d46d932e2abc5f1f7d582ffbe65a5b4d1588af7445",
|
"sha256:1e08fb9a15c914b81dd734ddd7fb10513016e5ce7e6704bdd5e1251ceee51ac9",
|
||||||
"sha256:2588021038bd5ada078de606f2a804cadd0a3cc6a79cb3e9bb3a8bf581325a4c",
|
"sha256:4dd76e9468d5536abd40ffbc7a247f83b2324f0c050556d9c371c2b9a9a95e31",
|
||||||
"sha256:2fa6a0e965779c8f2afb286f9ef798df770ba2b6cee063c650b96adec22c056a",
|
"sha256:4f9de21bafcba9683853f6c96c2d515e364aee631b178eaa5145fc1c61a3cc92",
|
||||||
"sha256:34afe9da5056aa123b8bfda1664bfe6fb4e9c6f311d8e4a6eb089da9a9173bf9",
|
"sha256:61a0391772490ddfb8a693c067df1ef5227257e72b0e4108482b8d41b5aee13f",
|
||||||
"sha256:3897ae5a21ca132efa219c029cce5e6bfc9c3d34ed7e892113d199c0b1b444a2",
|
"sha256:6981eae48b3b33399c8757036c7f5d48a535b962a7c2310d19361edeef64ce29",
|
||||||
"sha256:40657e1b78212d582a0edecafef133cf1dd02e6677f539b669db4746150d38f6",
|
"sha256:7e53a8c630f71db01b28cd9602a1ada68c937cbf2c333e6ed041390d6968faf4",
|
||||||
"sha256:48b5760dcbfe5cf97fd4fba23946681f3a81514c6ab8a45b50da67ac8fbc6c7b",
|
"sha256:810d445ae6069ce64030c78ff6127cd9cd178a9ac3361435708b907d8a04c693",
|
||||||
"sha256:5242ecd9e990aeb995b6d03dc3b2d112d4a78f2083e5a8e86d566340ae80fec4",
|
"sha256:93601c2deb321b4bad8f95df408e3fb3943d85012dddb6121336b8e24a0d1218",
|
||||||
"sha256:5cdc2e2195212208fbcae579b931407c1fa9997584f0a415421748aeafff1168",
|
"sha256:992e451b04667116680cb88f63449267c13e1ad134f30087dec8527242e9862a",
|
||||||
"sha256:5d7b06ea8816cbd4becfe5f70accae953c53c0e53aa98730ceccb0395520ee5d",
|
"sha256:9db528bccb9e8e20c08e716b3b09c6bdd64da0dd129b11e160bf082d4642ac23",
|
||||||
"sha256:7258c27115c1e3b5de9ac6c4f9957e3ee2c02c0b39222a24dc7aa03ba0e986f5",
|
"sha256:a0057f800de6acc4407fe75bb147b0c2b5cbb7c3ed110d3e5999cd01184d53b0",
|
||||||
"sha256:854c06fb86fd854140f37fb24dbf10621f5dab9e3b0c29a690ba595e3d543024",
|
"sha256:ba15742a13de85e9b8f3239c8f807723991fbfae24bad92d34a2b12e81904982",
|
||||||
"sha256:a21725862d0e855ae05da1dd25e3825ed712eaaccef6b03017fe0853a01aa45e",
|
"sha256:bce4f25c27c3435e4dace4815bcb2008b87e167e3bf4ee47ccdc5ce906eb4894",
|
||||||
"sha256:a83fe522d9698d8f9a101b860b1ee154c1d25f8a82ceb807d319f085b2627c5b",
|
"sha256:ca610d29415ee1a30a3f30fab7a8f4144e9d34c89a235d81292a1edb2b55f540",
|
||||||
"sha256:b3d64db762eae4a5ce04b6e3dd745dcca0fb9560eb931a5be97472e38652a161",
|
"sha256:d533d5e3259720fdbc1b37444491b024003e012c5173f7d06825a77508085430",
|
||||||
"sha256:e298d588744efda02379521a19639ebcd314fba7a49be22136204d7ed1782717",
|
"sha256:d84f29eb3ee44859052073b7636533ec995bd0f64e2fb43aeceefc70090e752b",
|
||||||
"sha256:e2c8dfa14677f90d976f68e0c923947ae68fa3961d61ee30976c388adc0b02c8",
|
"sha256:e37c99f89929af50ffaf912454b3e3b47fd64109659026b678c091a4cd450fb2",
|
||||||
"sha256:ecba2a15dfb2d97105be74bbfe5128bc5e9fa8477d8c46766505c1dda5883aac",
|
"sha256:e8a6ae970537e67830776488bca52000eaa37fa63b9988e8c487458d9cd5ace6",
|
||||||
"sha256:fc1ec9aa6f4d98d022101e015261c056ddebe3da6a8ccfc2c792cbe0349d48b7"
|
"sha256:faf2ee02e6612577ba0181f4347bcbcf591eb122f7841ae5ba233d12c39dcb4d"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"markers": "python_version >= '3.8'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==24.1.1"
|
"version": "==24.2.0"
|
||||||
},
|
},
|
||||||
"blinker": {
|
"blinker": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -1179,12 +1186,12 @@
|
||||||
},
|
},
|
||||||
"boto3": {
|
"boto3": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:65acfe7f1cf2a9b7df3d4edb87c8022e02685825bd1957e7bb678cc0d09f5e5f",
|
"sha256:8b3f5cc7fbedcbb22271c328039df8a6ab343001e746e0cdb24774c426cadcf8",
|
||||||
"sha256:73f5ec89cb3ddb3ed577317889fd2f2df783f66b6502a9a4239979607e33bf74"
|
"sha256:f201b6a416f809283d554c652211eecec9fe3a52ed4063dab3f3e7aea7571d9c"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"markers": "python_version >= '3.8'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==1.34.37"
|
"version": "==1.34.54"
|
||||||
},
|
},
|
||||||
"boto3-mocking": {
|
"boto3-mocking": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -1197,28 +1204,28 @@
|
||||||
},
|
},
|
||||||
"boto3-stubs": {
|
"boto3-stubs": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:97b5ca3d3145385acde5af46ca2da3fc74f433545034c36183f389e99771516e",
|
"sha256:7db5194e47f76e0010cd00b6ad9725db114d6a3fd04e52ceed3ef1181fe326bc",
|
||||||
"sha256:c6618c7126bac0337c05e161e9c428febc57d6a24d7ff62de46e67761f402c57"
|
"sha256:c7b2e8b99f4896cf1226df47d4badaaa8df7426008c96a428bf00205695669e9"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"markers": "python_version >= '3.8'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==1.34.37"
|
"version": "==1.34.54"
|
||||||
},
|
},
|
||||||
"botocore": {
|
"botocore": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:2a5bf33aacd2d970afd3d492e179e06ea98a5469030d5cfe7a2ad9995f7bb2ef",
|
"sha256:4061ff4be3efcf53547ebadf2c94d419dfc8be7beec24e9fa1819599ffd936fa",
|
||||||
"sha256:3c46ddb1679e6ef45ca78b48665398636bda532a07cd476e4b500697d13d9a99"
|
"sha256:bf215d93e9d5544c593962780d194e74c6ee40b883d0b885e62ef35fc0ec01e5"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.8'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==1.34.37"
|
"version": "==1.34.54"
|
||||||
},
|
},
|
||||||
"botocore-stubs": {
|
"botocore-stubs": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:087cd42973edcb5527dc97eec87fa29fffecc39691249486e02045677d4a2dbe",
|
"sha256:958f0084322dc9e549f73151b686fa51b15858fb2b3a573b9f4367f073fff463",
|
||||||
"sha256:d6bcea8a6872aa46d389027dc5c022241fd0a2047a8b858aa5005e6151ed30a7"
|
"sha256:bcc35bfbd14d1261813681c40108f2ce85fdf082c15b0a04016d3c22dd93b73f"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.8' and python_version < '4.0'",
|
"markers": "python_version >= '3.8' and python_version < '4.0'",
|
||||||
"version": "==1.34.37"
|
"version": "==1.34.54"
|
||||||
},
|
},
|
||||||
"click": {
|
"click": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -1427,11 +1434,11 @@
|
||||||
},
|
},
|
||||||
"python-dateutil": {
|
"python-dateutil": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86",
|
"sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3",
|
||||||
"sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"
|
"sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||||
"version": "==2.8.2"
|
"version": "==2.9.0.post0"
|
||||||
},
|
},
|
||||||
"pyyaml": {
|
"pyyaml": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -1492,11 +1499,11 @@
|
||||||
},
|
},
|
||||||
"rich": {
|
"rich": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:5cb5123b5cf9ee70584244246816e9114227e0b98ad9176eede6ad54bf5403fa",
|
"sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222",
|
||||||
"sha256:6da14c108c4866ee9520bbffa71f6fe3962e193b7da68720583850cd4548e235"
|
"sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432"
|
||||||
],
|
],
|
||||||
"markers": "python_full_version >= '3.7.0'",
|
"markers": "python_full_version >= '3.7.0'",
|
||||||
"version": "==13.7.0"
|
"version": "==13.7.1"
|
||||||
},
|
},
|
||||||
"s3transfer": {
|
"s3transfer": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -1532,11 +1539,11 @@
|
||||||
},
|
},
|
||||||
"stevedore": {
|
"stevedore": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:8cc040628f3cea5d7128f2e76cf486b2251a4e543c7b938f58d9a377f6694a2d",
|
"sha256:1c15d95766ca0569cad14cb6272d4d31dae66b011a929d7c18219c176ea1b5c9",
|
||||||
"sha256:a54534acf9b89bc7ed264807013b505bf07f74dbe4bcfa37d32bd063870b087c"
|
"sha256:46b93ca40e1114cea93d738a6c1e365396981bb6bb78c27045b7587c9473544d"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.8'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==5.1.0"
|
"version": "==5.2.0"
|
||||||
},
|
},
|
||||||
"tomli": {
|
"tomli": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -1548,11 +1555,11 @@
|
||||||
},
|
},
|
||||||
"types-awscrt": {
|
"types-awscrt": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:06a859189a329ca8e66d56ceeef2391488e39b878fbd2141f115eab4d416fe22",
|
"sha256:61811bbf4de95248939f9276a434be93d2b95f6ccfe8aa94e56999e9778cfcc2",
|
||||||
"sha256:f61a120d3e98ee1387bc5ca4b93437f258cc5c2af1f55f8634ec4cee5729f178"
|
"sha256:79d5bfb01f64701b6cf442e89a37d9c4dc6dbb79a46f2f611739b2418d30ecfd"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.7' and python_version < '4.0'",
|
"markers": "python_version >= '3.7' and python_version < '4.0'",
|
||||||
"version": "==0.20.3"
|
"version": "==0.20.5"
|
||||||
},
|
},
|
||||||
"types-cachetools": {
|
"types-cachetools": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -1580,12 +1587,12 @@
|
||||||
},
|
},
|
||||||
"types-requests": {
|
"types-requests": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:03a28ce1d7cd54199148e043b2079cdded22d6795d19a2c2a6791a4b2b5e2eb5",
|
"sha256:a82807ec6ddce8f00fe0e949da6d6bc1fbf1715420218a9640d695f70a9e5a9b",
|
||||||
"sha256:9592a9a4cb92d6d75d9b491a41477272b710e021011a2a3061157e2fb1f1a5d1"
|
"sha256:f1721dba8385958f504a5386240b92de4734e047a08a40751c1654d1ac3349c5"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"markers": "python_version >= '3.8'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==2.31.0.20240125"
|
"version": "==2.31.0.20240218"
|
||||||
},
|
},
|
||||||
"types-s3transfer": {
|
"types-s3transfer": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -1597,12 +1604,12 @@
|
||||||
},
|
},
|
||||||
"typing-extensions": {
|
"typing-extensions": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783",
|
"sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475",
|
||||||
"sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"
|
"sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"markers": "python_version >= '3.8'",
|
"markers": "python_version >= '3.8'",
|
||||||
"version": "==4.9.0"
|
"version": "==4.10.0"
|
||||||
},
|
},
|
||||||
"urllib3": {
|
"urllib3": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
|
|
@ -66,6 +66,7 @@ services:
|
||||||
command: >
|
command: >
|
||||||
bash -c " python manage.py migrate &&
|
bash -c " python manage.py migrate &&
|
||||||
python manage.py load &&
|
python manage.py load &&
|
||||||
|
python manage.py createcachetable &&
|
||||||
python manage.py runserver 0.0.0.0:8080"
|
python manage.py runserver 0.0.0.0:8080"
|
||||||
|
|
||||||
db:
|
db:
|
||||||
|
|
|
@ -2,10 +2,6 @@
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from time import sleep
|
|
||||||
from gevent import Timeout
|
|
||||||
from epplibwrapper.utility.pool_status import PoolStatus
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from epplib.client import Client
|
from epplib.client import Client
|
||||||
from epplib import commands
|
from epplib import commands
|
||||||
|
@ -18,8 +14,6 @@ from django.conf import settings
|
||||||
|
|
||||||
from .cert import Cert, Key
|
from .cert import Cert, Key
|
||||||
from .errors import ErrorCode, LoginError, RegistryError
|
from .errors import ErrorCode, LoginError, RegistryError
|
||||||
from .socket import Socket
|
|
||||||
from .utility.pool import EPPConnectionPool
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -43,8 +37,12 @@ class EPPLibWrapper:
|
||||||
ATTN: This should not be used directly. Use `Domain` from domain.py.
|
ATTN: This should not be used directly. Use `Domain` from domain.py.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, start_connection_pool=True) -> None:
|
def __init__(self) -> None:
|
||||||
"""Initialize settings which will be used for all connections."""
|
"""Initialize settings which will be used for all connections."""
|
||||||
|
# set _client to None initially. In the event that the __init__ fails
|
||||||
|
# before _client initializes, app should still start and be in a state
|
||||||
|
# that it can attempt _client initialization on send attempts
|
||||||
|
self._client = None # type: ignore
|
||||||
# prepare (but do not send) a Login command
|
# prepare (but do not send) a Login command
|
||||||
self._login = commands.Login(
|
self._login = commands.Login(
|
||||||
cl_id=settings.SECRET_REGISTRY_CL_ID,
|
cl_id=settings.SECRET_REGISTRY_CL_ID,
|
||||||
|
@ -54,9 +52,19 @@ class EPPLibWrapper:
|
||||||
"urn:ietf:params:xml:ns:contact-1.0",
|
"urn:ietf:params:xml:ns:contact-1.0",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
try:
|
||||||
|
self._initialize_client()
|
||||||
|
except Exception:
|
||||||
|
logger.warning("Unable to configure epplib. Registrar cannot contact registry.")
|
||||||
|
|
||||||
|
def _initialize_client(self) -> None:
|
||||||
|
"""Initialize a client, assuming _login defined. Sets _client to initialized
|
||||||
|
client. Raises errors if initialization fails.
|
||||||
|
This method will be called at app initialization, and also during retries."""
|
||||||
# establish a client object with a TCP socket transport
|
# establish a client object with a TCP socket transport
|
||||||
self._client = Client(
|
# note that type: ignore added in several places because linter complains
|
||||||
|
# about _client initially being set to None, and None type doesn't match code
|
||||||
|
self._client = Client( # type: ignore
|
||||||
SocketTransport(
|
SocketTransport(
|
||||||
settings.SECRET_REGISTRY_HOSTNAME,
|
settings.SECRET_REGISTRY_HOSTNAME,
|
||||||
cert_file=CERT.filename,
|
cert_file=CERT.filename,
|
||||||
|
@ -64,176 +72,95 @@ class EPPLibWrapper:
|
||||||
password=settings.SECRET_REGISTRY_KEY_PASSPHRASE,
|
password=settings.SECRET_REGISTRY_KEY_PASSPHRASE,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
try:
|
||||||
|
# use the _client object to connect
|
||||||
|
self._client.connect() # type: ignore
|
||||||
|
response = self._client.send(self._login) # type: ignore
|
||||||
|
if response.code >= 2000: # type: ignore
|
||||||
|
self._client.close() # type: ignore
|
||||||
|
raise LoginError(response.msg) # type: ignore
|
||||||
|
except TransportError as err:
|
||||||
|
message = "_initialize_client failed to execute due to a connection error."
|
||||||
|
logger.error(f"{message} Error: {err}")
|
||||||
|
raise RegistryError(message, code=ErrorCode.TRANSPORT_ERROR) from err
|
||||||
|
except LoginError as err:
|
||||||
|
raise err
|
||||||
|
except Exception as err:
|
||||||
|
message = "_initialize_client failed to execute due to an unknown error."
|
||||||
|
logger.error(f"{message} Error: {err}")
|
||||||
|
raise RegistryError(message) from err
|
||||||
|
|
||||||
self.pool_options = {
|
def _disconnect(self) -> None:
|
||||||
# Pool size
|
"""Close the connection."""
|
||||||
"size": settings.EPP_CONNECTION_POOL_SIZE,
|
try:
|
||||||
# Which errors the pool should look out for.
|
self._client.send(commands.Logout()) # type: ignore
|
||||||
# Avoid changing this unless necessary,
|
self._client.close() # type: ignore
|
||||||
# it can and will break things.
|
except Exception:
|
||||||
"exc_classes": (TransportError,),
|
logger.warning("Connection to registry was not cleanly closed.")
|
||||||
# Occasionally pings the registry to keep the connection alive.
|
|
||||||
# Value in seconds => (keepalive / size)
|
|
||||||
"keepalive": settings.POOL_KEEP_ALIVE,
|
|
||||||
}
|
|
||||||
|
|
||||||
self._pool = None
|
|
||||||
|
|
||||||
# Tracks the status of the pool
|
|
||||||
self.pool_status = PoolStatus()
|
|
||||||
|
|
||||||
if start_connection_pool:
|
|
||||||
self.start_connection_pool()
|
|
||||||
|
|
||||||
def _send(self, command):
|
def _send(self, command):
|
||||||
"""Helper function used by `send`."""
|
"""Helper function used by `send`."""
|
||||||
cmd_type = command.__class__.__name__
|
cmd_type = command.__class__.__name__
|
||||||
|
|
||||||
# Start a timeout to check if the pool is hanging
|
|
||||||
timeout = Timeout(settings.POOL_TIMEOUT)
|
|
||||||
timeout.start()
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if not self.pool_status.connection_success:
|
# check for the condition that the _client was not initialized properly
|
||||||
raise LoginError("Couldn't connect to the registry after three attempts")
|
# at app initialization
|
||||||
with self._pool.get() as connection:
|
if self._client is None:
|
||||||
response = connection.send(command)
|
self._initialize_client()
|
||||||
except Timeout as t:
|
response = self._client.send(command)
|
||||||
# If more than one pool exists,
|
|
||||||
# multiple timeouts can be floating around.
|
|
||||||
# We need to be specific as to which we are targeting.
|
|
||||||
if t is timeout:
|
|
||||||
# Flag that the pool is frozen,
|
|
||||||
# then restart the pool.
|
|
||||||
self.pool_status.pool_hanging = True
|
|
||||||
logger.error("Pool timed out")
|
|
||||||
self.start_connection_pool()
|
|
||||||
except (ValueError, ParsingError) as err:
|
except (ValueError, ParsingError) as err:
|
||||||
message = f"{cmd_type} failed to execute due to some syntax error."
|
message = f"{cmd_type} failed to execute due to some syntax error."
|
||||||
logger.error(f"{message} Error: {err}", exc_info=True)
|
logger.error(f"{message} Error: {err}")
|
||||||
raise RegistryError(message) from err
|
raise RegistryError(message) from err
|
||||||
except TransportError as err:
|
except TransportError as err:
|
||||||
message = f"{cmd_type} failed to execute due to a connection error."
|
message = f"{cmd_type} failed to execute due to a connection error."
|
||||||
logger.error(f"{message} Error: {err}", exc_info=True)
|
logger.error(f"{message} Error: {err}")
|
||||||
raise RegistryError(message, code=ErrorCode.TRANSPORT_ERROR) from err
|
raise RegistryError(message, code=ErrorCode.TRANSPORT_ERROR) from err
|
||||||
except LoginError as err:
|
except LoginError as err:
|
||||||
# For linter due to it not liking this line length
|
# For linter due to it not liking this line length
|
||||||
text = "failed to execute due to a registry login error."
|
text = "failed to execute due to a registry login error."
|
||||||
message = f"{cmd_type} {text}"
|
message = f"{cmd_type} {text}"
|
||||||
logger.error(f"{message} Error: {err}", exc_info=True)
|
logger.error(f"{message} Error: {err}")
|
||||||
raise RegistryError(message) from err
|
raise RegistryError(message) from err
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
message = f"{cmd_type} failed to execute due to an unknown error."
|
message = f"{cmd_type} failed to execute due to an unknown error."
|
||||||
logger.error(f"{message} Error: {err}", exc_info=True)
|
logger.error(f"{message} Error: {err}")
|
||||||
raise RegistryError(message) from err
|
raise RegistryError(message) from err
|
||||||
else:
|
else:
|
||||||
if response.code >= 2000:
|
if response.code >= 2000:
|
||||||
raise RegistryError(response.msg, code=response.code)
|
raise RegistryError(response.msg, code=response.code)
|
||||||
else:
|
else:
|
||||||
return response
|
return response
|
||||||
finally:
|
|
||||||
# Close the timeout no matter what happens
|
def _retry(self, command):
|
||||||
timeout.close()
|
"""Retry sending a command through EPP by re-initializing the client
|
||||||
|
and then sending the command."""
|
||||||
|
# re-initialize by disconnecting and initial
|
||||||
|
self._disconnect()
|
||||||
|
self._initialize_client()
|
||||||
|
return self._send(command)
|
||||||
|
|
||||||
def send(self, command, *, cleaned=False):
|
def send(self, command, *, cleaned=False):
|
||||||
"""Login, send the command, then close the connection. Tries 3 times."""
|
"""Login, the send the command. Retry once if an error is found"""
|
||||||
# try to prevent use of this method without appropriate safeguards
|
# try to prevent use of this method without appropriate safeguards
|
||||||
|
cmd_type = command.__class__.__name__
|
||||||
if not cleaned:
|
if not cleaned:
|
||||||
raise ValueError("Please sanitize user input before sending it.")
|
raise ValueError("Please sanitize user input before sending it.")
|
||||||
|
try:
|
||||||
# Reopen the pool if its closed
|
return self._send(command)
|
||||||
# Only occurs when a login error is raised, after connection is successful
|
except RegistryError as err:
|
||||||
if not self.pool_status.pool_running:
|
if (
|
||||||
# We want to reopen the connection pool,
|
err.is_transport_error()
|
||||||
# but we don't want the end user to wait while it opens.
|
or err.is_connection_error()
|
||||||
# Raise syntax doesn't allow this, so we use a try/catch
|
or err.is_session_error()
|
||||||
# block.
|
or err.is_server_error()
|
||||||
try:
|
or err.should_retry()
|
||||||
logger.error("Can't contact the Registry. Pool was not running.")
|
):
|
||||||
raise RegistryError("Can't contact the Registry. Pool was not running.")
|
message = f"{cmd_type} failed and will be retried"
|
||||||
except RegistryError as err:
|
logger.info(f"{message} Error: {err}")
|
||||||
|
return self._retry(command)
|
||||||
|
else:
|
||||||
raise err
|
raise err
|
||||||
finally:
|
|
||||||
# Code execution will halt after here.
|
|
||||||
# The end user will need to recall .send.
|
|
||||||
self.start_connection_pool()
|
|
||||||
|
|
||||||
counter = 0 # we'll try 3 times
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
return self._send(command)
|
|
||||||
except RegistryError as err:
|
|
||||||
if counter < 3 and (err.should_retry() or err.is_transport_error()):
|
|
||||||
logger.info(f"Retrying transport error. Attempt #{counter+1} of 3.")
|
|
||||||
counter += 1
|
|
||||||
sleep((counter * 50) / 1000) # sleep 50 ms to 150 ms
|
|
||||||
else: # don't try again
|
|
||||||
raise err
|
|
||||||
|
|
||||||
def get_pool(self):
|
|
||||||
"""Get the current pool instance"""
|
|
||||||
return self._pool
|
|
||||||
|
|
||||||
def _create_pool(self, client, login, options):
|
|
||||||
"""Creates and returns new pool instance"""
|
|
||||||
logger.info("New pool was created")
|
|
||||||
return EPPConnectionPool(client, login, options)
|
|
||||||
|
|
||||||
def start_connection_pool(self, restart_pool_if_exists=True):
|
|
||||||
"""Starts a connection pool for the registry.
|
|
||||||
|
|
||||||
restart_pool_if_exists -> bool:
|
|
||||||
If an instance of the pool already exists,
|
|
||||||
then then that instance will be killed first.
|
|
||||||
It is generally recommended to keep this enabled.
|
|
||||||
"""
|
|
||||||
# Since we reuse the same creds for each pool, we can test on
|
|
||||||
# one socket, and if successful, then we know we can connect.
|
|
||||||
if not self._test_registry_connection_success():
|
|
||||||
logger.warning("start_connection_pool() -> Cannot contact the Registry")
|
|
||||||
self.pool_status.connection_success = False
|
|
||||||
else:
|
|
||||||
self.pool_status.connection_success = True
|
|
||||||
|
|
||||||
# If this function is reinvoked, then ensure
|
|
||||||
# that we don't have duplicate data sitting around.
|
|
||||||
if self._pool is not None and restart_pool_if_exists:
|
|
||||||
logger.info("Connection pool restarting...")
|
|
||||||
self.kill_pool()
|
|
||||||
logger.info("Old pool killed")
|
|
||||||
|
|
||||||
self._pool = self._create_pool(self._client, self._login, self.pool_options)
|
|
||||||
|
|
||||||
self.pool_status.pool_running = True
|
|
||||||
self.pool_status.pool_hanging = False
|
|
||||||
|
|
||||||
logger.info("Connection pool started")
|
|
||||||
|
|
||||||
def kill_pool(self):
|
|
||||||
"""Kills the existing pool. Use this instead
|
|
||||||
of self._pool = None, as that doesn't clear
|
|
||||||
gevent instances."""
|
|
||||||
if self._pool is not None:
|
|
||||||
self._pool.kill_all_connections()
|
|
||||||
self._pool = None
|
|
||||||
self.pool_status.pool_running = False
|
|
||||||
return None
|
|
||||||
logger.info("kill_pool() was invoked but there was no pool to delete")
|
|
||||||
|
|
||||||
def _test_registry_connection_success(self):
|
|
||||||
"""Check that determines if our login
|
|
||||||
credentials are valid, and/or if the Registrar
|
|
||||||
can be contacted
|
|
||||||
"""
|
|
||||||
# This is closed in test_connection_success
|
|
||||||
socket = Socket(self._client, self._login)
|
|
||||||
can_login = False
|
|
||||||
|
|
||||||
# Something went wrong if this doesn't exist
|
|
||||||
if hasattr(socket, "test_connection_success"):
|
|
||||||
can_login = socket.test_connection_success()
|
|
||||||
|
|
||||||
return can_login
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -241,5 +168,4 @@ try:
|
||||||
CLIENT = EPPLibWrapper()
|
CLIENT = EPPLibWrapper()
|
||||||
logger.info("registry client initialized")
|
logger.info("registry client initialized")
|
||||||
except Exception:
|
except Exception:
|
||||||
CLIENT = None # type: ignore
|
logger.warning("Unable to configure epplib. Registrar cannot contact registry.")
|
||||||
logger.warning("Unable to configure epplib. Registrar cannot contact registry.", exc_info=True)
|
|
||||||
|
|
|
@ -1,102 +0,0 @@
|
||||||
import logging
|
|
||||||
from time import sleep
|
|
||||||
|
|
||||||
try:
|
|
||||||
from epplib import commands
|
|
||||||
from epplib.client import Client
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
from .errors import LoginError
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class Socket:
|
|
||||||
"""Context manager which establishes a TCP connection with registry."""
|
|
||||||
|
|
||||||
def __init__(self, client: Client, login: commands.Login) -> None:
|
|
||||||
"""Save the epplib client and login details."""
|
|
||||||
self.client = client
|
|
||||||
self.login = login
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
"""Runs connect(), which opens a connection with EPPLib."""
|
|
||||||
self.connect()
|
|
||||||
|
|
||||||
def __exit__(self, *args, **kwargs):
|
|
||||||
"""Runs disconnect(), which closes a connection with EPPLib."""
|
|
||||||
self.disconnect()
|
|
||||||
|
|
||||||
def connect(self):
|
|
||||||
"""Use epplib to connect."""
|
|
||||||
logger.info("Opening socket on connection pool")
|
|
||||||
self.client.connect()
|
|
||||||
response = self.client.send(self.login)
|
|
||||||
if self.is_login_error(response.code):
|
|
||||||
self.client.close()
|
|
||||||
raise LoginError(response.msg)
|
|
||||||
return self.client
|
|
||||||
|
|
||||||
def disconnect(self):
|
|
||||||
"""Close the connection."""
|
|
||||||
logger.info("Closing socket on connection pool")
|
|
||||||
try:
|
|
||||||
self.client.send(commands.Logout())
|
|
||||||
self.client.close()
|
|
||||||
except Exception as err:
|
|
||||||
logger.warning("Connection to registry was not cleanly closed.")
|
|
||||||
logger.error(err)
|
|
||||||
|
|
||||||
def send(self, command):
|
|
||||||
"""Sends a command to the registry.
|
|
||||||
If the RegistryError code is >= 2000,
|
|
||||||
then this function raises a LoginError.
|
|
||||||
The calling function should handle this."""
|
|
||||||
response = self.client.send(command)
|
|
||||||
if self.is_login_error(response.code):
|
|
||||||
self.client.close()
|
|
||||||
raise LoginError(response.msg)
|
|
||||||
|
|
||||||
return response
|
|
||||||
|
|
||||||
def is_login_error(self, code):
|
|
||||||
"""Returns the result of code >= 2000 for RegistryError.
|
|
||||||
This indicates that something weird happened on the Registry,
|
|
||||||
and that we should return a LoginError."""
|
|
||||||
return code >= 2000
|
|
||||||
|
|
||||||
def test_connection_success(self):
|
|
||||||
"""Tests if a successful connection can be made with the registry.
|
|
||||||
Tries 3 times."""
|
|
||||||
# Something went wrong if this doesn't exist
|
|
||||||
if not hasattr(self.client, "connect"):
|
|
||||||
logger.warning("self.client does not have a connect attribute")
|
|
||||||
return False
|
|
||||||
|
|
||||||
counter = 0 # we'll try 3 times
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
self.client.connect()
|
|
||||||
response = self.client.send(self.login)
|
|
||||||
except (LoginError, OSError) as err:
|
|
||||||
logger.error(err)
|
|
||||||
should_retry = True
|
|
||||||
if isinstance(err, LoginError):
|
|
||||||
should_retry = err.should_retry()
|
|
||||||
if should_retry and counter < 3:
|
|
||||||
counter += 1
|
|
||||||
sleep((counter * 50) / 1000) # sleep 50 ms to 150 ms
|
|
||||||
else: # don't try again
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
# If we encounter a login error, fail
|
|
||||||
if self.is_login_error(response.code):
|
|
||||||
logger.warning("A login error was found in test_connection_success")
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Otherwise, just return true
|
|
||||||
return True
|
|
||||||
finally:
|
|
||||||
self.disconnect()
|
|
257
src/epplibwrapper/tests/test_client.py
Normal file
257
src/epplibwrapper/tests/test_client.py
Normal file
|
@ -0,0 +1,257 @@
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
from django.test import TestCase
|
||||||
|
from epplibwrapper.client import EPPLibWrapper
|
||||||
|
from epplibwrapper.errors import RegistryError, LoginError
|
||||||
|
from .common import less_console_noise
|
||||||
|
import logging
|
||||||
|
|
||||||
|
try:
|
||||||
|
from epplib.exceptions import TransportError
|
||||||
|
from epplib.responses import Result
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class TestClient(TestCase):
|
||||||
|
"""Test the EPPlibwrapper client"""
|
||||||
|
|
||||||
|
def fake_result(self, code, msg):
|
||||||
|
"""Helper function to create a fake Result object"""
|
||||||
|
return Result(code=code, msg=msg, res_data=[], cl_tr_id="cl_tr_id", sv_tr_id="sv_tr_id")
|
||||||
|
|
||||||
|
@patch("epplibwrapper.client.Client")
|
||||||
|
def test_initialize_client_success(self, mock_client):
|
||||||
|
"""Test when the initialize_client is successful"""
|
||||||
|
with less_console_noise():
|
||||||
|
# Mock the Client instance and its methods
|
||||||
|
mock_connect = MagicMock()
|
||||||
|
# Create a mock Result instance
|
||||||
|
mock_result = MagicMock(spec=Result)
|
||||||
|
mock_result.code = 200
|
||||||
|
mock_result.msg = "Success"
|
||||||
|
mock_result.res_data = ["data1", "data2"]
|
||||||
|
mock_result.cl_tr_id = "client_id"
|
||||||
|
mock_result.sv_tr_id = "server_id"
|
||||||
|
mock_send = MagicMock(return_value=mock_result)
|
||||||
|
mock_client.return_value.connect = mock_connect
|
||||||
|
mock_client.return_value.send = mock_send
|
||||||
|
|
||||||
|
# Create EPPLibWrapper instance and initialize client
|
||||||
|
wrapper = EPPLibWrapper()
|
||||||
|
|
||||||
|
# Assert that connect method is called once
|
||||||
|
mock_connect.assert_called_once()
|
||||||
|
# Assert that _client is not None after initialization
|
||||||
|
self.assertIsNotNone(wrapper._client)
|
||||||
|
|
||||||
|
@patch("epplibwrapper.client.Client")
|
||||||
|
def test_initialize_client_transport_error(self, mock_client):
|
||||||
|
"""Test when the send(login) step of initialize_client raises a TransportError."""
|
||||||
|
with less_console_noise():
|
||||||
|
# Mock the Client instance and its methods
|
||||||
|
mock_connect = MagicMock()
|
||||||
|
mock_send = MagicMock(side_effect=TransportError("Transport error"))
|
||||||
|
mock_client.return_value.connect = mock_connect
|
||||||
|
mock_client.return_value.send = mock_send
|
||||||
|
|
||||||
|
with self.assertRaises(RegistryError):
|
||||||
|
# Create EPPLibWrapper instance and initialize client
|
||||||
|
# if functioning as expected, initial __init__ should except
|
||||||
|
# and log any Exception raised
|
||||||
|
wrapper = EPPLibWrapper()
|
||||||
|
# so call _initialize_client a second time directly to test
|
||||||
|
# the raised exception
|
||||||
|
wrapper._initialize_client()
|
||||||
|
|
||||||
|
@patch("epplibwrapper.client.Client")
|
||||||
|
def test_initialize_client_login_error(self, mock_client):
|
||||||
|
"""Test when the send(login) step of initialize_client returns (2400) comamnd failed code."""
|
||||||
|
with less_console_noise():
|
||||||
|
# Mock the Client instance and its methods
|
||||||
|
mock_connect = MagicMock()
|
||||||
|
# Create a mock Result instance
|
||||||
|
mock_result = MagicMock(spec=Result)
|
||||||
|
mock_result.code = 2400
|
||||||
|
mock_result.msg = "Login failed"
|
||||||
|
mock_result.res_data = ["data1", "data2"]
|
||||||
|
mock_result.cl_tr_id = "client_id"
|
||||||
|
mock_result.sv_tr_id = "server_id"
|
||||||
|
mock_send = MagicMock(return_value=mock_result)
|
||||||
|
mock_client.return_value.connect = mock_connect
|
||||||
|
mock_client.return_value.send = mock_send
|
||||||
|
|
||||||
|
with self.assertRaises(LoginError):
|
||||||
|
# Create EPPLibWrapper instance and initialize client
|
||||||
|
# if functioning as expected, initial __init__ should except
|
||||||
|
# and log any Exception raised
|
||||||
|
wrapper = EPPLibWrapper()
|
||||||
|
# so call _initialize_client a second time directly to test
|
||||||
|
# the raised exception
|
||||||
|
wrapper._initialize_client()
|
||||||
|
|
||||||
|
@patch("epplibwrapper.client.Client")
|
||||||
|
def test_initialize_client_unknown_exception(self, mock_client):
|
||||||
|
"""Test when the send(login) step of initialize_client raises an unexpected Exception."""
|
||||||
|
with less_console_noise():
|
||||||
|
# Mock the Client instance and its methods
|
||||||
|
mock_connect = MagicMock()
|
||||||
|
mock_send = MagicMock(side_effect=Exception("Unknown exception"))
|
||||||
|
mock_client.return_value.connect = mock_connect
|
||||||
|
mock_client.return_value.send = mock_send
|
||||||
|
|
||||||
|
with self.assertRaises(RegistryError):
|
||||||
|
# Create EPPLibWrapper instance and initialize client
|
||||||
|
# if functioning as expected, initial __init__ should except
|
||||||
|
# and log any Exception raised
|
||||||
|
wrapper = EPPLibWrapper()
|
||||||
|
# so call _initialize_client a second time directly to test
|
||||||
|
# the raised exception
|
||||||
|
wrapper._initialize_client()
|
||||||
|
|
||||||
|
@patch("epplibwrapper.client.Client")
|
||||||
|
def test_initialize_client_fails_recovers_with_send_command(self, mock_client):
|
||||||
|
"""Test when the initialize_client fails on the connect() step. And then a subsequent
|
||||||
|
call to send() should recover and re-initialize the client and properly return
|
||||||
|
the successful send command.
|
||||||
|
Flow:
|
||||||
|
Initialization step fails at app init
|
||||||
|
Send command fails (with 2400 code) prompting retry
|
||||||
|
Client closes and re-initializes, and command is sent successfully"""
|
||||||
|
with less_console_noise():
|
||||||
|
# Mock the Client instance and its methods
|
||||||
|
# close() should return successfully
|
||||||
|
mock_close = MagicMock()
|
||||||
|
mock_client.return_value.close = mock_close
|
||||||
|
# Create success and failure results
|
||||||
|
command_success_result = self.fake_result(1000, "Command completed successfully")
|
||||||
|
command_failure_result = self.fake_result(2400, "Command failed")
|
||||||
|
# side_effect for the connect() calls
|
||||||
|
# first connect() should raise an Exception
|
||||||
|
# subsequent connect() calls should return success
|
||||||
|
connect_call_count = 0
|
||||||
|
|
||||||
|
def connect_side_effect(*args, **kwargs):
|
||||||
|
nonlocal connect_call_count
|
||||||
|
connect_call_count += 1
|
||||||
|
if connect_call_count == 1:
|
||||||
|
raise Exception("Connection failed")
|
||||||
|
else:
|
||||||
|
return command_success_result
|
||||||
|
|
||||||
|
mock_connect = MagicMock(side_effect=connect_side_effect)
|
||||||
|
mock_client.return_value.connect = mock_connect
|
||||||
|
# side_effect for the send() calls
|
||||||
|
# first send will be the send("InfoDomainCommand") and should fail
|
||||||
|
# subsequend send() calls should return success
|
||||||
|
send_call_count = 0
|
||||||
|
|
||||||
|
def send_side_effect(*args, **kwargs):
|
||||||
|
nonlocal send_call_count
|
||||||
|
send_call_count += 1
|
||||||
|
if send_call_count == 1:
|
||||||
|
return command_failure_result
|
||||||
|
else:
|
||||||
|
return command_success_result
|
||||||
|
|
||||||
|
mock_send = MagicMock(side_effect=send_side_effect)
|
||||||
|
mock_client.return_value.send = mock_send
|
||||||
|
# Create EPPLibWrapper instance and call send command
|
||||||
|
wrapper = EPPLibWrapper()
|
||||||
|
wrapper.send("InfoDomainCommand", cleaned=True)
|
||||||
|
# two connect() calls should be made, the initial failed connect()
|
||||||
|
# and the successful connect() during retry()
|
||||||
|
self.assertEquals(mock_connect.call_count, 2)
|
||||||
|
# close() should only be called once, during retry()
|
||||||
|
mock_close.assert_called_once()
|
||||||
|
# send called 4 times: failed send("InfoDomainCommand"), passed send(logout),
|
||||||
|
# passed send(login), passed send("InfoDomainCommand")
|
||||||
|
self.assertEquals(mock_send.call_count, 4)
|
||||||
|
|
||||||
|
@patch("epplibwrapper.client.Client")
|
||||||
|
def test_send_command_failed_retries_and_fails_again(self, mock_client):
|
||||||
|
"""Test when the send("InfoDomainCommand) call fails with a 2400, prompting a retry
|
||||||
|
and the subsequent send("InfoDomainCommand) call also fails with a 2400, raise
|
||||||
|
a RegistryError
|
||||||
|
Flow:
|
||||||
|
Initialization succeeds
|
||||||
|
Send command fails (with 2400 code) prompting retry
|
||||||
|
Client closes and re-initializes, and command fails again with 2400"""
|
||||||
|
with less_console_noise():
|
||||||
|
# Mock the Client instance and its methods
|
||||||
|
# connect() and close() should succeed throughout
|
||||||
|
mock_connect = MagicMock()
|
||||||
|
mock_close = MagicMock()
|
||||||
|
# Create a mock Result instance
|
||||||
|
send_command_success_result = self.fake_result(1000, "Command completed successfully")
|
||||||
|
send_command_failure_result = self.fake_result(2400, "Command failed")
|
||||||
|
|
||||||
|
# side_effect for send command, passes for all other sends (login, logout), but
|
||||||
|
# fails for send("InfoDomainCommand")
|
||||||
|
def side_effect(*args, **kwargs):
|
||||||
|
if args[0] == "InfoDomainCommand":
|
||||||
|
return send_command_failure_result
|
||||||
|
else:
|
||||||
|
return send_command_success_result
|
||||||
|
|
||||||
|
mock_send = MagicMock(side_effect=side_effect)
|
||||||
|
mock_client.return_value.connect = mock_connect
|
||||||
|
mock_client.return_value.close = mock_close
|
||||||
|
mock_client.return_value.send = mock_send
|
||||||
|
|
||||||
|
with self.assertRaises(RegistryError):
|
||||||
|
# Create EPPLibWrapper instance and initialize client
|
||||||
|
wrapper = EPPLibWrapper()
|
||||||
|
# call send, which should throw a RegistryError (after retry)
|
||||||
|
wrapper.send("InfoDomainCommand", cleaned=True)
|
||||||
|
# connect() should be called twice, once during initialization, second time
|
||||||
|
# during retry
|
||||||
|
self.assertEquals(mock_connect.call_count, 2)
|
||||||
|
# close() is called once during retry
|
||||||
|
mock_close.assert_called_once()
|
||||||
|
# send() is called 5 times: send(login), send(command) fails, send(logout)
|
||||||
|
# send(login), send(command)
|
||||||
|
self.assertEquals(mock_send.call_count, 5)
|
||||||
|
|
||||||
|
@patch("epplibwrapper.client.Client")
|
||||||
|
def test_send_command_failure_prompts_successful_retry(self, mock_client):
|
||||||
|
"""Test when the send("InfoDomainCommand) call fails with a 2400, prompting a retry
|
||||||
|
and the subsequent send("InfoDomainCommand) call succeeds
|
||||||
|
Flow:
|
||||||
|
Initialization succeeds
|
||||||
|
Send command fails (with 2400 code) prompting retry
|
||||||
|
Client closes and re-initializes, and command succeeds"""
|
||||||
|
with less_console_noise():
|
||||||
|
# Mock the Client instance and its methods
|
||||||
|
# connect() and close() should succeed throughout
|
||||||
|
mock_connect = MagicMock()
|
||||||
|
mock_close = MagicMock()
|
||||||
|
# create success and failure result messages
|
||||||
|
send_command_success_result = self.fake_result(1000, "Command completed successfully")
|
||||||
|
send_command_failure_result = self.fake_result(2400, "Command failed")
|
||||||
|
# side_effect for send call, initial send(login) succeeds during initialization, next send(command)
|
||||||
|
# fails, subsequent sends (logout, login, command) all succeed
|
||||||
|
send_call_count = 0
|
||||||
|
|
||||||
|
def side_effect(*args, **kwargs):
|
||||||
|
nonlocal send_call_count
|
||||||
|
send_call_count += 1
|
||||||
|
if send_call_count == 2:
|
||||||
|
return send_command_failure_result
|
||||||
|
else:
|
||||||
|
return send_command_success_result
|
||||||
|
|
||||||
|
mock_send = MagicMock(side_effect=side_effect)
|
||||||
|
mock_client.return_value.connect = mock_connect
|
||||||
|
mock_client.return_value.close = mock_close
|
||||||
|
mock_client.return_value.send = mock_send
|
||||||
|
# Create EPPLibWrapper instance and initialize client
|
||||||
|
wrapper = EPPLibWrapper()
|
||||||
|
wrapper.send("InfoDomainCommand", cleaned=True)
|
||||||
|
# connect() is called twice, once during initialization of app, once during retry
|
||||||
|
self.assertEquals(mock_connect.call_count, 2)
|
||||||
|
# close() is called once, during retry
|
||||||
|
mock_close.assert_called_once()
|
||||||
|
# send() is called 5 times: send(login), send(command) fail, send(logout), send(login), send(command)
|
||||||
|
self.assertEquals(mock_send.call_count, 5)
|
|
@ -1,262 +0,0 @@
|
||||||
import datetime
|
|
||||||
from pathlib import Path
|
|
||||||
from unittest.mock import MagicMock, patch
|
|
||||||
from dateutil.tz import tzlocal # type: ignore
|
|
||||||
from django.test import TestCase
|
|
||||||
from epplibwrapper.client import EPPLibWrapper
|
|
||||||
from epplibwrapper.errors import RegistryError
|
|
||||||
from epplibwrapper.socket import Socket
|
|
||||||
from epplibwrapper.utility.pool import EPPConnectionPool
|
|
||||||
from registrar.models.domain import registry
|
|
||||||
from contextlib import ExitStack
|
|
||||||
from .common import less_console_noise
|
|
||||||
import logging
|
|
||||||
|
|
||||||
try:
|
|
||||||
from epplib import commands
|
|
||||||
from epplib.client import Client
|
|
||||||
from epplib.exceptions import TransportError
|
|
||||||
from epplib.transport import SocketTransport
|
|
||||||
from epplib.models import common, info
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class TestConnectionPool(TestCase):
|
|
||||||
"""Tests for our connection pooling behaviour"""
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
# Mimic the settings added to settings.py
|
|
||||||
self.pool_options = {
|
|
||||||
# Current pool size
|
|
||||||
"size": 1,
|
|
||||||
# Which errors the pool should look out for
|
|
||||||
"exc_classes": (TransportError,),
|
|
||||||
# Occasionally pings the registry to keep the connection alive.
|
|
||||||
# Value in seconds => (keepalive / size)
|
|
||||||
"keepalive": 60,
|
|
||||||
}
|
|
||||||
|
|
||||||
def fake_socket(self, login, client):
|
|
||||||
# Linter reasons
|
|
||||||
pw = "none"
|
|
||||||
# Create a fake client object
|
|
||||||
fake_client = Client(
|
|
||||||
SocketTransport(
|
|
||||||
"none",
|
|
||||||
cert_file="path/to/cert_file",
|
|
||||||
key_file="path/to/key_file",
|
|
||||||
password=pw,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return Socket(fake_client, MagicMock())
|
|
||||||
|
|
||||||
def patch_success(self):
|
|
||||||
return True
|
|
||||||
|
|
||||||
def fake_send(self, command, cleaned=None):
|
|
||||||
mock = MagicMock(
|
|
||||||
code=1000,
|
|
||||||
msg="Command completed successfully",
|
|
||||||
res_data=None,
|
|
||||||
cl_tr_id="xkw1uo#2023-10-17T15:29:09.559376",
|
|
||||||
sv_tr_id="5CcH4gxISuGkq8eqvr1UyQ==-35a",
|
|
||||||
extensions=[],
|
|
||||||
msg_q=None,
|
|
||||||
)
|
|
||||||
return mock
|
|
||||||
|
|
||||||
def fake_client(mock_client):
|
|
||||||
pw = "none"
|
|
||||||
client = Client(
|
|
||||||
SocketTransport(
|
|
||||||
"none",
|
|
||||||
cert_file="path/to/cert_file",
|
|
||||||
key_file="path/to/key_file",
|
|
||||||
password=pw,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return client
|
|
||||||
|
|
||||||
@patch.object(EPPLibWrapper, "_test_registry_connection_success", patch_success)
|
|
||||||
def test_pool_sends_data(self):
|
|
||||||
"""A .send is invoked on the pool successfully"""
|
|
||||||
expected_result = {
|
|
||||||
"cl_tr_id": None,
|
|
||||||
"code": 1000,
|
|
||||||
"extensions": [],
|
|
||||||
"msg": "Command completed successfully",
|
|
||||||
"msg_q": None,
|
|
||||||
"res_data": [
|
|
||||||
info.InfoDomainResultData(
|
|
||||||
roid="DF1340360-GOV",
|
|
||||||
statuses=[
|
|
||||||
common.Status(
|
|
||||||
state="serverTransferProhibited",
|
|
||||||
description=None,
|
|
||||||
lang="en",
|
|
||||||
),
|
|
||||||
common.Status(state="inactive", description=None, lang="en"),
|
|
||||||
],
|
|
||||||
cl_id="gov2023-ote",
|
|
||||||
cr_id="gov2023-ote",
|
|
||||||
cr_date=datetime.datetime(2023, 8, 15, 23, 56, 36, tzinfo=tzlocal()),
|
|
||||||
up_id="gov2023-ote",
|
|
||||||
up_date=datetime.datetime(2023, 8, 17, 2, 3, 19, tzinfo=tzlocal()),
|
|
||||||
tr_date=None,
|
|
||||||
name="test3.gov",
|
|
||||||
registrant="TuaWnx9hnm84GCSU",
|
|
||||||
admins=[],
|
|
||||||
nsset=None,
|
|
||||||
keyset=None,
|
|
||||||
ex_date=datetime.date(2024, 8, 15),
|
|
||||||
auth_info=info.DomainAuthInfo(pw="2fooBAR123fooBaz"),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
"sv_tr_id": "wRRNVhKhQW2m6wsUHbo/lA==-29a",
|
|
||||||
}
|
|
||||||
|
|
||||||
# Mock a response from EPP
|
|
||||||
def fake_receive(command, cleaned=None):
|
|
||||||
location = Path(__file__).parent / "utility" / "infoDomain.xml"
|
|
||||||
xml = (location).read_bytes()
|
|
||||||
return xml
|
|
||||||
|
|
||||||
def do_nothing(command):
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Mock what happens inside the "with"
|
|
||||||
with ExitStack() as stack:
|
|
||||||
stack.enter_context(patch.object(EPPConnectionPool, "_create_socket", self.fake_socket))
|
|
||||||
stack.enter_context(patch.object(Socket, "connect", self.fake_client))
|
|
||||||
stack.enter_context(patch.object(EPPConnectionPool, "kill_all_connections", do_nothing))
|
|
||||||
stack.enter_context(patch.object(SocketTransport, "send", self.fake_send))
|
|
||||||
stack.enter_context(patch.object(SocketTransport, "receive", fake_receive))
|
|
||||||
with less_console_noise():
|
|
||||||
# Restart the connection pool
|
|
||||||
registry.start_connection_pool()
|
|
||||||
# Pool should be running, and be the right size
|
|
||||||
self.assertEqual(registry.pool_status.connection_success, True)
|
|
||||||
self.assertEqual(registry.pool_status.pool_running, True)
|
|
||||||
|
|
||||||
# Send a command
|
|
||||||
result = registry.send(commands.InfoDomain(name="test.gov"), cleaned=True)
|
|
||||||
|
|
||||||
# Should this ever fail, it either means that the schema has changed,
|
|
||||||
# or the pool is broken.
|
|
||||||
# If the schema has changed: Update the associated infoDomain.xml file
|
|
||||||
self.assertEqual(result.__dict__, expected_result)
|
|
||||||
|
|
||||||
# The number of open pools should match the number of requested ones.
|
|
||||||
# If it is 0, then they failed to open
|
|
||||||
self.assertEqual(len(registry._pool.conn), self.pool_options["size"])
|
|
||||||
# Kill the connection pool
|
|
||||||
registry.kill_pool()
|
|
||||||
|
|
||||||
@patch.object(EPPLibWrapper, "_test_registry_connection_success", patch_success)
|
|
||||||
def test_pool_restarts_on_send(self):
|
|
||||||
"""A .send is invoked, but the pool isn't running.
|
|
||||||
The pool should restart."""
|
|
||||||
expected_result = {
|
|
||||||
"cl_tr_id": None,
|
|
||||||
"code": 1000,
|
|
||||||
"extensions": [],
|
|
||||||
"msg": "Command completed successfully",
|
|
||||||
"msg_q": None,
|
|
||||||
"res_data": [
|
|
||||||
info.InfoDomainResultData(
|
|
||||||
roid="DF1340360-GOV",
|
|
||||||
statuses=[
|
|
||||||
common.Status(
|
|
||||||
state="serverTransferProhibited",
|
|
||||||
description=None,
|
|
||||||
lang="en",
|
|
||||||
),
|
|
||||||
common.Status(state="inactive", description=None, lang="en"),
|
|
||||||
],
|
|
||||||
cl_id="gov2023-ote",
|
|
||||||
cr_id="gov2023-ote",
|
|
||||||
cr_date=datetime.datetime(2023, 8, 15, 23, 56, 36, tzinfo=tzlocal()),
|
|
||||||
up_id="gov2023-ote",
|
|
||||||
up_date=datetime.datetime(2023, 8, 17, 2, 3, 19, tzinfo=tzlocal()),
|
|
||||||
tr_date=None,
|
|
||||||
name="test3.gov",
|
|
||||||
registrant="TuaWnx9hnm84GCSU",
|
|
||||||
admins=[],
|
|
||||||
nsset=None,
|
|
||||||
keyset=None,
|
|
||||||
ex_date=datetime.date(2024, 8, 15),
|
|
||||||
auth_info=info.DomainAuthInfo(pw="2fooBAR123fooBaz"),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
"sv_tr_id": "wRRNVhKhQW2m6wsUHbo/lA==-29a",
|
|
||||||
}
|
|
||||||
|
|
||||||
# Mock a response from EPP
|
|
||||||
def fake_receive(command, cleaned=None):
|
|
||||||
location = Path(__file__).parent / "utility" / "infoDomain.xml"
|
|
||||||
xml = (location).read_bytes()
|
|
||||||
return xml
|
|
||||||
|
|
||||||
def do_nothing(command):
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Mock what happens inside the "with"
|
|
||||||
with ExitStack() as stack:
|
|
||||||
stack.enter_context(patch.object(EPPConnectionPool, "_create_socket", self.fake_socket))
|
|
||||||
stack.enter_context(patch.object(Socket, "connect", self.fake_client))
|
|
||||||
stack.enter_context(patch.object(EPPConnectionPool, "kill_all_connections", do_nothing))
|
|
||||||
stack.enter_context(patch.object(SocketTransport, "send", self.fake_send))
|
|
||||||
stack.enter_context(patch.object(SocketTransport, "receive", fake_receive))
|
|
||||||
with less_console_noise():
|
|
||||||
# Start the connection pool
|
|
||||||
registry.start_connection_pool()
|
|
||||||
# Kill the connection pool
|
|
||||||
registry.kill_pool()
|
|
||||||
|
|
||||||
self.assertEqual(registry.pool_status.pool_running, False)
|
|
||||||
|
|
||||||
# An exception should be raised as end user will be informed
|
|
||||||
# that they cannot connect to EPP
|
|
||||||
with self.assertRaises(RegistryError):
|
|
||||||
expected = "InfoDomain failed to execute due to a connection error."
|
|
||||||
result = registry.send(commands.InfoDomain(name="test.gov"), cleaned=True)
|
|
||||||
self.assertEqual(result, expected)
|
|
||||||
|
|
||||||
# A subsequent command should be successful, as the pool restarts
|
|
||||||
result = registry.send(commands.InfoDomain(name="test.gov"), cleaned=True)
|
|
||||||
# Should this ever fail, it either means that the schema has changed,
|
|
||||||
# or the pool is broken.
|
|
||||||
# If the schema has changed: Update the associated infoDomain.xml file
|
|
||||||
self.assertEqual(result.__dict__, expected_result)
|
|
||||||
|
|
||||||
# The number of open pools should match the number of requested ones.
|
|
||||||
# If it is 0, then they failed to open
|
|
||||||
self.assertEqual(len(registry._pool.conn), self.pool_options["size"])
|
|
||||||
# Kill the connection pool
|
|
||||||
registry.kill_pool()
|
|
||||||
|
|
||||||
@patch.object(EPPLibWrapper, "_test_registry_connection_success", patch_success)
|
|
||||||
def test_raises_connection_error(self):
|
|
||||||
"""A .send is invoked on the pool, but registry connection is lost
|
|
||||||
right as we send a command."""
|
|
||||||
|
|
||||||
with ExitStack() as stack:
|
|
||||||
stack.enter_context(patch.object(EPPConnectionPool, "_create_socket", self.fake_socket))
|
|
||||||
stack.enter_context(patch.object(Socket, "connect", self.fake_client))
|
|
||||||
with less_console_noise():
|
|
||||||
# Start the connection pool
|
|
||||||
registry.start_connection_pool()
|
|
||||||
|
|
||||||
# Pool should be running
|
|
||||||
self.assertEqual(registry.pool_status.connection_success, True)
|
|
||||||
self.assertEqual(registry.pool_status.pool_running, True)
|
|
||||||
|
|
||||||
# Try to send a command out - should fail
|
|
||||||
with self.assertRaises(RegistryError):
|
|
||||||
expected = "InfoDomain failed to execute due to a connection error."
|
|
||||||
result = registry.send(commands.InfoDomain(name="test.gov"), cleaned=True)
|
|
||||||
self.assertEqual(result, expected)
|
|
|
@ -1,151 +0,0 @@
|
||||||
import logging
|
|
||||||
from typing import List
|
|
||||||
import gevent
|
|
||||||
from geventconnpool import ConnectionPool
|
|
||||||
from epplibwrapper.socket import Socket
|
|
||||||
from epplibwrapper.utility.pool_error import PoolError, PoolErrorCodes
|
|
||||||
|
|
||||||
try:
|
|
||||||
from epplib.commands import Hello
|
|
||||||
from epplib.exceptions import TransportError
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
from gevent.lock import BoundedSemaphore
|
|
||||||
from collections import deque
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class EPPConnectionPool(ConnectionPool):
|
|
||||||
"""A connection pool for EPPLib.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
client (Client): The client
|
|
||||||
login (commands.Login): Login creds
|
|
||||||
options (dict): Options for the ConnectionPool
|
|
||||||
base class
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, client, login, options: dict):
|
|
||||||
# For storing shared credentials
|
|
||||||
self._client = client
|
|
||||||
self._login = login
|
|
||||||
|
|
||||||
# Keep track of each greenlet
|
|
||||||
self.greenlets: List[gevent.Greenlet] = []
|
|
||||||
|
|
||||||
# Define optional pool settings.
|
|
||||||
# Kept in a dict so that the parent class,
|
|
||||||
# client.py, can maintain seperation/expandability
|
|
||||||
self.size = 1
|
|
||||||
if "size" in options:
|
|
||||||
self.size = options["size"]
|
|
||||||
|
|
||||||
self.exc_classes = tuple((TransportError,))
|
|
||||||
if "exc_classes" in options:
|
|
||||||
self.exc_classes = options["exc_classes"]
|
|
||||||
|
|
||||||
self.keepalive = None
|
|
||||||
if "keepalive" in options:
|
|
||||||
self.keepalive = options["keepalive"]
|
|
||||||
|
|
||||||
# Determines the period in which new
|
|
||||||
# gevent threads are spun up.
|
|
||||||
# This time period is in seconds. So for instance, .1 would be .1 seconds.
|
|
||||||
self.spawn_frequency = 0.1
|
|
||||||
if "spawn_frequency" in options:
|
|
||||||
self.spawn_frequency = options["spawn_frequency"]
|
|
||||||
|
|
||||||
self.conn: deque = deque()
|
|
||||||
self.lock = BoundedSemaphore(self.size)
|
|
||||||
|
|
||||||
self.populate_all_connections()
|
|
||||||
|
|
||||||
def _new_connection(self):
|
|
||||||
socket = self._create_socket(self._client, self._login)
|
|
||||||
try:
|
|
||||||
connection = socket.connect()
|
|
||||||
return connection
|
|
||||||
except Exception as err:
|
|
||||||
message = f"Failed to execute due to a registry error: {err}"
|
|
||||||
logger.error(message, exc_info=True)
|
|
||||||
# We want to raise a pool error rather than a LoginError here
|
|
||||||
# because if this occurs internally, we should handle this
|
|
||||||
# differently than we otherwise would for LoginError.
|
|
||||||
raise PoolError(code=PoolErrorCodes.NEW_CONNECTION_FAILED) from err
|
|
||||||
|
|
||||||
def _keepalive(self, c):
|
|
||||||
"""Sends a command to the server to keep the connection alive."""
|
|
||||||
try:
|
|
||||||
# Sends a ping to the registry via EPPLib
|
|
||||||
c.send(Hello())
|
|
||||||
except Exception as err:
|
|
||||||
message = "Failed to keep the connection alive."
|
|
||||||
logger.error(message, exc_info=True)
|
|
||||||
raise PoolError(code=PoolErrorCodes.KEEP_ALIVE_FAILED) from err
|
|
||||||
|
|
||||||
def _keepalive_periodic(self):
|
|
||||||
"""Overriding _keepalive_periodic from geventconnpool so that PoolErrors
|
|
||||||
are properly handled, as opposed to printing to stdout"""
|
|
||||||
delay = float(self.keepalive) / self.size
|
|
||||||
while 1:
|
|
||||||
try:
|
|
||||||
with self.get() as c:
|
|
||||||
self._keepalive(c)
|
|
||||||
except PoolError as err:
|
|
||||||
logger.error(err.message, exc_info=True)
|
|
||||||
except self.exc_classes:
|
|
||||||
# Nothing to do, the pool will generate a new connection later
|
|
||||||
pass
|
|
||||||
gevent.sleep(delay)
|
|
||||||
|
|
||||||
def _create_socket(self, client, login) -> Socket:
|
|
||||||
"""Creates and returns a socket instance"""
|
|
||||||
socket = Socket(client, login)
|
|
||||||
return socket
|
|
||||||
|
|
||||||
def get_connections(self):
|
|
||||||
"""Returns the connection queue"""
|
|
||||||
return self.conn
|
|
||||||
|
|
||||||
def kill_all_connections(self):
|
|
||||||
"""Kills all active connections in the pool."""
|
|
||||||
try:
|
|
||||||
if len(self.conn) > 0 or len(self.greenlets) > 0:
|
|
||||||
logger.info("Attempting to kill connections")
|
|
||||||
gevent.killall(self.greenlets)
|
|
||||||
|
|
||||||
self.greenlets.clear()
|
|
||||||
for connection in self.conn:
|
|
||||||
connection.disconnect()
|
|
||||||
self.conn.clear()
|
|
||||||
|
|
||||||
# Clear the semaphore
|
|
||||||
self.lock = BoundedSemaphore(self.size)
|
|
||||||
logger.info("Finished killing connections")
|
|
||||||
else:
|
|
||||||
logger.info("No connections to kill.")
|
|
||||||
except Exception as err:
|
|
||||||
logger.error("Could not kill all connections.")
|
|
||||||
raise PoolError(code=PoolErrorCodes.KILL_ALL_FAILED) from err
|
|
||||||
|
|
||||||
def populate_all_connections(self):
|
|
||||||
"""Generates the connection pool.
|
|
||||||
If any connections exist, kill them first.
|
|
||||||
Based off of the __init__ definition for geventconnpool.
|
|
||||||
"""
|
|
||||||
if len(self.conn) > 0 or len(self.greenlets) > 0:
|
|
||||||
self.kill_all_connections()
|
|
||||||
|
|
||||||
# Setup the lock
|
|
||||||
for i in range(self.size):
|
|
||||||
self.lock.acquire()
|
|
||||||
|
|
||||||
# Open multiple connections
|
|
||||||
for i in range(self.size):
|
|
||||||
self.greenlets.append(gevent.spawn_later(self.spawn_frequency * i, self._addOne))
|
|
||||||
|
|
||||||
# Open a "keepalive" thread if we want to ping open connections
|
|
||||||
if self.keepalive:
|
|
||||||
self.greenlets.append(gevent.spawn(self._keepalive_periodic))
|
|
|
@ -1,46 +0,0 @@
|
||||||
from enum import IntEnum
|
|
||||||
|
|
||||||
|
|
||||||
class PoolErrorCodes(IntEnum):
|
|
||||||
"""Used in the PoolError class for
|
|
||||||
error mapping.
|
|
||||||
|
|
||||||
Overview of contact error codes:
|
|
||||||
- 2000 KILL_ALL_FAILED
|
|
||||||
- 2001 NEW_CONNECTION_FAILED
|
|
||||||
- 2002 KEEP_ALIVE_FAILED
|
|
||||||
"""
|
|
||||||
|
|
||||||
KILL_ALL_FAILED = 2000
|
|
||||||
NEW_CONNECTION_FAILED = 2001
|
|
||||||
KEEP_ALIVE_FAILED = 2002
|
|
||||||
|
|
||||||
|
|
||||||
class PoolError(Exception):
|
|
||||||
"""
|
|
||||||
Overview of contact error codes:
|
|
||||||
- 2000 KILL_ALL_FAILED
|
|
||||||
- 2001 NEW_CONNECTION_FAILED
|
|
||||||
- 2002 KEEP_ALIVE_FAILED
|
|
||||||
|
|
||||||
Note: These are separate from the error codes returned from EppLib
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Used variables due to linter requirements
|
|
||||||
kill_failed = "Could not kill all connections. Are multiple pools running?"
|
|
||||||
conn_failed = "Failed to execute due to a registry error. 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 = {
|
|
||||||
PoolErrorCodes.KILL_ALL_FAILED: kill_failed,
|
|
||||||
PoolErrorCodes.NEW_CONNECTION_FAILED: conn_failed,
|
|
||||||
PoolErrorCodes.KEEP_ALIVE_FAILED: alive_failed,
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, *args, code=None, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
self.code = code
|
|
||||||
if self.code in self._error_mapping:
|
|
||||||
self.message = self._error_mapping.get(self.code)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"{self.message}"
|
|
|
@ -1,12 +0,0 @@
|
||||||
class PoolStatus:
|
|
||||||
"""A list of Booleans to keep track of Pool Status.
|
|
||||||
|
|
||||||
pool_running -> bool: Tracks if the pool itself is active or not.
|
|
||||||
connection_success -> bool: Tracks if connection is possible with the registry.
|
|
||||||
pool_hanging -> pool: Tracks if the pool has exceeded its timeout period.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.pool_running = False
|
|
||||||
self.connection_success = False
|
|
||||||
self.pool_hanging = False
|
|
|
@ -14,7 +14,7 @@ from django.contrib.contenttypes.models import ContentType
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from dateutil.relativedelta import relativedelta # type: ignore
|
from dateutil.relativedelta import relativedelta # type: ignore
|
||||||
from epplibwrapper.errors import ErrorCode, RegistryError
|
from epplibwrapper.errors import ErrorCode, RegistryError
|
||||||
from registrar.models import Contact, Domain, DomainApplication, DraftDomain, User, Website
|
from registrar.models import Contact, Domain, DomainRequest, DraftDomain, User, Website
|
||||||
from registrar.utility import csv_export
|
from registrar.utility import csv_export
|
||||||
from registrar.views.utility.mixins import OrderableFieldsMixin
|
from registrar.views.utility.mixins import OrderableFieldsMixin
|
||||||
from django.contrib.admin.views.main import ORDER_VAR
|
from django.contrib.admin.views.main import ORDER_VAR
|
||||||
|
@ -69,12 +69,12 @@ class DomainInformationInlineForm(forms.ModelForm):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class DomainApplicationAdminForm(forms.ModelForm):
|
class DomainRequestAdminForm(forms.ModelForm):
|
||||||
"""Custom form to limit transitions to available transitions.
|
"""Custom form to limit transitions to available transitions.
|
||||||
This form utilizes the custom widget for its class's ManyToMany UIs."""
|
This form utilizes the custom widget for its class's ManyToMany UIs."""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.DomainApplication
|
model = models.DomainRequest
|
||||||
fields = "__all__"
|
fields = "__all__"
|
||||||
widgets = {
|
widgets = {
|
||||||
"current_websites": NoAutocompleteFilteredSelectMultiple("current_websites", False),
|
"current_websites": NoAutocompleteFilteredSelectMultiple("current_websites", False),
|
||||||
|
@ -85,24 +85,24 @@ class DomainApplicationAdminForm(forms.ModelForm):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
application = kwargs.get("instance")
|
domain_request = kwargs.get("instance")
|
||||||
if application and application.pk:
|
if domain_request and domain_request.pk:
|
||||||
current_state = application.status
|
current_state = domain_request.status
|
||||||
|
|
||||||
# first option in status transitions is current state
|
# first option in status transitions is current state
|
||||||
available_transitions = [(current_state, application.get_status_display())]
|
available_transitions = [(current_state, domain_request.get_status_display())]
|
||||||
|
|
||||||
transitions = get_available_FIELD_transitions(
|
transitions = get_available_FIELD_transitions(
|
||||||
application, models.DomainApplication._meta.get_field("status")
|
domain_request, models.DomainRequest._meta.get_field("status")
|
||||||
)
|
)
|
||||||
|
|
||||||
for transition in transitions:
|
for transition in transitions:
|
||||||
available_transitions.append((transition.target, transition.target.label))
|
available_transitions.append((transition.target, transition.target.label))
|
||||||
|
|
||||||
# only set the available transitions if the user is not restricted
|
# only set the available transitions if the user is not restricted
|
||||||
# from editing the domain application; otherwise, the form will be
|
# from editing the domain request; otherwise, the form will be
|
||||||
# readonly and the status field will not have a widget
|
# readonly and the status field will not have a widget
|
||||||
if not application.creator.is_restricted():
|
if not domain_request.creator.is_restricted():
|
||||||
self.fields["status"].widget.choices = available_transitions
|
self.fields["status"].widget.choices = available_transitions
|
||||||
|
|
||||||
|
|
||||||
|
@ -218,8 +218,8 @@ class AdminSortFields:
|
||||||
"alternative_domains": (Website, "website"),
|
"alternative_domains": (Website, "website"),
|
||||||
# == DraftDomain == #
|
# == DraftDomain == #
|
||||||
"requested_domain": (DraftDomain, "name"),
|
"requested_domain": (DraftDomain, "name"),
|
||||||
# == DomainApplication == #
|
# == DomainRequest == #
|
||||||
"domain_application": (DomainApplication, "requested_domain__name"),
|
"domain_request": (DomainRequest, "requested_domain__name"),
|
||||||
# == Domain == #
|
# == Domain == #
|
||||||
"domain": (Domain, "name"),
|
"domain": (Domain, "name"),
|
||||||
"approved_domain": (Domain, "name"),
|
"approved_domain": (Domain, "name"),
|
||||||
|
@ -466,7 +466,7 @@ class MyUserAdmin(BaseUserAdmin):
|
||||||
def get_search_results(self, request, queryset, search_term):
|
def get_search_results(self, request, queryset, search_term):
|
||||||
"""
|
"""
|
||||||
Override for get_search_results. This affects any upstream model using autocomplete_fields,
|
Override for get_search_results. This affects any upstream model using autocomplete_fields,
|
||||||
such as DomainApplication. This is because autocomplete_fields uses an API call to fetch data,
|
such as DomainRequest. This is because autocomplete_fields uses an API call to fetch data,
|
||||||
and this fetch comes from this method.
|
and this fetch comes from this method.
|
||||||
"""
|
"""
|
||||||
# Custom filtering logic
|
# Custom filtering logic
|
||||||
|
@ -480,13 +480,13 @@ class MyUserAdmin(BaseUserAdmin):
|
||||||
request_get = request.GET
|
request_get = request.GET
|
||||||
|
|
||||||
# The request defines model name and field name.
|
# The request defines model name and field name.
|
||||||
# For instance, model_name could be "DomainApplication"
|
# For instance, model_name could be "DomainRequest"
|
||||||
# and field_name could be "investigator".
|
# and field_name could be "investigator".
|
||||||
model_name = request_get.get("model_name", None)
|
model_name = request_get.get("model_name", None)
|
||||||
field_name = request_get.get("field_name", None)
|
field_name = request_get.get("field_name", None)
|
||||||
|
|
||||||
# Make sure we're only modifying requests from these models.
|
# Make sure we're only modifying requests from these models.
|
||||||
models_to_target = {"domainapplication"}
|
models_to_target = {"domainrequest"}
|
||||||
if model_name in models_to_target:
|
if model_name in models_to_target:
|
||||||
# Define rules per field
|
# Define rules per field
|
||||||
match field_name:
|
match field_name:
|
||||||
|
@ -777,7 +777,7 @@ class DomainInformationAdmin(ListHeaderAdmin):
|
||||||
search_help_text = "Search by domain."
|
search_help_text = "Search by domain."
|
||||||
|
|
||||||
fieldsets = [
|
fieldsets = [
|
||||||
(None, {"fields": ["creator", "domain_application", "notes"]}),
|
(None, {"fields": ["creator", "domain_request", "notes"]}),
|
||||||
(
|
(
|
||||||
"Type of organization",
|
"Type of organization",
|
||||||
{
|
{
|
||||||
|
@ -828,7 +828,7 @@ class DomainInformationAdmin(ListHeaderAdmin):
|
||||||
"type_of_work",
|
"type_of_work",
|
||||||
"more_organization_information",
|
"more_organization_information",
|
||||||
"domain",
|
"domain",
|
||||||
"domain_application",
|
"domain_request",
|
||||||
"submitter",
|
"submitter",
|
||||||
"no_other_contacts_rationale",
|
"no_other_contacts_rationale",
|
||||||
"anything_else",
|
"anything_else",
|
||||||
|
@ -841,7 +841,7 @@ class DomainInformationAdmin(ListHeaderAdmin):
|
||||||
|
|
||||||
autocomplete_fields = [
|
autocomplete_fields = [
|
||||||
"creator",
|
"creator",
|
||||||
"domain_application",
|
"domain_request",
|
||||||
"authorizing_official",
|
"authorizing_official",
|
||||||
"domain",
|
"domain",
|
||||||
"submitter",
|
"submitter",
|
||||||
|
@ -866,10 +866,10 @@ class DomainInformationAdmin(ListHeaderAdmin):
|
||||||
return readonly_fields # Read-only fields for analysts
|
return readonly_fields # Read-only fields for analysts
|
||||||
|
|
||||||
|
|
||||||
class DomainApplicationAdmin(ListHeaderAdmin):
|
class DomainRequestAdmin(ListHeaderAdmin):
|
||||||
"""Custom domain applications admin class."""
|
"""Custom domain requests admin class."""
|
||||||
|
|
||||||
form = DomainApplicationAdminForm
|
form = DomainRequestAdminForm
|
||||||
|
|
||||||
class InvestigatorFilter(admin.SimpleListFilter):
|
class InvestigatorFilter(admin.SimpleListFilter):
|
||||||
"""Custom investigator filter that only displays users with the manager role"""
|
"""Custom investigator filter that only displays users with the manager role"""
|
||||||
|
@ -884,7 +884,7 @@ class DomainApplicationAdmin(ListHeaderAdmin):
|
||||||
"""
|
"""
|
||||||
# Select all investigators that are staff, then order by name and email
|
# Select all investigators that are staff, then order by name and email
|
||||||
privileged_users = (
|
privileged_users = (
|
||||||
DomainApplication.objects.select_related("investigator")
|
DomainRequest.objects.select_related("investigator")
|
||||||
.filter(investigator__is_staff=True)
|
.filter(investigator__is_staff=True)
|
||||||
.order_by("investigator__first_name", "investigator__last_name", "investigator__email")
|
.order_by("investigator__first_name", "investigator__last_name", "investigator__email")
|
||||||
)
|
)
|
||||||
|
@ -1057,17 +1057,17 @@ class DomainApplicationAdmin(ListHeaderAdmin):
|
||||||
# Trigger action when a fieldset is changed
|
# Trigger action when a fieldset is changed
|
||||||
def save_model(self, request, obj, form, change):
|
def save_model(self, request, obj, form, change):
|
||||||
if obj and obj.creator.status != models.User.RESTRICTED:
|
if obj and obj.creator.status != models.User.RESTRICTED:
|
||||||
if change: # Check if the application is being edited
|
if change: # Check if the domain request is being edited
|
||||||
# Get the original application from the database
|
# Get the original domain request from the database
|
||||||
original_obj = models.DomainApplication.objects.get(pk=obj.pk)
|
original_obj = models.DomainRequest.objects.get(pk=obj.pk)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
obj
|
obj
|
||||||
and original_obj.status == models.DomainApplication.ApplicationStatus.APPROVED
|
and original_obj.status == models.DomainRequest.DomainRequestStatus.APPROVED
|
||||||
and obj.status != models.DomainApplication.ApplicationStatus.APPROVED
|
and obj.status != models.DomainRequest.DomainRequestStatus.APPROVED
|
||||||
and not obj.domain_is_not_active()
|
and not obj.domain_is_not_active()
|
||||||
):
|
):
|
||||||
# If an admin tried to set an approved application to
|
# If an admin tried to set an approved domain request to
|
||||||
# another status and the related domain is already
|
# another status and the related domain is already
|
||||||
# active, shortcut the action and throw a friendly
|
# active, shortcut the action and throw a friendly
|
||||||
# error message. This action would still not go through
|
# error message. This action would still not go through
|
||||||
|
@ -1083,9 +1083,7 @@ class DomainApplicationAdmin(ListHeaderAdmin):
|
||||||
)
|
)
|
||||||
|
|
||||||
elif (
|
elif (
|
||||||
obj
|
obj and obj.status == models.DomainRequest.DomainRequestStatus.REJECTED and not obj.rejection_reason
|
||||||
and obj.status == models.DomainApplication.ApplicationStatus.REJECTED
|
|
||||||
and not obj.rejection_reason
|
|
||||||
):
|
):
|
||||||
# This condition should never be triggered.
|
# This condition should never be triggered.
|
||||||
# The opposite of this condition is acceptable (rejected -> other status and rejection_reason)
|
# The opposite of this condition is acceptable (rejected -> other status and rejection_reason)
|
||||||
|
@ -1102,14 +1100,14 @@ class DomainApplicationAdmin(ListHeaderAdmin):
|
||||||
else:
|
else:
|
||||||
if obj.status != original_obj.status:
|
if obj.status != original_obj.status:
|
||||||
status_method_mapping = {
|
status_method_mapping = {
|
||||||
models.DomainApplication.ApplicationStatus.STARTED: None,
|
models.DomainRequest.DomainRequestStatus.STARTED: None,
|
||||||
models.DomainApplication.ApplicationStatus.SUBMITTED: obj.submit,
|
models.DomainRequest.DomainRequestStatus.SUBMITTED: obj.submit,
|
||||||
models.DomainApplication.ApplicationStatus.IN_REVIEW: obj.in_review,
|
models.DomainRequest.DomainRequestStatus.IN_REVIEW: obj.in_review,
|
||||||
models.DomainApplication.ApplicationStatus.ACTION_NEEDED: obj.action_needed,
|
models.DomainRequest.DomainRequestStatus.ACTION_NEEDED: obj.action_needed,
|
||||||
models.DomainApplication.ApplicationStatus.APPROVED: obj.approve,
|
models.DomainRequest.DomainRequestStatus.APPROVED: obj.approve,
|
||||||
models.DomainApplication.ApplicationStatus.WITHDRAWN: obj.withdraw,
|
models.DomainRequest.DomainRequestStatus.WITHDRAWN: obj.withdraw,
|
||||||
models.DomainApplication.ApplicationStatus.REJECTED: obj.reject,
|
models.DomainRequest.DomainRequestStatus.REJECTED: obj.reject,
|
||||||
models.DomainApplication.ApplicationStatus.INELIGIBLE: (obj.reject_with_prejudice),
|
models.DomainRequest.DomainRequestStatus.INELIGIBLE: (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:
|
||||||
|
@ -1129,13 +1127,13 @@ class DomainApplicationAdmin(ListHeaderAdmin):
|
||||||
|
|
||||||
messages.error(
|
messages.error(
|
||||||
request,
|
request,
|
||||||
"This action is not permitted for applications with a restricted creator.",
|
"This action is not permitted for domain requests with a restricted creator.",
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_readonly_fields(self, request, obj=None):
|
def get_readonly_fields(self, request, obj=None):
|
||||||
"""Set the read-only state on form elements.
|
"""Set the read-only state on form elements.
|
||||||
We have 2 conditions that determine which fields are read-only:
|
We have 2 conditions that determine which fields are read-only:
|
||||||
admin user permissions and the application creator's status, so
|
admin user permissions and the domain request creator's status, so
|
||||||
we'll use the baseline readonly_fields and extend it as needed.
|
we'll use the baseline readonly_fields and extend it as needed.
|
||||||
"""
|
"""
|
||||||
readonly_fields = list(self.readonly_fields)
|
readonly_fields = list(self.readonly_fields)
|
||||||
|
@ -1160,7 +1158,7 @@ class DomainApplicationAdmin(ListHeaderAdmin):
|
||||||
if obj and obj.creator.status == models.User.RESTRICTED:
|
if obj and obj.creator.status == models.User.RESTRICTED:
|
||||||
messages.warning(
|
messages.warning(
|
||||||
request,
|
request,
|
||||||
"Cannot edit an application with a restricted creator.",
|
"Cannot edit a domain request with a restricted creator.",
|
||||||
)
|
)
|
||||||
|
|
||||||
def change_view(self, request, object_id, form_url="", extra_context=None):
|
def change_view(self, request, object_id, form_url="", extra_context=None):
|
||||||
|
@ -1204,7 +1202,7 @@ class DomainInformationInline(admin.StackedInline):
|
||||||
|
|
||||||
autocomplete_fields = [
|
autocomplete_fields = [
|
||||||
"creator",
|
"creator",
|
||||||
"domain_application",
|
"domain_request",
|
||||||
"authorizing_official",
|
"authorizing_official",
|
||||||
"domain",
|
"domain",
|
||||||
"submitter",
|
"submitter",
|
||||||
|
@ -1728,6 +1726,6 @@ admin.site.register(models.DraftDomain, DraftDomainAdmin)
|
||||||
admin.site.register(models.Host, MyHostAdmin)
|
admin.site.register(models.Host, MyHostAdmin)
|
||||||
admin.site.register(models.Website, WebsiteAdmin)
|
admin.site.register(models.Website, WebsiteAdmin)
|
||||||
admin.site.register(models.PublicContact, AuditedAdmin)
|
admin.site.register(models.PublicContact, AuditedAdmin)
|
||||||
admin.site.register(models.DomainApplication, DomainApplicationAdmin)
|
admin.site.register(models.DomainRequest, DomainRequestAdmin)
|
||||||
admin.site.register(models.TransitionDomain, TransitionDomainAdmin)
|
admin.site.register(models.TransitionDomain, TransitionDomainAdmin)
|
||||||
admin.site.register(models.VerifiedByStaff, VerifiedByStaffAdmin)
|
admin.site.register(models.VerifiedByStaff, VerifiedByStaffAdmin)
|
||||||
|
|
|
@ -135,6 +135,8 @@ html[data-theme="dark"] {
|
||||||
color: var(--primary-fg);
|
color: var(--primary-fg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#branding h1,
|
#branding h1,
|
||||||
h1, h2, h3,
|
h1, h2, h3,
|
||||||
.module h2 {
|
.module h2 {
|
||||||
|
|
|
@ -188,15 +188,12 @@ WSGI_APPLICATION = "registrar.config.wsgi.application"
|
||||||
# https://docs.djangoproject.com/en/4.0/howto/static-files/
|
# https://docs.djangoproject.com/en/4.0/howto/static-files/
|
||||||
|
|
||||||
|
|
||||||
# Caching is disabled by default.
|
CACHES = {
|
||||||
# For a low to medium traffic site, caching causes more
|
"default": {
|
||||||
# problems than it solves. Should caching be desired,
|
"BACKEND": "django.core.cache.backends.db.DatabaseCache",
|
||||||
# a reasonable start might be:
|
"LOCATION": "cache_table",
|
||||||
# CACHES = {
|
}
|
||||||
# "default": {
|
}
|
||||||
# "BACKEND": "django.core.cache.backends.db.DatabaseCache",
|
|
||||||
# }
|
|
||||||
# }
|
|
||||||
|
|
||||||
# Absolute path to the directory where `collectstatic`
|
# Absolute path to the directory where `collectstatic`
|
||||||
# will place static files for deployment.
|
# will place static files for deployment.
|
||||||
|
@ -601,20 +598,6 @@ SECRET_REGISTRY_KEY = secret_registry_key
|
||||||
SECRET_REGISTRY_KEY_PASSPHRASE = secret_registry_key_passphrase
|
SECRET_REGISTRY_KEY_PASSPHRASE = secret_registry_key_passphrase
|
||||||
SECRET_REGISTRY_HOSTNAME = secret_registry_hostname
|
SECRET_REGISTRY_HOSTNAME = secret_registry_hostname
|
||||||
|
|
||||||
# Use this variable to set the size of our connection pool in client.py
|
|
||||||
# WARNING: Setting this value too high could cause frequent app crashes!
|
|
||||||
# Having too many connections open could cause the sandbox to timeout,
|
|
||||||
# as the spinup time could exceed the timeout time.
|
|
||||||
EPP_CONNECTION_POOL_SIZE = 1
|
|
||||||
|
|
||||||
# Determines the interval in which we ping open connections in seconds
|
|
||||||
# Calculated as POOL_KEEP_ALIVE / EPP_CONNECTION_POOL_SIZE
|
|
||||||
POOL_KEEP_ALIVE = 60
|
|
||||||
|
|
||||||
# Determines how long we try to keep a pool alive for,
|
|
||||||
# before restarting it.
|
|
||||||
POOL_TIMEOUT = 60
|
|
||||||
|
|
||||||
# endregion
|
# endregion
|
||||||
# region: Security and Privacy----------------------------------------------###
|
# region: Security and Privacy----------------------------------------------###
|
||||||
|
|
||||||
|
|
|
@ -13,18 +13,18 @@ from registrar import views
|
||||||
from registrar.views.admin_views import ExportData
|
from registrar.views.admin_views import ExportData
|
||||||
|
|
||||||
|
|
||||||
from registrar.views.application import Step
|
from registrar.views.domain_request import Step
|
||||||
from registrar.views.utility import always_404
|
from registrar.views.utility import always_404
|
||||||
from api.views import available, get_current_federal, get_current_full
|
from api.views import available, get_current_federal, get_current_full
|
||||||
|
|
||||||
|
|
||||||
APPLICATION_NAMESPACE = views.ApplicationWizard.URL_NAMESPACE
|
DOMAIN_REQUEST_NAMESPACE = views.DomainRequestWizard.URL_NAMESPACE
|
||||||
application_urls = [
|
domain_request_urls = [
|
||||||
path("", views.ApplicationWizard.as_view(), name=""),
|
path("", views.DomainRequestWizard.as_view(), name=""),
|
||||||
path("finished/", views.Finished.as_view(), name="finished"),
|
path("finished/", views.Finished.as_view(), name="finished"),
|
||||||
]
|
]
|
||||||
|
|
||||||
# dynamically generate the other application_urls
|
# dynamically generate the other domain_request_urls
|
||||||
for step, view in [
|
for step, view in [
|
||||||
# add/remove steps here
|
# add/remove steps here
|
||||||
(Step.ORGANIZATION_TYPE, views.OrganizationType),
|
(Step.ORGANIZATION_TYPE, views.OrganizationType),
|
||||||
|
@ -43,7 +43,7 @@ for step, view in [
|
||||||
(Step.REQUIREMENTS, views.Requirements),
|
(Step.REQUIREMENTS, views.Requirements),
|
||||||
(Step.REVIEW, views.Review),
|
(Step.REVIEW, views.Review),
|
||||||
]:
|
]:
|
||||||
application_urls.append(path(f"{step}/", view.as_view(), name=step))
|
domain_request_urls.append(path(f"{step}/", view.as_view(), name=step))
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
@ -55,28 +55,28 @@ urlpatterns = [
|
||||||
path("export_data/", ExportData.as_view(), name="admin_export_data"),
|
path("export_data/", ExportData.as_view(), name="admin_export_data"),
|
||||||
path("admin/", admin.site.urls),
|
path("admin/", admin.site.urls),
|
||||||
path(
|
path(
|
||||||
"application/<id>/edit/",
|
"domain-request/<id>/edit/",
|
||||||
views.ApplicationWizard.as_view(),
|
views.DomainRequestWizard.as_view(),
|
||||||
name=views.ApplicationWizard.EDIT_URL_NAME,
|
name=views.DomainRequestWizard.EDIT_URL_NAME,
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"application/<int:pk>",
|
"domain-request/<int:pk>",
|
||||||
views.ApplicationStatus.as_view(),
|
views.DomainRequestStatus.as_view(),
|
||||||
name="application-status",
|
name="domain-request-status",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"application/<int:pk>/withdraw",
|
"domain-request/<int:pk>/withdraw",
|
||||||
views.ApplicationWithdrawConfirmation.as_view(),
|
views.DomainRequestWithdrawConfirmation.as_view(),
|
||||||
name="application-withdraw-confirmation",
|
name="domain-request-withdraw-confirmation",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"application/<int:pk>/withdrawconfirmed",
|
"domain-request/<int:pk>/withdrawconfirmed",
|
||||||
views.ApplicationWithdrawn.as_view(),
|
views.DomainRequestWithdrawn.as_view(),
|
||||||
name="application-withdrawn",
|
name="domain-request-withdrawn",
|
||||||
),
|
),
|
||||||
path("health", views.health, name="health"),
|
path("health", views.health, name="health"),
|
||||||
path("openid/", include("djangooidc.urls")),
|
path("openid/", include("djangooidc.urls")),
|
||||||
path("request/", include((application_urls, APPLICATION_NAMESPACE))),
|
path("request/", include((domain_request_urls, DOMAIN_REQUEST_NAMESPACE))),
|
||||||
path("api/v1/available/", available, name="available"),
|
path("api/v1/available/", available, name="available"),
|
||||||
path("api/v1/get-report/current-federal", get_current_federal, name="get-current-federal"),
|
path("api/v1/get-report/current-federal", get_current_federal, name="get-current-federal"),
|
||||||
path("api/v1/get-report/current-full", get_current_full, name="get-current-full"),
|
path("api/v1/get-report/current-full", get_current_full, name="get-current-full"),
|
||||||
|
@ -138,9 +138,9 @@ urlpatterns = [
|
||||||
name="invitation-delete",
|
name="invitation-delete",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"application/<int:pk>/delete",
|
"domain-request/<int:pk>/delete",
|
||||||
views.DomainApplicationDeleteView.as_view(http_method_names=["post"]),
|
views.DomainRequestDeleteView.as_view(http_method_names=["post"]),
|
||||||
name="application-delete",
|
name="domain-request-delete",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
"domain/<int:pk>/users/<int:user_pk>/delete",
|
"domain/<int:pk>/users/<int:user_pk>/delete",
|
||||||
|
|
|
@ -4,7 +4,7 @@ from faker import Faker
|
||||||
|
|
||||||
from registrar.models import (
|
from registrar.models import (
|
||||||
User,
|
User,
|
||||||
DomainApplication,
|
DomainRequest,
|
||||||
DraftDomain,
|
DraftDomain,
|
||||||
Contact,
|
Contact,
|
||||||
Website,
|
Website,
|
||||||
|
@ -14,9 +14,9 @@ fake = Faker()
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class DomainApplicationFixture:
|
class DomainRequestFixture:
|
||||||
"""
|
"""
|
||||||
Load domain applications into the database.
|
Load domain requests into the database.
|
||||||
|
|
||||||
Make sure this class' `load` method is called from `handle`
|
Make sure this class' `load` method is called from `handle`
|
||||||
in management/commands/load.py, then use `./manage.py load`
|
in management/commands/load.py, then use `./manage.py load`
|
||||||
|
@ -49,27 +49,27 @@ class DomainApplicationFixture:
|
||||||
# },
|
# },
|
||||||
DA = [
|
DA = [
|
||||||
{
|
{
|
||||||
"status": DomainApplication.ApplicationStatus.STARTED,
|
"status": DomainRequest.DomainRequestStatus.STARTED,
|
||||||
"organization_name": "Example - Finished but not submitted",
|
"organization_name": "Example - Finished but not submitted",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"status": DomainApplication.ApplicationStatus.SUBMITTED,
|
"status": DomainRequest.DomainRequestStatus.SUBMITTED,
|
||||||
"organization_name": "Example - Submitted but pending investigation",
|
"organization_name": "Example - Submitted but pending investigation",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"status": DomainApplication.ApplicationStatus.IN_REVIEW,
|
"status": DomainRequest.DomainRequestStatus.IN_REVIEW,
|
||||||
"organization_name": "Example - In investigation",
|
"organization_name": "Example - In investigation",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"status": DomainApplication.ApplicationStatus.IN_REVIEW,
|
"status": DomainRequest.DomainRequestStatus.IN_REVIEW,
|
||||||
"organization_name": "Example - Approved",
|
"organization_name": "Example - Approved",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"status": DomainApplication.ApplicationStatus.WITHDRAWN,
|
"status": DomainRequest.DomainRequestStatus.WITHDRAWN,
|
||||||
"organization_name": "Example - Withdrawn",
|
"organization_name": "Example - Withdrawn",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"status": DomainApplication.ApplicationStatus.ACTION_NEEDED,
|
"status": DomainRequest.DomainRequestStatus.ACTION_NEEDED,
|
||||||
"organization_name": "Example - Action needed",
|
"organization_name": "Example - Action needed",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -94,7 +94,7 @@ class DomainApplicationFixture:
|
||||||
return f"{fake.slug()}.gov"
|
return f"{fake.slug()}.gov"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _set_non_foreign_key_fields(cls, da: DomainApplication, app: dict):
|
def _set_non_foreign_key_fields(cls, da: DomainRequest, 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 = app["organization_type"] if "organization_type" in app else "federal"
|
da.organization_type = app["organization_type"] if "organization_type" in app else "federal"
|
||||||
|
@ -102,7 +102,7 @@ class DomainApplicationFixture:
|
||||||
app["federal_agency"]
|
app["federal_agency"]
|
||||||
if "federal_agency" in app
|
if "federal_agency" in app
|
||||||
# Random choice of agency for selects, used as placeholders for testing.
|
# Random choice of agency for selects, used as placeholders for testing.
|
||||||
else random.choice(DomainApplication.AGENCIES) # nosec
|
else random.choice(DomainRequest.AGENCIES) # nosec
|
||||||
)
|
)
|
||||||
da.submission_date = fake.date()
|
da.submission_date = fake.date()
|
||||||
da.federal_type = (
|
da.federal_type = (
|
||||||
|
@ -121,7 +121,7 @@ class DomainApplicationFixture:
|
||||||
da.is_policy_acknowledged = app["is_policy_acknowledged"] if "is_policy_acknowledged" in app else True
|
da.is_policy_acknowledged = 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: DomainRequest, app: dict, user: User):
|
||||||
"""Helper method used by `load`."""
|
"""Helper method used by `load`."""
|
||||||
if not da.investigator:
|
if not da.investigator:
|
||||||
da.investigator = User.objects.get(username=user.username) if "investigator" in app else None
|
da.investigator = User.objects.get(username=user.username) if "investigator" in app else None
|
||||||
|
@ -145,7 +145,7 @@ class DomainApplicationFixture:
|
||||||
da.requested_domain = DraftDomain.objects.create(name=cls.fake_dot_gov())
|
da.requested_domain = DraftDomain.objects.create(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: DomainRequest, app: dict):
|
||||||
"""Helper method used by `load`."""
|
"""Helper method used by `load`."""
|
||||||
if "other_contacts" in app:
|
if "other_contacts" in app:
|
||||||
for contact in app["other_contacts"]:
|
for contact in app["other_contacts"]:
|
||||||
|
@ -176,8 +176,8 @@ class DomainApplicationFixture:
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def load(cls):
|
def load(cls):
|
||||||
"""Creates domain applications for each user in the database."""
|
"""Creates domain requests for each user in the database."""
|
||||||
logger.info("Going to load %s domain applications" % len(cls.DA))
|
logger.info("Going to load %s domain requests" % len(cls.DA))
|
||||||
try:
|
try:
|
||||||
users = list(User.objects.all()) # force evaluation to catch db errors
|
users = list(User.objects.all()) # force evaluation to catch db errors
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -185,10 +185,10 @@ class DomainApplicationFixture:
|
||||||
return
|
return
|
||||||
|
|
||||||
for user in users:
|
for user in users:
|
||||||
logger.debug("Loading domain applications for %s" % user)
|
logger.debug("Loading domain requests for %s" % user)
|
||||||
for app in cls.DA:
|
for app in cls.DA:
|
||||||
try:
|
try:
|
||||||
da, _ = DomainApplication.objects.get_or_create(
|
da, _ = DomainRequest.objects.get_or_create(
|
||||||
creator=user,
|
creator=user,
|
||||||
organization_name=app["organization_name"],
|
organization_name=app["organization_name"],
|
||||||
)
|
)
|
||||||
|
@ -200,7 +200,7 @@ class DomainApplicationFixture:
|
||||||
logger.warning(e)
|
logger.warning(e)
|
||||||
|
|
||||||
|
|
||||||
class DomainFixture(DomainApplicationFixture):
|
class DomainFixture(DomainRequestFixture):
|
||||||
"""Create one domain and permissions on it for each user."""
|
"""Create one domain and permissions on it for each user."""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -213,12 +213,12 @@ 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(
|
domain_request = DomainRequest.objects.filter(
|
||||||
creator=user, status=DomainApplication.ApplicationStatus.IN_REVIEW
|
creator=user, status=DomainRequest.DomainRequestStatus.IN_REVIEW
|
||||||
).last()
|
).last()
|
||||||
logger.debug(f"Approving {application} for {user}")
|
logger.debug(f"Approving {domain_request} for {user}")
|
||||||
|
|
||||||
# We don't want fixtures sending out real emails to
|
# We don't want fixtures sending out real emails to
|
||||||
# fake email addresses, so we just skip that and log it instead
|
# fake email addresses, so we just skip that and log it instead
|
||||||
application.approve(send_email=False)
|
domain_request.approve(send_email=False)
|
||||||
application.save()
|
domain_request.save()
|
|
@ -1,4 +1,4 @@
|
||||||
from .application_wizard import *
|
from .domain_request_wizard import *
|
||||||
from .domain import (
|
from .domain import (
|
||||||
DomainAddUserForm,
|
DomainAddUserForm,
|
||||||
NameserverFormset,
|
NameserverFormset,
|
||||||
|
|
|
@ -10,7 +10,7 @@ from django.core.validators import RegexValidator, MaxLengthValidator
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django.db.models.fields.related import ForeignObjectRel
|
from django.db.models.fields.related import ForeignObjectRel
|
||||||
|
|
||||||
from registrar.models import Contact, DomainApplication, DraftDomain, Domain
|
from registrar.models import Contact, DomainRequest, DraftDomain, Domain
|
||||||
from registrar.templatetags.url_helpers import public_site_url
|
from registrar.templatetags.url_helpers import public_site_url
|
||||||
from registrar.utility.enums import ValidationReturnType
|
from registrar.utility.enums import ValidationReturnType
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ class RegistrarForm(forms.Form):
|
||||||
"""
|
"""
|
||||||
A common set of methods and configuration.
|
A common set of methods and configuration.
|
||||||
|
|
||||||
The registrar's domain application is several pages of "steps".
|
The registrar's domain request is several pages of "steps".
|
||||||
Each step is an HTML form containing one or more Django "forms".
|
Each step is an HTML form containing one or more Django "forms".
|
||||||
|
|
||||||
Subclass this class to create new forms.
|
Subclass this class to create new forms.
|
||||||
|
@ -29,11 +29,11 @@ class RegistrarForm(forms.Form):
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
kwargs.setdefault("label_suffix", "")
|
kwargs.setdefault("label_suffix", "")
|
||||||
# save a reference to an application object
|
# save a reference to a domain request object
|
||||||
self.application = kwargs.pop("application", None)
|
self.domain_request = kwargs.pop("domain_request", None)
|
||||||
super(RegistrarForm, self).__init__(*args, **kwargs)
|
super(RegistrarForm, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
def to_database(self, obj: DomainApplication | Contact):
|
def to_database(self, obj: DomainRequest | Contact):
|
||||||
"""
|
"""
|
||||||
Adds this form's cleaned data to `obj` and saves `obj`.
|
Adds this form's cleaned data to `obj` and saves `obj`.
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ class RegistrarForm(forms.Form):
|
||||||
obj.save()
|
obj.save()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_database(cls, obj: DomainApplication | Contact | None):
|
def from_database(cls, obj: DomainRequest | Contact | None):
|
||||||
"""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 {}
|
||||||
|
@ -61,8 +61,8 @@ class RegistrarFormSet(forms.BaseFormSet):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
# save a reference to an application object
|
# save a reference to an domain_request object
|
||||||
self.application = kwargs.pop("application", None)
|
self.domain_request = kwargs.pop("domain_request", None)
|
||||||
super(RegistrarFormSet, self).__init__(*args, **kwargs)
|
super(RegistrarFormSet, self).__init__(*args, **kwargs)
|
||||||
# quick workaround to ensure that the HTML `required`
|
# quick workaround to ensure that the HTML `required`
|
||||||
# attribute shows up on required fields for any forms
|
# attribute shows up on required fields for any forms
|
||||||
|
@ -85,7 +85,7 @@ class RegistrarFormSet(forms.BaseFormSet):
|
||||||
"""Code to run before an item in the formset is created in the database."""
|
"""Code to run before an item in the formset is created in the database."""
|
||||||
return cleaned
|
return cleaned
|
||||||
|
|
||||||
def to_database(self, obj: DomainApplication):
|
def to_database(self, obj: DomainRequest):
|
||||||
"""
|
"""
|
||||||
Adds this form's cleaned data to `obj` and saves `obj`.
|
Adds this form's cleaned data to `obj` and saves `obj`.
|
||||||
|
|
||||||
|
@ -97,7 +97,7 @@ class RegistrarFormSet(forms.BaseFormSet):
|
||||||
|
|
||||||
def _to_database(
|
def _to_database(
|
||||||
self,
|
self,
|
||||||
obj: DomainApplication,
|
obj: DomainRequest,
|
||||||
join: str,
|
join: str,
|
||||||
should_delete: Callable,
|
should_delete: Callable,
|
||||||
pre_update: Callable,
|
pre_update: Callable,
|
||||||
|
@ -137,14 +137,14 @@ class RegistrarFormSet(forms.BaseFormSet):
|
||||||
if should_delete(cleaned):
|
if should_delete(cleaned):
|
||||||
if hasattr(db_obj, "has_more_than_one_join") and db_obj.has_more_than_one_join(related_name):
|
if hasattr(db_obj, "has_more_than_one_join") and db_obj.has_more_than_one_join(related_name):
|
||||||
# Remove the specific relationship without deleting the object
|
# Remove the specific relationship without deleting the object
|
||||||
getattr(db_obj, related_name).remove(self.application)
|
getattr(db_obj, related_name).remove(self.domain_request)
|
||||||
else:
|
else:
|
||||||
# If there are no other relationships, delete the object
|
# If there are no other relationships, delete the object
|
||||||
db_obj.delete()
|
db_obj.delete()
|
||||||
else:
|
else:
|
||||||
if hasattr(db_obj, "has_more_than_one_join") and db_obj.has_more_than_one_join(related_name):
|
if hasattr(db_obj, "has_more_than_one_join") and db_obj.has_more_than_one_join(related_name):
|
||||||
# create a new db_obj and disconnect existing one
|
# create a new db_obj and disconnect existing one
|
||||||
getattr(db_obj, related_name).remove(self.application)
|
getattr(db_obj, related_name).remove(self.domain_request)
|
||||||
kwargs = pre_create(db_obj, cleaned)
|
kwargs = pre_create(db_obj, cleaned)
|
||||||
getattr(obj, join).create(**kwargs)
|
getattr(obj, join).create(**kwargs)
|
||||||
else:
|
else:
|
||||||
|
@ -163,15 +163,15 @@ class RegistrarFormSet(forms.BaseFormSet):
|
||||||
return query.values()
|
return query.values()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_database(cls, obj: DomainApplication, join: str, on_fetch: Callable):
|
def from_database(cls, obj: DomainRequest, join: str, on_fetch: Callable):
|
||||||
"""Returns a dict of form field values gotten from `obj`."""
|
"""Returns a dict of form field values gotten from `obj`."""
|
||||||
return on_fetch(getattr(obj, join).order_by("created_at")) # order matters
|
return on_fetch(getattr(obj, join).order_by("created_at")) # order matters
|
||||||
|
|
||||||
|
|
||||||
class OrganizationTypeForm(RegistrarForm):
|
class OrganizationTypeForm(RegistrarForm):
|
||||||
organization_type = forms.ChoiceField(
|
organization_type = forms.ChoiceField(
|
||||||
# use the long names in the application form
|
# use the long names in the domain request form
|
||||||
choices=DomainApplication.OrganizationChoicesVerbose.choices,
|
choices=DomainRequest.OrganizationChoicesVerbose.choices,
|
||||||
widget=forms.RadioSelect,
|
widget=forms.RadioSelect,
|
||||||
error_messages={"required": "Select the type of organization you represent."},
|
error_messages={"required": "Select the type of organization you represent."},
|
||||||
)
|
)
|
||||||
|
@ -201,7 +201,7 @@ class TribalGovernmentForm(RegistrarForm):
|
||||||
# into a link. There should be no user-facing input in the
|
# into a link. There should be no user-facing input in the
|
||||||
# HTML indicated here.
|
# HTML indicated here.
|
||||||
mark_safe( # nosec
|
mark_safe( # nosec
|
||||||
"You can’t complete this application yet. "
|
"You can’t complete this domain request yet. "
|
||||||
"Only tribes recognized by the U.S. federal government "
|
"Only tribes recognized by the U.S. federal government "
|
||||||
"or by a U.S. state government are eligible for .gov "
|
"or by a U.S. state government are eligible for .gov "
|
||||||
'domains. Use our <a href="{}">contact form</a> to '
|
'domains. Use our <a href="{}">contact form</a> to '
|
||||||
|
@ -215,7 +215,7 @@ class TribalGovernmentForm(RegistrarForm):
|
||||||
|
|
||||||
class OrganizationFederalForm(RegistrarForm):
|
class OrganizationFederalForm(RegistrarForm):
|
||||||
federal_type = forms.ChoiceField(
|
federal_type = forms.ChoiceField(
|
||||||
choices=DomainApplication.BranchChoices.choices,
|
choices=DomainRequest.BranchChoices.choices,
|
||||||
widget=forms.RadioSelect,
|
widget=forms.RadioSelect,
|
||||||
error_messages={"required": ("Select the part of the federal government your organization is in.")},
|
error_messages={"required": ("Select the part of the federal government your organization is in.")},
|
||||||
)
|
)
|
||||||
|
@ -251,7 +251,7 @@ class OrganizationContactForm(RegistrarForm):
|
||||||
# it is a federal agency. Use clean to check programatically
|
# it is a federal agency. Use clean to check programatically
|
||||||
# if it has been filled in when required.
|
# if it has been filled in when required.
|
||||||
required=False,
|
required=False,
|
||||||
choices=[("", "--Select--")] + DomainApplication.AGENCY_CHOICES,
|
choices=[("", "--Select--")] + DomainRequest.AGENCY_CHOICES,
|
||||||
)
|
)
|
||||||
organization_name = forms.CharField(
|
organization_name = forms.CharField(
|
||||||
label="Organization name",
|
label="Organization name",
|
||||||
|
@ -271,7 +271,7 @@ class OrganizationContactForm(RegistrarForm):
|
||||||
)
|
)
|
||||||
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--")] + DomainRequest.StateTerritoryChoices.choices,
|
||||||
error_messages={
|
error_messages={
|
||||||
"required": ("Select the state, territory, or military post where your organization is located.")
|
"required": ("Select the state, territory, or military post where your organization is located.")
|
||||||
},
|
},
|
||||||
|
@ -294,16 +294,16 @@ class OrganizationContactForm(RegistrarForm):
|
||||||
def clean_federal_agency(self):
|
def clean_federal_agency(self):
|
||||||
"""Require something to be selected when this is a federal agency."""
|
"""Require something to be selected when this is a federal agency."""
|
||||||
federal_agency = self.cleaned_data.get("federal_agency", None)
|
federal_agency = self.cleaned_data.get("federal_agency", None)
|
||||||
# need the application object to know if this is federal
|
# need the domain request object to know if this is federal
|
||||||
if self.application is None:
|
if self.domain_request is None:
|
||||||
# hmm, no saved application object?, default require the agency
|
# hmm, no saved domain request object?, default require the agency
|
||||||
if not federal_agency:
|
if not federal_agency:
|
||||||
# no answer was selected
|
# no answer was selected
|
||||||
raise forms.ValidationError(
|
raise forms.ValidationError(
|
||||||
"Select the federal agency your organization is in.",
|
"Select the federal agency your organization is in.",
|
||||||
code="required",
|
code="required",
|
||||||
)
|
)
|
||||||
if self.application.is_federal():
|
if self.domain_request.is_federal():
|
||||||
if not federal_agency:
|
if not federal_agency:
|
||||||
# no answer was selected
|
# no answer was selected
|
||||||
raise forms.ValidationError(
|
raise forms.ValidationError(
|
||||||
|
@ -390,7 +390,7 @@ class BaseCurrentSitesFormSet(RegistrarFormSet):
|
||||||
website = cleaned.get("website", "")
|
website = cleaned.get("website", "")
|
||||||
return website.strip() == ""
|
return website.strip() == ""
|
||||||
|
|
||||||
def to_database(self, obj: DomainApplication):
|
def to_database(self, obj: DomainRequest):
|
||||||
# If we want to test against multiple joins for a website object, replace the empty array
|
# If we want to test against multiple joins for a website object, replace the empty array
|
||||||
# and change the JOIN in the models to allow for reverse references
|
# and change the JOIN in the models to allow for reverse references
|
||||||
self._to_database(obj, self.JOIN, self.should_delete, self.pre_update, self.pre_create)
|
self._to_database(obj, self.JOIN, self.should_delete, self.pre_update, self.pre_create)
|
||||||
|
@ -444,7 +444,7 @@ class BaseAlternativeDomainFormSet(RegistrarFormSet):
|
||||||
else:
|
else:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def to_database(self, obj: DomainApplication):
|
def to_database(self, obj: DomainRequest):
|
||||||
# If we want to test against multiple joins for a website object, replace the empty array and
|
# If we want to test against multiple joins for a website object, replace the empty array and
|
||||||
# change the JOIN in the models to allow for reverse references
|
# change the JOIN in the models to allow for reverse references
|
||||||
self._to_database(obj, self.JOIN, self.should_delete, self.pre_update, self.pre_create)
|
self._to_database(obj, self.JOIN, self.should_delete, self.pre_update, self.pre_create)
|
||||||
|
@ -530,7 +530,7 @@ class YourContactForm(RegistrarForm):
|
||||||
if not self.is_valid():
|
if not self.is_valid():
|
||||||
return
|
return
|
||||||
contact = getattr(obj, "submitter", None)
|
contact = getattr(obj, "submitter", None)
|
||||||
if contact is not None and not contact.has_more_than_one_join("submitted_applications"):
|
if contact is not None and not contact.has_more_than_one_join("submitted_domain_requests"):
|
||||||
# if contact exists in the database and is not joined to other entities
|
# if contact exists in the database and is not joined to other entities
|
||||||
super().to_database(contact)
|
super().to_database(contact)
|
||||||
else:
|
else:
|
||||||
|
@ -578,13 +578,13 @@ class OtherContactsYesNoForm(RegistrarForm):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
"""Extend the initialization of the form from RegistrarForm __init__"""
|
"""Extend the initialization of the form from RegistrarForm __init__"""
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
# set the initial value based on attributes of application
|
# set the initial value based on attributes of domain request
|
||||||
if self.application and self.application.has_other_contacts():
|
if self.domain_request and self.domain_request.has_other_contacts():
|
||||||
initial_value = True
|
initial_value = True
|
||||||
elif self.application and self.application.has_rationale():
|
elif self.domain_request and self.domain_request.has_rationale():
|
||||||
initial_value = False
|
initial_value = False
|
||||||
else:
|
else:
|
||||||
# No pre-selection for new applications
|
# No pre-selection for new domain requests
|
||||||
initial_value = None
|
initial_value = None
|
||||||
|
|
||||||
self.fields["has_other_contacts"] = forms.TypedChoiceField(
|
self.fields["has_other_contacts"] = forms.TypedChoiceField(
|
||||||
|
@ -687,7 +687,7 @@ class BaseOtherContactsFormSet(RegistrarFormSet):
|
||||||
this case, all forms in formset are marked for deletion. Both of these conditions
|
this case, all forms in formset are marked for deletion. Both of these conditions
|
||||||
must co-exist.
|
must co-exist.
|
||||||
Also, other_contacts have db relationships to multiple db objects. When attempting
|
Also, other_contacts have db relationships to multiple db objects. When attempting
|
||||||
to delete an other_contact from an application, those db relationships must be
|
to delete an other_contact from a domain request, those db relationships must be
|
||||||
tested and handled.
|
tested and handled.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -701,7 +701,7 @@ class BaseOtherContactsFormSet(RegistrarFormSet):
|
||||||
Override __init__ for RegistrarFormSet.
|
Override __init__ for RegistrarFormSet.
|
||||||
"""
|
"""
|
||||||
self.formset_data_marked_for_deletion = False
|
self.formset_data_marked_for_deletion = False
|
||||||
self.application = kwargs.pop("application", None)
|
self.domain_request = kwargs.pop("domain_request", None)
|
||||||
super(RegistrarFormSet, self).__init__(*args, **kwargs)
|
super(RegistrarFormSet, self).__init__(*args, **kwargs)
|
||||||
# quick workaround to ensure that the HTML `required`
|
# quick workaround to ensure that the HTML `required`
|
||||||
# attribute shows up on required fields for the first form
|
# attribute shows up on required fields for the first form
|
||||||
|
@ -722,7 +722,7 @@ class BaseOtherContactsFormSet(RegistrarFormSet):
|
||||||
cleaned.pop("DELETE")
|
cleaned.pop("DELETE")
|
||||||
return cleaned
|
return cleaned
|
||||||
|
|
||||||
def to_database(self, obj: DomainApplication):
|
def to_database(self, obj: DomainRequest):
|
||||||
self._to_database(obj, self.JOIN, self.should_delete, self.pre_update, self.pre_create)
|
self._to_database(obj, self.JOIN, self.should_delete, self.pre_update, self.pre_create)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
|
@ -5,7 +5,7 @@ from auditlog.context import disable_auditlog # type: ignore
|
||||||
|
|
||||||
|
|
||||||
from registrar.fixtures_users import UserFixture
|
from registrar.fixtures_users import UserFixture
|
||||||
from registrar.fixtures_applications import DomainApplicationFixture, DomainFixture
|
from registrar.fixtures_domain_requests import DomainRequestFixture, DomainFixture
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -16,6 +16,6 @@ class Command(BaseCommand):
|
||||||
# https://github.com/jazzband/django-auditlog/issues/17
|
# https://github.com/jazzband/django-auditlog/issues/17
|
||||||
with disable_auditlog():
|
with disable_auditlog():
|
||||||
UserFixture.load()
|
UserFixture.load()
|
||||||
DomainApplicationFixture.load()
|
DomainRequestFixture.load()
|
||||||
DomainFixture.load()
|
DomainFixture.load()
|
||||||
logger.info("All fixtures loaded.")
|
logger.info("All fixtures loaded.")
|
||||||
|
|
|
@ -15,7 +15,7 @@ from registrar.management.commands.utility.terminal_helper import (
|
||||||
TerminalHelper,
|
TerminalHelper,
|
||||||
)
|
)
|
||||||
from registrar.models.contact import Contact
|
from registrar.models.contact import Contact
|
||||||
from registrar.models.domain_application import DomainApplication
|
from registrar.models.domain_request import DomainRequest
|
||||||
from registrar.models.domain_information import DomainInformation
|
from registrar.models.domain_information import DomainInformation
|
||||||
from registrar.models.user import User
|
from registrar.models.user import User
|
||||||
|
|
||||||
|
@ -817,9 +817,9 @@ class Command(BaseCommand):
|
||||||
raise Exception(f"Domain {existing_domain} wants to be added" "but doesn't exist in the DB")
|
raise Exception(f"Domain {existing_domain} wants to be added" "but doesn't exist in the DB")
|
||||||
invitation.save()
|
invitation.save()
|
||||||
|
|
||||||
valid_org_choices = [(name, value) for name, value in DomainApplication.OrganizationChoices.choices]
|
valid_org_choices = [(name, value) for name, value in DomainRequest.OrganizationChoices.choices]
|
||||||
valid_fed_choices = [value for name, value in DomainApplication.BranchChoices.choices]
|
valid_fed_choices = [value for name, value in DomainRequest.BranchChoices.choices]
|
||||||
valid_agency_choices = DomainApplication.AGENCIES
|
valid_agency_choices = DomainRequest.AGENCIES
|
||||||
# ======================================================
|
# ======================================================
|
||||||
# ================= DOMAIN INFORMATION =================
|
# ================= DOMAIN INFORMATION =================
|
||||||
logger.info(
|
logger.info(
|
||||||
|
|
|
@ -5,7 +5,6 @@ import phonenumber_field.modelfields
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("registrar", "0070_domainapplication_rejection_reason"),
|
("registrar", "0070_domainapplication_rejection_reason"),
|
||||||
]
|
]
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
# Generated by Django 4.2.10 on 2024-03-05 21:44
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("registrar", "0071_alter_contact_first_name_alter_contact_last_name_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="publiccontact",
|
||||||
|
name="fax",
|
||||||
|
field=models.CharField(
|
||||||
|
help_text="Contact's fax number (null ok). Must be in ITU.E164.2005 format.", null=True
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="publiccontact",
|
||||||
|
name="voice",
|
||||||
|
field=models.CharField(help_text="Contact's phone number. Must be in ITU.E164.2005 format"),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,110 @@
|
||||||
|
# Generated by Django 4.2.10 on 2024-03-12 16:50
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("registrar", "0072_alter_publiccontact_fax_alter_publiccontact_voice"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="domainapplication",
|
||||||
|
name="approved_domain",
|
||||||
|
field=models.OneToOneField(
|
||||||
|
blank=True,
|
||||||
|
help_text="The approved domain",
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
related_name="domain_request",
|
||||||
|
to="registrar.domain",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="domainapplication",
|
||||||
|
name="creator",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.PROTECT,
|
||||||
|
related_name="domain_requests_created",
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="domainapplication",
|
||||||
|
name="investigator",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
related_name="domain_requests_investigating",
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="domainapplication",
|
||||||
|
name="other_contacts",
|
||||||
|
field=models.ManyToManyField(
|
||||||
|
blank=True, related_name="contact_domain_requests", to="registrar.contact", verbose_name="contacts"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="domainapplication",
|
||||||
|
name="requested_domain",
|
||||||
|
field=models.OneToOneField(
|
||||||
|
blank=True,
|
||||||
|
help_text="The requested domain",
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.PROTECT,
|
||||||
|
related_name="domain_request",
|
||||||
|
to="registrar.draftdomain",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="domainapplication",
|
||||||
|
name="submitter",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.PROTECT,
|
||||||
|
related_name="submitted_domain_requests",
|
||||||
|
to="registrar.contact",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="domaininformation",
|
||||||
|
name="domain_application",
|
||||||
|
field=models.OneToOneField(
|
||||||
|
blank=True,
|
||||||
|
help_text="Associated domain request",
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.PROTECT,
|
||||||
|
related_name="DomainRequest_info",
|
||||||
|
to="registrar.domainapplication",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="domaininformation",
|
||||||
|
name="other_contacts",
|
||||||
|
field=models.ManyToManyField(
|
||||||
|
blank=True,
|
||||||
|
related_name="contact_domain_requests_information",
|
||||||
|
to="registrar.contact",
|
||||||
|
verbose_name="contacts",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="domaininformation",
|
||||||
|
name="submitter",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.PROTECT,
|
||||||
|
related_name="submitted_domain_requests_information",
|
||||||
|
to="registrar.contact",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,22 @@
|
||||||
|
# Generated by Django 4.2.10 on 2024-03-12 16:53
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("registrar", "0073_alter_domainapplication_approved_domain_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameModel(
|
||||||
|
old_name="DomainApplication",
|
||||||
|
new_name="DomainRequest",
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name="domaininformation",
|
||||||
|
old_name="domain_application",
|
||||||
|
new_name="domain_request",
|
||||||
|
),
|
||||||
|
]
|
37
src/registrar/migrations/0075_create_groups_v08.py
Normal file
37
src/registrar/migrations/0075_create_groups_v08.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", "0074_rename_domainapplication_domainrequest_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(
|
||||||
|
create_groups,
|
||||||
|
reverse_code=migrations.RunPython.noop,
|
||||||
|
atomic=True,
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,6 +1,6 @@
|
||||||
from auditlog.registry import auditlog # type: ignore
|
from auditlog.registry import auditlog # type: ignore
|
||||||
from .contact import Contact
|
from .contact import Contact
|
||||||
from .domain_application import DomainApplication
|
from .domain_request import DomainRequest
|
||||||
from .domain_information import DomainInformation
|
from .domain_information import DomainInformation
|
||||||
from .domain import Domain
|
from .domain import Domain
|
||||||
from .draft_domain import DraftDomain
|
from .draft_domain import DraftDomain
|
||||||
|
@ -17,7 +17,7 @@ from .verified_by_staff import VerifiedByStaff
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"Contact",
|
"Contact",
|
||||||
"DomainApplication",
|
"DomainRequest",
|
||||||
"DomainInformation",
|
"DomainInformation",
|
||||||
"Domain",
|
"Domain",
|
||||||
"DraftDomain",
|
"DraftDomain",
|
||||||
|
@ -34,7 +34,7 @@ __all__ = [
|
||||||
]
|
]
|
||||||
|
|
||||||
auditlog.register(Contact)
|
auditlog.register(Contact)
|
||||||
auditlog.register(DomainApplication)
|
auditlog.register(DomainRequest)
|
||||||
auditlog.register(Domain)
|
auditlog.register(Domain)
|
||||||
auditlog.register(DraftDomain)
|
auditlog.register(DraftDomain)
|
||||||
auditlog.register(DomainInvitation)
|
auditlog.register(DomainInvitation)
|
||||||
|
|
|
@ -897,8 +897,8 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
def security_contact(self, contact: PublicContact):
|
def security_contact(self, contact: PublicContact):
|
||||||
"""makes the contact in the registry,
|
"""makes the contact in the registry,
|
||||||
for security the public contact should have the org or registrant information
|
for security the public contact should have the org or registrant information
|
||||||
from domain information (not domain application)
|
from domain information (not domain request)
|
||||||
and should have the security email from DomainApplication"""
|
and should have the security email from DomainRequest"""
|
||||||
logger.info("making security contact in registry")
|
logger.info("making security contact in registry")
|
||||||
self._set_singleton_contact(contact, expectedType=contact.ContactTypeChoices.SECURITY)
|
self._set_singleton_contact(contact, expectedType=contact.ContactTypeChoices.SECURITY)
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ from __future__ import annotations
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
|
|
||||||
from registrar.models.utility.domain_helper import DomainHelper
|
from registrar.models.utility.domain_helper import DomainHelper
|
||||||
from .domain_application import DomainApplication
|
from .domain_request import DomainRequest
|
||||||
from .utility.time_stamped_model import TimeStampedModel
|
from .utility.time_stamped_model import TimeStampedModel
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
@ -15,22 +15,22 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class DomainInformation(TimeStampedModel):
|
class DomainInformation(TimeStampedModel):
|
||||||
"""A registrant's domain information for that domain, exported from
|
"""A registrant's domain information for that domain, exported from
|
||||||
DomainApplication. We use these field from DomainApplication with few exceptions
|
DomainRequest. We use these field from DomainRequest with few exceptions
|
||||||
which are 'removed' via pop at the bottom of this file. Most of design for domain
|
which are 'removed' via pop at the bottom of this file. Most of design for domain
|
||||||
management's user information are based on application, but we cannot change
|
management's user information are based on domain_request, but we cannot change
|
||||||
the application once approved, so copying them that way we can make changes
|
the domain request once approved, so copying them that way we can make changes
|
||||||
after its approved. Most fields here are copied from Application."""
|
after its approved. Most fields here are copied from DomainRequest."""
|
||||||
|
|
||||||
StateTerritoryChoices = DomainApplication.StateTerritoryChoices
|
StateTerritoryChoices = DomainRequest.StateTerritoryChoices
|
||||||
|
|
||||||
# use the short names in Django admin
|
# use the short names in Django admin
|
||||||
OrganizationChoices = DomainApplication.OrganizationChoices
|
OrganizationChoices = DomainRequest.OrganizationChoices
|
||||||
|
|
||||||
BranchChoices = DomainApplication.BranchChoices
|
BranchChoices = DomainRequest.BranchChoices
|
||||||
|
|
||||||
AGENCY_CHOICES = DomainApplication.AGENCY_CHOICES
|
AGENCY_CHOICES = DomainRequest.AGENCY_CHOICES
|
||||||
|
|
||||||
# This is the application user who created this application. The contact
|
# This is the domain request user who created this domain request. The contact
|
||||||
# information that they gave is in the `submitter` field
|
# information that they gave is in the `submitter` field
|
||||||
creator = models.ForeignKey(
|
creator = models.ForeignKey(
|
||||||
"registrar.User",
|
"registrar.User",
|
||||||
|
@ -38,13 +38,13 @@ class DomainInformation(TimeStampedModel):
|
||||||
related_name="information_created",
|
related_name="information_created",
|
||||||
)
|
)
|
||||||
|
|
||||||
domain_application = models.OneToOneField(
|
domain_request = models.OneToOneField(
|
||||||
"registrar.DomainApplication",
|
"registrar.DomainRequest",
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
related_name="domainapplication_info",
|
related_name="DomainRequest_info",
|
||||||
help_text="Associated domain application",
|
help_text="Associated domain request",
|
||||||
unique=True,
|
unique=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -163,13 +163,13 @@ class DomainInformation(TimeStampedModel):
|
||||||
help_text="Domain to which this information belongs",
|
help_text="Domain to which this information belongs",
|
||||||
)
|
)
|
||||||
|
|
||||||
# This is the contact information provided by the applicant. The
|
# This is the contact information provided by the domain requestor. The
|
||||||
# application user who created it is in the `creator` field.
|
# user who created the domain request is in the `creator` field.
|
||||||
submitter = models.ForeignKey(
|
submitter = models.ForeignKey(
|
||||||
"registrar.Contact",
|
"registrar.Contact",
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
related_name="submitted_applications_information",
|
related_name="submitted_domain_requests_information",
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -182,7 +182,7 @@ class DomainInformation(TimeStampedModel):
|
||||||
other_contacts = models.ManyToManyField(
|
other_contacts = models.ManyToManyField(
|
||||||
"registrar.Contact",
|
"registrar.Contact",
|
||||||
blank=True,
|
blank=True,
|
||||||
related_name="contact_applications_information",
|
related_name="contact_domain_requests_information",
|
||||||
verbose_name="contacts",
|
verbose_name="contacts",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -220,25 +220,25 @@ class DomainInformation(TimeStampedModel):
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_from_da(cls, domain_application: DomainApplication, domain=None):
|
def create_from_da(cls, domain_request: DomainRequest, domain=None):
|
||||||
"""Takes in a DomainApplication and converts it into DomainInformation"""
|
"""Takes in a DomainRequest and converts it into DomainInformation"""
|
||||||
|
|
||||||
# Throw an error if we get None - we can't create something from nothing
|
# Throw an error if we get None - we can't create something from nothing
|
||||||
if domain_application is None:
|
if domain_request is None:
|
||||||
raise ValueError("The provided DomainApplication is None")
|
raise ValueError("The provided DomainRequest is None")
|
||||||
|
|
||||||
# Throw an error if the da doesn't have an id
|
# Throw an error if the da doesn't have an id
|
||||||
if not hasattr(domain_application, "id"):
|
if not hasattr(domain_request, "id"):
|
||||||
raise ValueError("The provided DomainApplication has no id")
|
raise ValueError("The provided DomainRequest has no id")
|
||||||
|
|
||||||
# check if we have a record that corresponds with the domain
|
# check if we have a record that corresponds with the domain
|
||||||
# application, if so short circuit the create
|
# domain_request, if so short circuit the create
|
||||||
existing_domain_info = cls.objects.filter(domain_application__id=domain_application.id).first()
|
existing_domain_info = cls.objects.filter(domain_request__id=domain_request.id).first()
|
||||||
if existing_domain_info:
|
if existing_domain_info:
|
||||||
return existing_domain_info
|
return existing_domain_info
|
||||||
|
|
||||||
# Get the fields that exist on both DomainApplication and DomainInformation
|
# Get the fields that exist on both DomainRequest and DomainInformation
|
||||||
common_fields = DomainHelper.get_common_fields(DomainApplication, DomainInformation)
|
common_fields = DomainHelper.get_common_fields(DomainRequest, DomainInformation)
|
||||||
|
|
||||||
# Get a list of all many_to_many relations on DomainInformation (needs to be saved differently)
|
# Get a list of all many_to_many relations on DomainInformation (needs to be saved differently)
|
||||||
info_many_to_many_fields = DomainInformation._get_many_to_many_fields()
|
info_many_to_many_fields = DomainInformation._get_many_to_many_fields()
|
||||||
|
@ -249,11 +249,11 @@ class DomainInformation(TimeStampedModel):
|
||||||
for field in common_fields:
|
for field in common_fields:
|
||||||
# If the field isn't many_to_many, populate the da_dict.
|
# If the field isn't many_to_many, populate the da_dict.
|
||||||
# If it is, populate da_many_to_many_dict as we need to save this later.
|
# If it is, populate da_many_to_many_dict as we need to save this later.
|
||||||
if hasattr(domain_application, field):
|
if hasattr(domain_request, field):
|
||||||
if field not in info_many_to_many_fields:
|
if field not in info_many_to_many_fields:
|
||||||
da_dict[field] = getattr(domain_application, field)
|
da_dict[field] = getattr(domain_request, field)
|
||||||
else:
|
else:
|
||||||
da_many_to_many_dict[field] = getattr(domain_application, field).all()
|
da_many_to_many_dict[field] = getattr(domain_request, field).all()
|
||||||
|
|
||||||
# This will not happen in normal code flow, but having some redundancy doesn't hurt.
|
# This will not happen in normal code flow, but having some redundancy doesn't hurt.
|
||||||
# da_dict should not have "id" under any circumstances.
|
# da_dict should not have "id" under any circumstances.
|
||||||
|
@ -266,8 +266,8 @@ class DomainInformation(TimeStampedModel):
|
||||||
# Create a placeholder DomainInformation object
|
# Create a placeholder DomainInformation object
|
||||||
domain_info = DomainInformation(**da_dict)
|
domain_info = DomainInformation(**da_dict)
|
||||||
|
|
||||||
# Add the domain_application and domain fields
|
# Add the domain_request and domain fields
|
||||||
domain_info.domain_application = domain_application
|
domain_info.domain_request = domain_request
|
||||||
if domain:
|
if domain:
|
||||||
domain_info.domain = domain
|
domain_info.domain = domain
|
||||||
|
|
||||||
|
|
|
@ -17,11 +17,11 @@ from itertools import chain
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class DomainApplication(TimeStampedModel):
|
class DomainRequest(TimeStampedModel):
|
||||||
"""A registrant's application for a new domain."""
|
"""A registrant's domain request for a new domain."""
|
||||||
|
|
||||||
# Constants for choice fields
|
# Constants for choice fields
|
||||||
class ApplicationStatus(models.TextChoices):
|
class DomainRequestStatus(models.TextChoices):
|
||||||
STARTED = "started", "Started"
|
STARTED = "started", "Started"
|
||||||
SUBMITTED = "submitted", "Submitted"
|
SUBMITTED = "submitted", "Submitted"
|
||||||
IN_REVIEW = "in review", "In review"
|
IN_REVIEW = "in review", "In review"
|
||||||
|
@ -115,7 +115,7 @@ class DomainApplication(TimeStampedModel):
|
||||||
class OrganizationChoicesVerbose(models.TextChoices):
|
class OrganizationChoicesVerbose(models.TextChoices):
|
||||||
"""
|
"""
|
||||||
Secondary organization choices
|
Secondary organization choices
|
||||||
For use in the application form and on the templates
|
For use in the domain request form and on the templates
|
||||||
Keys need to match OrganizationChoices
|
Keys need to match OrganizationChoices
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -366,10 +366,10 @@ class DomainApplication(TimeStampedModel):
|
||||||
NAMING_REQUIREMENTS = "naming_not_met", "Naming requirements not met"
|
NAMING_REQUIREMENTS = "naming_not_met", "Naming requirements not met"
|
||||||
OTHER = "other", "Other/Unspecified"
|
OTHER = "other", "Other/Unspecified"
|
||||||
|
|
||||||
# #### Internal fields about the application #####
|
# #### Internal fields about the domain request #####
|
||||||
status = FSMField(
|
status = FSMField(
|
||||||
choices=ApplicationStatus.choices, # possible states as an array of constants
|
choices=DomainRequestStatus.choices, # possible states as an array of constants
|
||||||
default=ApplicationStatus.STARTED, # sensible default
|
default=DomainRequestStatus.STARTED, # sensible default
|
||||||
protected=False, # can change state directly, particularly in Django admin
|
protected=False, # can change state directly, particularly in Django admin
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -379,12 +379,12 @@ class DomainApplication(TimeStampedModel):
|
||||||
blank=True,
|
blank=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
# This is the application user who created this application. The contact
|
# This is the domain request user who created this domain request. The contact
|
||||||
# information that they gave is in the `submitter` field
|
# information that they gave is in the `submitter` field
|
||||||
creator = models.ForeignKey(
|
creator = models.ForeignKey(
|
||||||
"registrar.User",
|
"registrar.User",
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
related_name="applications_created",
|
related_name="domain_requests_created",
|
||||||
)
|
)
|
||||||
|
|
||||||
investigator = models.ForeignKey(
|
investigator = models.ForeignKey(
|
||||||
|
@ -392,7 +392,7 @@ class DomainApplication(TimeStampedModel):
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
related_name="applications_investigating",
|
related_name="domain_requests_investigating",
|
||||||
)
|
)
|
||||||
|
|
||||||
# ##### data fields from the initial form #####
|
# ##### data fields from the initial form #####
|
||||||
|
@ -499,7 +499,7 @@ class DomainApplication(TimeStampedModel):
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
)
|
)
|
||||||
|
|
||||||
# "+" means no reverse relation to lookup applications from Website
|
# "+" means no reverse relation to lookup domain requests from Website
|
||||||
current_websites = models.ManyToManyField(
|
current_websites = models.ManyToManyField(
|
||||||
"registrar.Website",
|
"registrar.Website",
|
||||||
blank=True,
|
blank=True,
|
||||||
|
@ -512,7 +512,7 @@ class DomainApplication(TimeStampedModel):
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text="The approved domain",
|
help_text="The approved domain",
|
||||||
related_name="domain_application",
|
related_name="domain_request",
|
||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -521,7 +521,7 @@ class DomainApplication(TimeStampedModel):
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text="The requested domain",
|
help_text="The requested domain",
|
||||||
related_name="domain_application",
|
related_name="domain_request",
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
)
|
)
|
||||||
alternative_domains = models.ManyToManyField(
|
alternative_domains = models.ManyToManyField(
|
||||||
|
@ -530,13 +530,13 @@ class DomainApplication(TimeStampedModel):
|
||||||
related_name="alternatives+",
|
related_name="alternatives+",
|
||||||
)
|
)
|
||||||
|
|
||||||
# This is the contact information provided by the applicant. The
|
# This is the contact information provided by the domain requestor. The
|
||||||
# application user who created it is in the `creator` field.
|
# user who created the domain request is in the `creator` field.
|
||||||
submitter = models.ForeignKey(
|
submitter = models.ForeignKey(
|
||||||
"registrar.Contact",
|
"registrar.Contact",
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
related_name="submitted_applications",
|
related_name="submitted_domain_requests",
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -549,7 +549,7 @@ class DomainApplication(TimeStampedModel):
|
||||||
other_contacts = models.ManyToManyField(
|
other_contacts = models.ManyToManyField(
|
||||||
"registrar.Contact",
|
"registrar.Contact",
|
||||||
blank=True,
|
blank=True,
|
||||||
related_name="contact_applications",
|
related_name="contact_domain_requests",
|
||||||
verbose_name="contacts",
|
verbose_name="contacts",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -571,7 +571,7 @@ class DomainApplication(TimeStampedModel):
|
||||||
help_text="Acknowledged .gov acceptable use policy",
|
help_text="Acknowledged .gov acceptable use policy",
|
||||||
)
|
)
|
||||||
|
|
||||||
# submission date records when application is submitted
|
# submission date records when domain request is submitted
|
||||||
submission_date = models.DateField(
|
submission_date = models.DateField(
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
|
@ -590,7 +590,7 @@ class DomainApplication(TimeStampedModel):
|
||||||
if self.requested_domain and self.requested_domain.name:
|
if self.requested_domain and self.requested_domain.name:
|
||||||
return self.requested_domain.name
|
return self.requested_domain.name
|
||||||
else:
|
else:
|
||||||
return f"{self.status} application created by {self.creator}"
|
return f"{self.status} domain request created by {self.creator}"
|
||||||
except Exception:
|
except Exception:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
@ -638,7 +638,7 @@ class DomainApplication(TimeStampedModel):
|
||||||
email_template,
|
email_template,
|
||||||
email_template_subject,
|
email_template_subject,
|
||||||
self.submitter.email,
|
self.submitter.email,
|
||||||
context={"application": self},
|
context={"domain_request": self},
|
||||||
bcc_address=bcc_address,
|
bcc_address=bcc_address,
|
||||||
)
|
)
|
||||||
logger.info(f"The {new_status} email sent to: {self.submitter.email}")
|
logger.info(f"The {new_status} email sent to: {self.submitter.email}")
|
||||||
|
@ -648,15 +648,15 @@ class DomainApplication(TimeStampedModel):
|
||||||
@transition(
|
@transition(
|
||||||
field="status",
|
field="status",
|
||||||
source=[
|
source=[
|
||||||
ApplicationStatus.STARTED,
|
DomainRequestStatus.STARTED,
|
||||||
ApplicationStatus.IN_REVIEW,
|
DomainRequestStatus.IN_REVIEW,
|
||||||
ApplicationStatus.ACTION_NEEDED,
|
DomainRequestStatus.ACTION_NEEDED,
|
||||||
ApplicationStatus.WITHDRAWN,
|
DomainRequestStatus.WITHDRAWN,
|
||||||
],
|
],
|
||||||
target=ApplicationStatus.SUBMITTED,
|
target=DomainRequestStatus.SUBMITTED,
|
||||||
)
|
)
|
||||||
def submit(self):
|
def submit(self):
|
||||||
"""Submit an application that is started.
|
"""Submit a domain request that is started.
|
||||||
|
|
||||||
As a side effect, an email notification is sent."""
|
As a side effect, an email notification is sent."""
|
||||||
|
|
||||||
|
@ -679,7 +679,7 @@ class DomainApplication(TimeStampedModel):
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
# Limit email notifications to transitions from Started and Withdrawn
|
# Limit email notifications to transitions from Started and Withdrawn
|
||||||
limited_statuses = [self.ApplicationStatus.STARTED, self.ApplicationStatus.WITHDRAWN]
|
limited_statuses = [self.DomainRequestStatus.STARTED, self.DomainRequestStatus.WITHDRAWN]
|
||||||
|
|
||||||
bcc_address = ""
|
bcc_address = ""
|
||||||
if settings.IS_PRODUCTION:
|
if settings.IS_PRODUCTION:
|
||||||
|
@ -697,17 +697,17 @@ class DomainApplication(TimeStampedModel):
|
||||||
@transition(
|
@transition(
|
||||||
field="status",
|
field="status",
|
||||||
source=[
|
source=[
|
||||||
ApplicationStatus.SUBMITTED,
|
DomainRequestStatus.SUBMITTED,
|
||||||
ApplicationStatus.ACTION_NEEDED,
|
DomainRequestStatus.ACTION_NEEDED,
|
||||||
ApplicationStatus.APPROVED,
|
DomainRequestStatus.APPROVED,
|
||||||
ApplicationStatus.REJECTED,
|
DomainRequestStatus.REJECTED,
|
||||||
ApplicationStatus.INELIGIBLE,
|
DomainRequestStatus.INELIGIBLE,
|
||||||
],
|
],
|
||||||
target=ApplicationStatus.IN_REVIEW,
|
target=DomainRequestStatus.IN_REVIEW,
|
||||||
conditions=[domain_is_not_active],
|
conditions=[domain_is_not_active],
|
||||||
)
|
)
|
||||||
def in_review(self):
|
def in_review(self):
|
||||||
"""Investigate an application that has been submitted.
|
"""Investigate a domain request that has been submitted.
|
||||||
|
|
||||||
This action is logged.
|
This action is logged.
|
||||||
|
|
||||||
|
@ -716,13 +716,13 @@ class DomainApplication(TimeStampedModel):
|
||||||
As side effects this will delete the domain and domain_information
|
As side effects this will delete the domain and domain_information
|
||||||
(will cascade) when they exist."""
|
(will cascade) when they exist."""
|
||||||
|
|
||||||
if self.status == self.ApplicationStatus.APPROVED:
|
if self.status == self.DomainRequestStatus.APPROVED:
|
||||||
self.delete_and_clean_up_domain("in_review")
|
self.delete_and_clean_up_domain("in_review")
|
||||||
|
|
||||||
if self.status == self.ApplicationStatus.REJECTED:
|
if self.status == self.DomainRequestStatus.REJECTED:
|
||||||
self.rejection_reason = None
|
self.rejection_reason = None
|
||||||
|
|
||||||
literal = DomainApplication.ApplicationStatus.IN_REVIEW
|
literal = DomainRequest.DomainRequestStatus.IN_REVIEW
|
||||||
# Check if the tuple exists, then grab its value
|
# Check if the tuple exists, then grab its value
|
||||||
in_review = literal if literal is not None else "In Review"
|
in_review = literal if literal is not None else "In Review"
|
||||||
logger.info(f"A status change occurred. {self} was changed to '{in_review}'")
|
logger.info(f"A status change occurred. {self} was changed to '{in_review}'")
|
||||||
|
@ -730,16 +730,16 @@ class DomainApplication(TimeStampedModel):
|
||||||
@transition(
|
@transition(
|
||||||
field="status",
|
field="status",
|
||||||
source=[
|
source=[
|
||||||
ApplicationStatus.IN_REVIEW,
|
DomainRequestStatus.IN_REVIEW,
|
||||||
ApplicationStatus.APPROVED,
|
DomainRequestStatus.APPROVED,
|
||||||
ApplicationStatus.REJECTED,
|
DomainRequestStatus.REJECTED,
|
||||||
ApplicationStatus.INELIGIBLE,
|
DomainRequestStatus.INELIGIBLE,
|
||||||
],
|
],
|
||||||
target=ApplicationStatus.ACTION_NEEDED,
|
target=DomainRequestStatus.ACTION_NEEDED,
|
||||||
conditions=[domain_is_not_active],
|
conditions=[domain_is_not_active],
|
||||||
)
|
)
|
||||||
def action_needed(self):
|
def action_needed(self):
|
||||||
"""Send back an application that is under investigation or rejected.
|
"""Send back a domain request that is under investigation or rejected.
|
||||||
|
|
||||||
This action is logged.
|
This action is logged.
|
||||||
|
|
||||||
|
@ -748,13 +748,13 @@ class DomainApplication(TimeStampedModel):
|
||||||
As side effects this will delete the domain and domain_information
|
As side effects this will delete the domain and domain_information
|
||||||
(will cascade) when they exist."""
|
(will cascade) when they exist."""
|
||||||
|
|
||||||
if self.status == self.ApplicationStatus.APPROVED:
|
if self.status == self.DomainRequestStatus.APPROVED:
|
||||||
self.delete_and_clean_up_domain("reject_with_prejudice")
|
self.delete_and_clean_up_domain("reject_with_prejudice")
|
||||||
|
|
||||||
if self.status == self.ApplicationStatus.REJECTED:
|
if self.status == self.DomainRequestStatus.REJECTED:
|
||||||
self.rejection_reason = None
|
self.rejection_reason = None
|
||||||
|
|
||||||
literal = DomainApplication.ApplicationStatus.ACTION_NEEDED
|
literal = DomainRequest.DomainRequestStatus.ACTION_NEEDED
|
||||||
# Check if the tuple is setup correctly, then grab its value
|
# Check if the tuple is setup correctly, then grab its value
|
||||||
action_needed = literal if literal is not None else "Action Needed"
|
action_needed = literal if literal is not None else "Action Needed"
|
||||||
logger.info(f"A status change occurred. {self} was changed to '{action_needed}'")
|
logger.info(f"A status change occurred. {self} was changed to '{action_needed}'")
|
||||||
|
@ -762,21 +762,21 @@ class DomainApplication(TimeStampedModel):
|
||||||
@transition(
|
@transition(
|
||||||
field="status",
|
field="status",
|
||||||
source=[
|
source=[
|
||||||
ApplicationStatus.SUBMITTED,
|
DomainRequestStatus.SUBMITTED,
|
||||||
ApplicationStatus.IN_REVIEW,
|
DomainRequestStatus.IN_REVIEW,
|
||||||
ApplicationStatus.ACTION_NEEDED,
|
DomainRequestStatus.ACTION_NEEDED,
|
||||||
ApplicationStatus.REJECTED,
|
DomainRequestStatus.REJECTED,
|
||||||
],
|
],
|
||||||
target=ApplicationStatus.APPROVED,
|
target=DomainRequestStatus.APPROVED,
|
||||||
)
|
)
|
||||||
def approve(self, send_email=True):
|
def approve(self, send_email=True):
|
||||||
"""Approve an application that has been submitted.
|
"""Approve a domain request that has been submitted.
|
||||||
|
|
||||||
This action cleans up the rejection status if moving away from rejected.
|
This action cleans up the rejection status if moving away from rejected.
|
||||||
|
|
||||||
This has substantial side-effects because it creates another database
|
This has substantial side-effects because it creates another database
|
||||||
object for the approved Domain and makes the user who created the
|
object for the approved Domain and makes the user who created the
|
||||||
application into an admin on that domain. It also triggers an email
|
domain request into an admin on that domain. It also triggers an email
|
||||||
notification."""
|
notification."""
|
||||||
|
|
||||||
# create the domain
|
# create the domain
|
||||||
|
@ -786,9 +786,9 @@ class DomainApplication(TimeStampedModel):
|
||||||
created_domain = Domain.objects.create(name=self.requested_domain.name)
|
created_domain = Domain.objects.create(name=self.requested_domain.name)
|
||||||
self.approved_domain = created_domain
|
self.approved_domain = created_domain
|
||||||
|
|
||||||
# copy the information from domainapplication into domaininformation
|
# copy the information from DomainRequest into domaininformation
|
||||||
DomainInformation = apps.get_model("registrar.DomainInformation")
|
DomainInformation = apps.get_model("registrar.DomainInformation")
|
||||||
DomainInformation.create_from_da(domain_application=self, domain=created_domain)
|
DomainInformation.create_from_da(domain_request=self, domain=created_domain)
|
||||||
|
|
||||||
# create the permission for the user
|
# create the permission for the user
|
||||||
UserDomainRole = apps.get_model("registrar.UserDomainRole")
|
UserDomainRole = apps.get_model("registrar.UserDomainRole")
|
||||||
|
@ -796,11 +796,11 @@ class DomainApplication(TimeStampedModel):
|
||||||
user=self.creator, domain=created_domain, role=UserDomainRole.Roles.MANAGER
|
user=self.creator, domain=created_domain, role=UserDomainRole.Roles.MANAGER
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.status == self.ApplicationStatus.REJECTED:
|
if self.status == self.DomainRequestStatus.REJECTED:
|
||||||
self.rejection_reason = None
|
self.rejection_reason = None
|
||||||
|
|
||||||
self._send_status_update_email(
|
self._send_status_update_email(
|
||||||
"application approved",
|
"domain request approved",
|
||||||
"emails/status_change_approved.txt",
|
"emails/status_change_approved.txt",
|
||||||
"emails/status_change_approved_subject.txt",
|
"emails/status_change_approved_subject.txt",
|
||||||
send_email,
|
send_email,
|
||||||
|
@ -808,11 +808,11 @@ class DomainApplication(TimeStampedModel):
|
||||||
|
|
||||||
@transition(
|
@transition(
|
||||||
field="status",
|
field="status",
|
||||||
source=[ApplicationStatus.SUBMITTED, ApplicationStatus.IN_REVIEW, ApplicationStatus.ACTION_NEEDED],
|
source=[DomainRequestStatus.SUBMITTED, DomainRequestStatus.IN_REVIEW, DomainRequestStatus.ACTION_NEEDED],
|
||||||
target=ApplicationStatus.WITHDRAWN,
|
target=DomainRequestStatus.WITHDRAWN,
|
||||||
)
|
)
|
||||||
def withdraw(self):
|
def withdraw(self):
|
||||||
"""Withdraw an application that has been submitted."""
|
"""Withdraw a domain request that has been submitted."""
|
||||||
|
|
||||||
self._send_status_update_email(
|
self._send_status_update_email(
|
||||||
"withdraw",
|
"withdraw",
|
||||||
|
@ -822,17 +822,17 @@ class DomainApplication(TimeStampedModel):
|
||||||
|
|
||||||
@transition(
|
@transition(
|
||||||
field="status",
|
field="status",
|
||||||
source=[ApplicationStatus.IN_REVIEW, ApplicationStatus.ACTION_NEEDED, ApplicationStatus.APPROVED],
|
source=[DomainRequestStatus.IN_REVIEW, DomainRequestStatus.ACTION_NEEDED, DomainRequestStatus.APPROVED],
|
||||||
target=ApplicationStatus.REJECTED,
|
target=DomainRequestStatus.REJECTED,
|
||||||
conditions=[domain_is_not_active],
|
conditions=[domain_is_not_active],
|
||||||
)
|
)
|
||||||
def reject(self):
|
def reject(self):
|
||||||
"""Reject an application that has been submitted.
|
"""Reject a domain request that has been submitted.
|
||||||
|
|
||||||
As side effects this will delete the domain and domain_information
|
As side effects this will delete the domain and domain_information
|
||||||
(will cascade), and send an email notification."""
|
(will cascade), and send an email notification."""
|
||||||
|
|
||||||
if self.status == self.ApplicationStatus.APPROVED:
|
if self.status == self.DomainRequestStatus.APPROVED:
|
||||||
self.delete_and_clean_up_domain("reject")
|
self.delete_and_clean_up_domain("reject")
|
||||||
|
|
||||||
self._send_status_update_email(
|
self._send_status_update_email(
|
||||||
|
@ -844,24 +844,24 @@ class DomainApplication(TimeStampedModel):
|
||||||
@transition(
|
@transition(
|
||||||
field="status",
|
field="status",
|
||||||
source=[
|
source=[
|
||||||
ApplicationStatus.IN_REVIEW,
|
DomainRequestStatus.IN_REVIEW,
|
||||||
ApplicationStatus.ACTION_NEEDED,
|
DomainRequestStatus.ACTION_NEEDED,
|
||||||
ApplicationStatus.APPROVED,
|
DomainRequestStatus.APPROVED,
|
||||||
ApplicationStatus.REJECTED,
|
DomainRequestStatus.REJECTED,
|
||||||
],
|
],
|
||||||
target=ApplicationStatus.INELIGIBLE,
|
target=DomainRequestStatus.INELIGIBLE,
|
||||||
conditions=[domain_is_not_active],
|
conditions=[domain_is_not_active],
|
||||||
)
|
)
|
||||||
def reject_with_prejudice(self):
|
def reject_with_prejudice(self):
|
||||||
"""The applicant is a bad actor, reject with prejudice.
|
"""The applicant is a bad actor, reject with prejudice.
|
||||||
|
|
||||||
No email As a side effect, but we block the applicant from editing
|
No email As a side effect, but we block the applicant from editing
|
||||||
any existing domains/applications and from submitting new aplications.
|
any existing domains/domain requests and from submitting new aplications.
|
||||||
We do this by setting an ineligible status on the user, which the
|
We do this by setting an ineligible status on the user, which the
|
||||||
permissions classes test against. This will also delete the domain
|
permissions classes test against. This will also delete the domain
|
||||||
and domain_information (will cascade) when they exist."""
|
and domain_information (will cascade) when they exist."""
|
||||||
|
|
||||||
if self.status == self.ApplicationStatus.APPROVED:
|
if self.status == self.DomainRequestStatus.APPROVED:
|
||||||
self.delete_and_clean_up_domain("reject_with_prejudice")
|
self.delete_and_clean_up_domain("reject_with_prejudice")
|
||||||
|
|
||||||
self.creator.restrict_user()
|
self.creator.restrict_user()
|
||||||
|
@ -869,18 +869,18 @@ class DomainApplication(TimeStampedModel):
|
||||||
# ## Form policies ###
|
# ## Form policies ###
|
||||||
#
|
#
|
||||||
# These methods control what questions need to be answered by applicants
|
# These methods control what questions need to be answered by applicants
|
||||||
# during the application flow. They are policies about the application so
|
# during the domain request flow. They are policies about the domain request so
|
||||||
# they appear here.
|
# they appear here.
|
||||||
|
|
||||||
def show_organization_federal(self) -> bool:
|
def show_organization_federal(self) -> bool:
|
||||||
"""Show this step if the answer to the first question was "federal"."""
|
"""Show this step if the answer to the first question was "federal"."""
|
||||||
user_choice = self.organization_type
|
user_choice = self.organization_type
|
||||||
return user_choice == DomainApplication.OrganizationChoices.FEDERAL
|
return user_choice == DomainRequest.OrganizationChoices.FEDERAL
|
||||||
|
|
||||||
def show_tribal_government(self) -> bool:
|
def show_tribal_government(self) -> bool:
|
||||||
"""Show this step if the answer to the first question was "tribal"."""
|
"""Show this step if the answer to the first question was "tribal"."""
|
||||||
user_choice = self.organization_type
|
user_choice = self.organization_type
|
||||||
return user_choice == DomainApplication.OrganizationChoices.TRIBAL
|
return user_choice == DomainRequest.OrganizationChoices.TRIBAL
|
||||||
|
|
||||||
def show_organization_election(self) -> bool:
|
def show_organization_election(self) -> bool:
|
||||||
"""Show this step if the answer to the first question implies it.
|
"""Show this step if the answer to the first question implies it.
|
||||||
|
@ -890,9 +890,9 @@ class DomainApplication(TimeStampedModel):
|
||||||
"""
|
"""
|
||||||
user_choice = self.organization_type
|
user_choice = self.organization_type
|
||||||
excluded = [
|
excluded = [
|
||||||
DomainApplication.OrganizationChoices.FEDERAL,
|
DomainRequest.OrganizationChoices.FEDERAL,
|
||||||
DomainApplication.OrganizationChoices.INTERSTATE,
|
DomainRequest.OrganizationChoices.INTERSTATE,
|
||||||
DomainApplication.OrganizationChoices.SCHOOL_DISTRICT,
|
DomainRequest.OrganizationChoices.SCHOOL_DISTRICT,
|
||||||
]
|
]
|
||||||
return bool(user_choice and user_choice not in excluded)
|
return bool(user_choice and user_choice not in excluded)
|
||||||
|
|
||||||
|
@ -900,27 +900,27 @@ class DomainApplication(TimeStampedModel):
|
||||||
"""Show this step if this is a special district or interstate."""
|
"""Show this step if this is a special district or interstate."""
|
||||||
user_choice = self.organization_type
|
user_choice = self.organization_type
|
||||||
return user_choice in [
|
return user_choice in [
|
||||||
DomainApplication.OrganizationChoices.SPECIAL_DISTRICT,
|
DomainRequest.OrganizationChoices.SPECIAL_DISTRICT,
|
||||||
DomainApplication.OrganizationChoices.INTERSTATE,
|
DomainRequest.OrganizationChoices.INTERSTATE,
|
||||||
]
|
]
|
||||||
|
|
||||||
def has_rationale(self) -> bool:
|
def has_rationale(self) -> bool:
|
||||||
"""Does this application have no_other_contacts_rationale?"""
|
"""Does this domain request have no_other_contacts_rationale?"""
|
||||||
return bool(self.no_other_contacts_rationale)
|
return bool(self.no_other_contacts_rationale)
|
||||||
|
|
||||||
def has_other_contacts(self) -> bool:
|
def has_other_contacts(self) -> bool:
|
||||||
"""Does this application have other contacts listed?"""
|
"""Does this domain request have other contacts listed?"""
|
||||||
return self.other_contacts.exists()
|
return self.other_contacts.exists()
|
||||||
|
|
||||||
def is_federal(self) -> Union[bool, None]:
|
def is_federal(self) -> Union[bool, None]:
|
||||||
"""Is this application for a federal agency?
|
"""Is this domain request for a federal agency?
|
||||||
|
|
||||||
organization_type can be both null and blank,
|
organization_type can be both null and blank,
|
||||||
"""
|
"""
|
||||||
if not self.organization_type:
|
if not self.organization_type:
|
||||||
# organization_type is either blank or None, can't answer
|
# organization_type is either blank or None, can't answer
|
||||||
return None
|
return None
|
||||||
if self.organization_type == DomainApplication.OrganizationChoices.FEDERAL:
|
if self.organization_type == DomainRequest.OrganizationChoices.FEDERAL:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
|
@ -8,8 +8,6 @@ from registrar.utility.enums import DefaultEmail
|
||||||
|
|
||||||
from .utility.time_stamped_model import TimeStampedModel
|
from .utility.time_stamped_model import TimeStampedModel
|
||||||
|
|
||||||
from phonenumber_field.modelfields import PhoneNumberField # type: ignore
|
|
||||||
|
|
||||||
|
|
||||||
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."""
|
||||||
|
@ -71,8 +69,8 @@ class PublicContact(TimeStampedModel):
|
||||||
pc = models.CharField(null=False, help_text="Contact's postal code")
|
pc = models.CharField(null=False, help_text="Contact's postal code")
|
||||||
cc = models.CharField(null=False, help_text="Contact's country code")
|
cc = models.CharField(null=False, help_text="Contact's country code")
|
||||||
email = models.EmailField(null=False, help_text="Contact's email address")
|
email = models.EmailField(null=False, help_text="Contact's email address")
|
||||||
voice = PhoneNumberField(null=False, help_text="Contact's phone number. Must be in ITU.E164.2005 format")
|
voice = models.CharField(null=False, help_text="Contact's phone number. Must be in ITU.E164.2005 format")
|
||||||
fax = PhoneNumberField(
|
fax = models.CharField(
|
||||||
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.",
|
||||||
)
|
)
|
||||||
|
|
|
@ -33,8 +33,8 @@ class UserGroup(Group):
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"app_label": "registrar",
|
"app_label": "registrar",
|
||||||
"model": "domainapplication",
|
"model": "domainrequest",
|
||||||
"permissions": ["change_domainapplication"],
|
"permissions": ["change_domainrequest"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"app_label": "registrar",
|
"app_label": "registrar",
|
||||||
|
|
|
@ -184,7 +184,7 @@ class DomainHelper:
|
||||||
model_1_fields = set(field.name for field in model_1._meta.get_fields() if field.name != "id")
|
model_1_fields = set(field.name for field in model_1._meta.get_fields() if field.name != "id")
|
||||||
model_2_fields = set(field.name for field in model_2._meta.get_fields() if field.name != "id")
|
model_2_fields = set(field.name for field in model_2._meta.get_fields() if field.name != "id")
|
||||||
|
|
||||||
# Get the fields that exist on both DomainApplication and DomainInformation
|
# Get the fields that exist on both DomainRequest and DomainInformation
|
||||||
common_fields = model_1_fields & model_2_fields
|
common_fields = model_1_fields & model_2_fields
|
||||||
|
|
||||||
return common_fields
|
return common_fields
|
||||||
|
|
|
@ -4,7 +4,7 @@ from .utility.time_stamped_model import TimeStampedModel
|
||||||
|
|
||||||
|
|
||||||
class Website(TimeStampedModel):
|
class Website(TimeStampedModel):
|
||||||
"""Keep domain names in their own table so that applications can refer to
|
"""Keep domain names in their own table so that domain requests can refer to
|
||||||
many of them."""
|
many of them."""
|
||||||
|
|
||||||
# domain names have strictly limited lengths, 255 characters is more than
|
# domain names have strictly limited lengths, 255 characters is more than
|
||||||
|
|
|
@ -1,126 +0,0 @@
|
||||||
{% extends 'base.html' %}
|
|
||||||
|
|
||||||
{% load custom_filters %}
|
|
||||||
|
|
||||||
{% block title %}Domain request status | {{ domainapplication.requested_domain.name }} | {% endblock %}
|
|
||||||
{% load static url_helpers %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<main id="main-content" class="grid-container">
|
|
||||||
<div class="grid-col desktop:grid-offset-2 desktop:grid-col-8">
|
|
||||||
<a href="{% url 'home' %}" class="breadcrumb__back">
|
|
||||||
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img">
|
|
||||||
<use xlink:href="{% static 'img/sprite.svg' %}#arrow_back"></use>
|
|
||||||
</svg>
|
|
||||||
|
|
||||||
<p class="margin-left-05 margin-top-0 margin-bottom-0 line-height-sans-1">
|
|
||||||
Back to manage your domains
|
|
||||||
</p>
|
|
||||||
</a>
|
|
||||||
<h1>Domain request for {{ domainapplication.requested_domain.name }}</h1>
|
|
||||||
<div
|
|
||||||
class="usa-summary-box dotgov-status-box margin-top-3 padding-left-2"
|
|
||||||
role="region"
|
|
||||||
aria-labelledby="summary-box-key-information"
|
|
||||||
>
|
|
||||||
<div class="usa-summary-box__body">
|
|
||||||
<p class="usa-summary-box__heading font-sans-md margin-bottom-0"
|
|
||||||
id="summary-box-key-information"
|
|
||||||
>
|
|
||||||
<span class="text-bold text-primary-darker">
|
|
||||||
Status:
|
|
||||||
</span>
|
|
||||||
{% if domainapplication.status == 'approved' %} Approved
|
|
||||||
{% elif domainapplication.status == 'in review' %} In review
|
|
||||||
{% elif domainapplication.status == 'rejected' %} Rejected
|
|
||||||
{% elif domainapplication.status == 'submitted' %} Submitted
|
|
||||||
{% elif domainapplication.status == 'ineligible' %} Ineligible
|
|
||||||
{% else %}ERROR Please contact technical support/dev
|
|
||||||
{% endif %}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<br>
|
|
||||||
<p> <b class="review__step__name">Last updated:</b> {{domainapplication.updated_at|date:"F j, Y"}}<br>
|
|
||||||
<b class="review__step__name">Request #:</b> {{domainapplication.id}}</p>
|
|
||||||
<p>{% include "includes/domain_application.html" %}</p>
|
|
||||||
<p><a href="{% url 'application-withdraw-confirmation' pk=domainapplication.id %}" class="usa-button usa-button--outline withdraw_outline">
|
|
||||||
Withdraw request</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid-col desktop:grid-offset-2 maxw-tablet">
|
|
||||||
<h2 class="text-primary-darker"> Summary of your domain request </h2>
|
|
||||||
{% with heading_level='h3' %}
|
|
||||||
{% with org_type=domainapplication.get_organization_type_display %}
|
|
||||||
{% include "includes/summary_item.html" with title='Type of organization' value=org_type heading_level=heading_level %}
|
|
||||||
{% endwith %}
|
|
||||||
|
|
||||||
{% if domainapplication.tribe_name %}
|
|
||||||
{% include "includes/summary_item.html" with title='Tribal government' value=domainapplication.tribe_name heading_level=heading_level %}
|
|
||||||
|
|
||||||
{% if domainapplication.federally_recognized_tribe %}
|
|
||||||
<p>Federally-recognized tribe</p>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if domainapplication.state_recognized_tribe %}
|
|
||||||
<p>State-recognized tribe</p>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if domainapplication.get_federal_type_display %}
|
|
||||||
{% include "includes/summary_item.html" with title='Federal government branch' value=domainapplication.get_federal_type_display heading_level=heading_level %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if domainapplication.is_election_board %}
|
|
||||||
{% with value=domainapplication.is_election_board|yesno:"Yes,No,Incomplete" %}
|
|
||||||
{% include "includes/summary_item.html" with title='Election office' value=value heading_level=heading_level %}
|
|
||||||
{% endwith %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if domainapplication.organization_name %}
|
|
||||||
{% include "includes/summary_item.html" with title='Organization name and mailing address' value=domainapplication address='true' heading_level=heading_level %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if domainapplication.about_your_organization %}
|
|
||||||
{% include "includes/summary_item.html" with title='About your organization' value=domainapplication.about_your_organization heading_level=heading_level %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if domainapplication.authorizing_official %}
|
|
||||||
{% include "includes/summary_item.html" with title='Authorizing official' value=domainapplication.authorizing_official contact='true' heading_level=heading_level %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if domainapplication.current_websites.all %}
|
|
||||||
{% include "includes/summary_item.html" with title='Current websites' value=domainapplication.current_websites.all list='true' heading_level=heading_level %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if domainapplication.requested_domain %}
|
|
||||||
{% include "includes/summary_item.html" with title='.gov domain' value=domainapplication.requested_domain heading_level=heading_level %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if domainapplication.alternative_domains.all %}
|
|
||||||
{% include "includes/summary_item.html" with title='Alternative domains' value=domainapplication.alternative_domains.all list='true' heading_level=heading_level %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if domainapplication.purpose %}
|
|
||||||
{% include "includes/summary_item.html" with title='Purpose of your domain' value=domainapplication.purpose heading_level=heading_level %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if domainapplication.submitter %}
|
|
||||||
{% include "includes/summary_item.html" with title='Your contact information' value=domainapplication.submitter contact='true' heading_level=heading_level %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if domainapplication.other_contacts.all %}
|
|
||||||
{% include "includes/summary_item.html" with title='Other employees from your organization' value=domainapplication.other_contacts.all contact='true' list='true' heading_level=heading_level %}
|
|
||||||
{% else %}
|
|
||||||
{% include "includes/summary_item.html" with title='Other employees from your organization' value=domainapplication.no_other_contacts_rationale heading_level=heading_level %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% include "includes/summary_item.html" with title='Anything else?' value=domainapplication.anything_else|default:"No" heading_level=heading_level %}
|
|
||||||
|
|
||||||
{% endwith %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</main>
|
|
||||||
{% endblock %}
|
|
|
@ -1,21 +0,0 @@
|
||||||
{% extends 'base.html' %}
|
|
||||||
|
|
||||||
{% block title %}Withdraw request for {{ domainapplication.requested_domain.name }} | {% endblock %}
|
|
||||||
{% load static url_helpers %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="grid-container">
|
|
||||||
<div class="grid-col desktop:grid-offset-2 desktop:grid-col-8">
|
|
||||||
|
|
||||||
|
|
||||||
<h1>Withdraw request for {{ domainapplication.requested_domain.name }}?</h1>
|
|
||||||
|
|
||||||
<p>If you withdraw your request, we won't review it. Once you withdraw your request, you can edit it and submit it again. </p>
|
|
||||||
|
|
||||||
<p><a href="{% url 'application-withdrawn' domainapplication.id %}" class="usa-button withdraw">Withdraw request</a>
|
|
||||||
<a href="{% url 'application-status' domainapplication.id %}">Cancel</a></p>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends 'application_form.html' %}
|
{% extends 'domain_request_form.html' %}
|
||||||
{% load field_helpers %}
|
{% load field_helpers %}
|
||||||
|
|
||||||
{% block form_instructions %}
|
{% block form_instructions %}
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends 'application_form.html' %}
|
{% extends 'domain_request_form.html' %}
|
||||||
{% load field_helpers %}
|
{% load field_helpers %}
|
||||||
|
|
||||||
{% block form_instructions %}
|
{% block form_instructions %}
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends 'application_form.html' %}
|
{% extends 'domain_request_form.html' %}
|
||||||
{% load field_helpers url_helpers %}
|
{% load field_helpers url_helpers %}
|
||||||
|
|
||||||
{% block form_instructions %}
|
{% block form_instructions %}
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends 'application_form.html' %}
|
{% extends 'domain_request_form.html' %}
|
||||||
{% load static field_helpers %}
|
{% load static field_helpers %}
|
||||||
|
|
||||||
{% block form_instructions %}
|
{% block form_instructions %}
|
|
@ -19,15 +19,16 @@
|
||||||
|
|
||||||
<h2>Next steps in this process</h2>
|
<h2>Next steps in this process</h2>
|
||||||
|
|
||||||
<p> We’ll review your request. This usually takes 20 business days. During
|
<p> We’ll review your request. This review period can take 30 business days. Due to the volume of requests, the wait time is longer than usual. We appreciate your patience.</p>
|
||||||
this review we’ll verify that:</p>
|
|
||||||
|
<p>During our review we’ll verify that:</p>
|
||||||
<ul class="usa-list">
|
<ul class="usa-list">
|
||||||
<li>Your organization is eligible for a .gov domain.</li>
|
<li>Your organization is eligible for a .gov domain.</li>
|
||||||
<li>You work at the organization and/or can make requests on its behalf.</li>
|
<li>You work at the organization and/or can make requests on its behalf.</li>
|
||||||
<li>Your requested domain meets our naming requirements.</li>
|
<li>Your requested domain meets our naming requirements.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<p> We’ll email you if we have questions and when we complete our review. You can <a href="{% url 'home' %}">check the status</a>
|
<p> We’ll email you if we have questions. We’ll also email you as soon as we complete our review. You can <a href="{% url 'home' %}">check the status</a>
|
||||||
of your request at any time on the registrar homepage.</p>
|
of your request at any time on the registrar homepage.</p>
|
||||||
|
|
||||||
<p> <a class="usa-link" rel="noopener noreferrer" target="_blank" href="{% public_site_url 'contact' %}">Contact us if you need help during this process</a>.</p>
|
<p> <a class="usa-link" rel="noopener noreferrer" target="_blank" href="{% public_site_url 'contact' %}">Contact us if you need help during this process</a>.</p>
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends 'application_form.html' %}
|
{% extends 'domain_request_form.html' %}
|
||||||
{% load static field_helpers url_helpers %}
|
{% load static field_helpers url_helpers %}
|
||||||
|
|
||||||
{% block form_instructions %}
|
{% block form_instructions %}
|
|
@ -6,12 +6,12 @@
|
||||||
<div class="grid-container">
|
<div class="grid-container">
|
||||||
<div class="grid-row grid-gap">
|
<div class="grid-row grid-gap">
|
||||||
<div class="tablet:grid-col-3">
|
<div class="tablet:grid-col-3">
|
||||||
{% include 'application_sidebar.html' %}
|
{% include 'domain_request_sidebar.html' %}
|
||||||
</div>
|
</div>
|
||||||
<div class="tablet:grid-col-9">
|
<div class="tablet:grid-col-9">
|
||||||
<main id="main-content" class="grid-container register-form-step">
|
<main id="main-content" class="grid-container register-form-step">
|
||||||
{% if steps.prev %}
|
{% if steps.prev %}
|
||||||
<a href="{% namespaced_url 'application' steps.prev %}" class="breadcrumb__back">
|
<a href="{% namespaced_url 'domain-request' steps.prev %}" class="breadcrumb__back">
|
||||||
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24" height="24">
|
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24" height="24">
|
||||||
<use xlink:href="{%static 'img/sprite.svg'%}#arrow_back"></use>
|
<use xlink:href="{%static 'img/sprite.svg'%}#arrow_back"></use>
|
||||||
</svg><span class="margin-left-05">Previous step</span>
|
</svg><span class="margin-left-05">Previous step</span>
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends 'application_form.html' %}
|
{% extends 'domain_request_form.html' %}
|
||||||
{% load field_helpers url_helpers %}
|
{% load field_helpers url_helpers %}
|
||||||
|
|
||||||
{% block form_instructions %}
|
{% block form_instructions %}
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends 'application_form.html' %}
|
{% extends 'domain_request_form.html' %}
|
||||||
{% load field_helpers %}
|
{% load field_helpers %}
|
||||||
|
|
||||||
{% block form_instructions %}
|
{% block form_instructions %}
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends 'application_form.html' %}
|
{% extends 'domain_request_form.html' %}
|
||||||
{% load field_helpers %}
|
{% load field_helpers %}
|
||||||
|
|
||||||
{% block form_instructions %}
|
{% block form_instructions %}
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends 'application_form.html' %}
|
{% extends 'domain_request_form.html' %}
|
||||||
{% load field_helpers %}
|
{% load field_helpers %}
|
||||||
|
|
||||||
{% block form_instructions %}
|
{% block form_instructions %}
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends 'application_form.html' %}
|
{% extends 'domain_request_form.html' %}
|
||||||
{% load static field_helpers %}
|
{% load static field_helpers %}
|
||||||
|
|
||||||
{% block form_instructions %}
|
{% block form_instructions %}
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends 'application_form.html' %}
|
{% extends 'domain_request_form.html' %}
|
||||||
{% load field_helpers url_helpers %}
|
{% load field_helpers url_helpers %}
|
||||||
|
|
||||||
{% block form_instructions %}
|
{% block form_instructions %}
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends 'application_form.html' %}
|
{% extends 'domain_request_form.html' %}
|
||||||
{% load field_helpers %}
|
{% load field_helpers %}
|
||||||
|
|
||||||
{% block form_instructions %}
|
{% block form_instructions %}
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends 'application_form.html' %}
|
{% extends 'domain_request_form.html' %}
|
||||||
{% load static url_helpers %}
|
{% load static url_helpers %}
|
||||||
{% load custom_filters %}
|
{% load custom_filters %}
|
||||||
|
|
||||||
|
@ -23,98 +23,98 @@
|
||||||
<section class="summary-item margin-top-3">
|
<section class="summary-item margin-top-3">
|
||||||
|
|
||||||
{% if step == Step.ORGANIZATION_TYPE %}
|
{% if step == Step.ORGANIZATION_TYPE %}
|
||||||
{% namespaced_url 'application' step as application_url %}
|
{% namespaced_url 'domain-request' step as domain_request_url %}
|
||||||
{% if application.organization_type is not None %}
|
{% if domain_request.organization_type is not None %}
|
||||||
{% with title=form_titles|get_item:step value=application.get_organization_type_display|default:"Incomplete" %}
|
{% with title=form_titles|get_item:step value=domain_request.get_organization_type_display|default:"Incomplete" %}
|
||||||
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=application_url %}
|
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=domain_request_url %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% with title=form_titles|get_item:step value="Incomplete" %}
|
{% with title=form_titles|get_item:step value="Incomplete" %}
|
||||||
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=application_url %}
|
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=domain_request_url %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if step == Step.TRIBAL_GOVERNMENT %}
|
{% if step == Step.TRIBAL_GOVERNMENT %}
|
||||||
{% namespaced_url 'application' step as application_url %}
|
{% namespaced_url 'domain-request' step as domain_request_url %}
|
||||||
{% with title=form_titles|get_item:step value=application.tribe_name|default:"Incomplete" %}
|
{% with title=form_titles|get_item:step value=domain_request.tribe_name|default:"Incomplete" %}
|
||||||
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=application_url %}
|
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=domain_request_url %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% if application.federally_recognized_tribe %}<p>Federally-recognized tribe</p>{% endif %}
|
{% if domain_request.federally_recognized_tribe %}<p>Federally-recognized tribe</p>{% endif %}
|
||||||
{% if application.state_recognized_tribe %}<p>State-recognized tribe</p>{% endif %}
|
{% if domain_request.state_recognized_tribe %}<p>State-recognized tribe</p>{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
{% if step == Step.ORGANIZATION_FEDERAL %}
|
{% if step == Step.ORGANIZATION_FEDERAL %}
|
||||||
{% namespaced_url 'application' step as application_url %}
|
{% namespaced_url 'domain-request' step as domain_request_url %}
|
||||||
{% with title=form_titles|get_item:step value=application.get_federal_type_display|default:"Incomplete" %}
|
{% with title=form_titles|get_item:step value=domain_request.get_federal_type_display|default:"Incomplete" %}
|
||||||
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=application_url %}
|
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=domain_request_url %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if step == Step.ORGANIZATION_ELECTION %}
|
{% if step == Step.ORGANIZATION_ELECTION %}
|
||||||
{% namespaced_url 'application' step as application_url %}
|
{% namespaced_url 'domain-request' step as domain_request_url %}
|
||||||
{% with title=form_titles|get_item:step value=application.is_election_board|yesno:"Yes,No,Incomplete" %}
|
{% with title=form_titles|get_item:step value=domain_request.is_election_board|yesno:"Yes,No,Incomplete" %}
|
||||||
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=application_url %}
|
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=domain_request_url %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if step == Step.ORGANIZATION_CONTACT %}
|
{% if step == Step.ORGANIZATION_CONTACT %}
|
||||||
{% namespaced_url 'application' step as application_url %}
|
{% namespaced_url 'domain-request' step as domain_request_url %}
|
||||||
{% if application.organization_name %}
|
{% if domain_request.organization_name %}
|
||||||
{% with title=form_titles|get_item:step value=application %}
|
{% with title=form_titles|get_item:step value=domain_request %}
|
||||||
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=application_url address='true' %}
|
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=domain_request_url address='true' %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% with title=form_titles|get_item:step value='Incomplete' %}
|
{% with title=form_titles|get_item:step value='Incomplete' %}
|
||||||
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=application_url %}
|
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=domain_request_url %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if step == Step.ABOUT_YOUR_ORGANIZATION %}
|
{% if step == Step.ABOUT_YOUR_ORGANIZATION %}
|
||||||
{% namespaced_url 'application' step as application_url %}
|
{% namespaced_url 'domain-request' step as domain_request_url %}
|
||||||
{% with title=form_titles|get_item:step value=application.about_your_organization|default:"Incomplete" %}
|
{% with title=form_titles|get_item:step value=domain_request.about_your_organization|default:"Incomplete" %}
|
||||||
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=application_url %}
|
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=domain_request_url %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if step == Step.AUTHORIZING_OFFICIAL %}
|
{% if step == Step.AUTHORIZING_OFFICIAL %}
|
||||||
{% namespaced_url 'application' step as application_url %}
|
{% namespaced_url 'domain-request' step as domain_request_url %}
|
||||||
{% if application.authorizing_official is not None %}
|
{% if domain_request.authorizing_official is not None %}
|
||||||
{% with title=form_titles|get_item:step value=application.authorizing_official %}
|
{% with title=form_titles|get_item:step value=domain_request.authorizing_official %}
|
||||||
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=application_url contact='true' %}
|
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=domain_request_url contact='true' %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% with title=form_titles|get_item:step value="Incomplete" %}
|
{% with title=form_titles|get_item:step value="Incomplete" %}
|
||||||
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=application_url %}
|
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=domain_request_url %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if step == Step.CURRENT_SITES %}
|
{% if step == Step.CURRENT_SITES %}
|
||||||
{% namespaced_url 'application' step as application_url %}
|
{% namespaced_url 'domain-request' step as domain_request_url %}
|
||||||
{% if application.current_websites.all %}
|
{% if domain_request.current_websites.all %}
|
||||||
{% with title=form_titles|get_item:step value=application.current_websites.all %}
|
{% with title=form_titles|get_item:step value=domain_request.current_websites.all %}
|
||||||
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=application_url list='true' %}
|
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=domain_request_url list='true' %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% with title=form_titles|get_item:step value='None' %}
|
{% with title=form_titles|get_item:step value='None' %}
|
||||||
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=application_url %}
|
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=domain_request_url %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if step == Step.DOTGOV_DOMAIN %}
|
{% if step == Step.DOTGOV_DOMAIN %}
|
||||||
{% namespaced_url 'application' step as application_url %}
|
{% namespaced_url 'domain-request' step as domain_request_url %}
|
||||||
{% with title=form_titles|get_item:step value=application.requested_domain.name|default:"Incomplete" %}
|
{% with title=form_titles|get_item:step value=domain_request.requested_domain.name|default:"Incomplete" %}
|
||||||
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=application_url %}
|
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=domain_request_url %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
||||||
{% if application.alternative_domains.all %}
|
{% if domain_request.alternative_domains.all %}
|
||||||
<h3 class="register-form-review-header">Alternative domains</h3>
|
<h3 class="register-form-review-header">Alternative domains</h3>
|
||||||
<ul class="usa-list usa-list--unstyled margin-top-0">
|
<ul class="usa-list usa-list--unstyled margin-top-0">
|
||||||
{% for site in application.alternative_domains.all %}
|
{% for site in domain_request.alternative_domains.all %}
|
||||||
<li>{{ site.website }}</li>
|
<li>{{ site.website }}</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -122,51 +122,51 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if step == Step.PURPOSE %}
|
{% if step == Step.PURPOSE %}
|
||||||
{% namespaced_url 'application' step as application_url %}
|
{% namespaced_url 'domain-request' step as domain_request_url %}
|
||||||
{% with title=form_titles|get_item:step value=application.purpose|default:"Incomplete" %}
|
{% with title=form_titles|get_item:step value=domain_request.purpose|default:"Incomplete" %}
|
||||||
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=application_url %}
|
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=domain_request_url %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if step == Step.YOUR_CONTACT %}
|
{% if step == Step.YOUR_CONTACT %}
|
||||||
{% namespaced_url 'application' step as application_url %}
|
{% namespaced_url 'domain-request' step as domain_request_url %}
|
||||||
{% if application.submitter is not None %}
|
{% if domain_request.submitter is not None %}
|
||||||
{% with title=form_titles|get_item:step value=application.submitter %}
|
{% with title=form_titles|get_item:step value=domain_request.submitter %}
|
||||||
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=application_url contact='true' %}
|
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=domain_request_url contact='true' %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% with title=form_titles|get_item:step value="Incomplete" %}
|
{% with title=form_titles|get_item:step value="Incomplete" %}
|
||||||
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=application_url %}
|
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=domain_request_url %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if step == Step.OTHER_CONTACTS %}
|
{% if step == Step.OTHER_CONTACTS %}
|
||||||
{% namespaced_url 'application' step as application_url %}
|
{% namespaced_url 'domain-request' step as domain_request_url %}
|
||||||
{% if application.other_contacts.all %}
|
{% if domain_request.other_contacts.all %}
|
||||||
{% with title=form_titles|get_item:step value=application.other_contacts.all %}
|
{% with title=form_titles|get_item:step value=domain_request.other_contacts.all %}
|
||||||
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=application_url contact='true' list='true' %}
|
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=domain_request_url contact='true' list='true' %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% with title=form_titles|get_item:step value=application.no_other_contacts_rationale|default:"Incomplete" %}
|
{% with title=form_titles|get_item:step value=domain_request.no_other_contacts_rationale|default:"Incomplete" %}
|
||||||
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=application_url %}
|
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=domain_request_url %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
{% if step == Step.ANYTHING_ELSE %}
|
{% if step == Step.ANYTHING_ELSE %}
|
||||||
{% namespaced_url 'application' step as application_url %}
|
{% namespaced_url 'domain-request' step as domain_request_url %}
|
||||||
{% with title=form_titles|get_item:step value=application.anything_else|default:"No" %}
|
{% with title=form_titles|get_item:step value=domain_request.anything_else|default:"No" %}
|
||||||
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=application_url %}
|
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=domain_request_url %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
{% if step == Step.REQUIREMENTS %}
|
{% if step == Step.REQUIREMENTS %}
|
||||||
{% namespaced_url 'application' step as application_url %}
|
{% namespaced_url 'domain-request' step as domain_request_url %}
|
||||||
{% with title=form_titles|get_item:step value=application.is_policy_acknowledged|yesno:"I agree.,I do not agree.,I do not agree." %}
|
{% with title=form_titles|get_item:step value=domain_request.is_policy_acknowledged|yesno:"I agree.,I do not agree.,I do not agree." %}
|
||||||
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=application_url %}
|
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=domain_request_url %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
</svg>
|
</svg>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{% namespaced_url 'application' this_step %}"
|
<a href="{% namespaced_url 'domain-request' this_step %}"
|
||||||
{% if this_step == steps.current %}
|
{% if this_step == steps.current %}
|
||||||
class="usa-current"
|
class="usa-current"
|
||||||
{% else %}
|
{% else %}
|
126
src/registrar/templates/domain_request_status.html
Normal file
126
src/registrar/templates/domain_request_status.html
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% load custom_filters %}
|
||||||
|
|
||||||
|
{% block title %}Domain request status | {{ DomainRequest.requested_domain.name }} | {% endblock %}
|
||||||
|
{% load static url_helpers %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<main id="main-content" class="grid-container">
|
||||||
|
<div class="grid-col desktop:grid-offset-2 desktop:grid-col-8">
|
||||||
|
<a href="{% url 'home' %}" class="breadcrumb__back">
|
||||||
|
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img">
|
||||||
|
<use xlink:href="{% static 'img/sprite.svg' %}#arrow_back"></use>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<p class="margin-left-05 margin-top-0 margin-bottom-0 line-height-sans-1">
|
||||||
|
Back to manage your domains
|
||||||
|
</p>
|
||||||
|
</a>
|
||||||
|
<h1>Domain request for {{ DomainRequest.requested_domain.name }}</h1>
|
||||||
|
<div
|
||||||
|
class="usa-summary-box dotgov-status-box margin-top-3 padding-left-2"
|
||||||
|
role="region"
|
||||||
|
aria-labelledby="summary-box-key-information"
|
||||||
|
>
|
||||||
|
<div class="usa-summary-box__body">
|
||||||
|
<p class="usa-summary-box__heading font-sans-md margin-bottom-0"
|
||||||
|
id="summary-box-key-information"
|
||||||
|
>
|
||||||
|
<span class="text-bold text-primary-darker">
|
||||||
|
Status:
|
||||||
|
</span>
|
||||||
|
{% if DomainRequest.status == 'approved' %} Approved
|
||||||
|
{% elif DomainRequest.status == 'in review' %} In review
|
||||||
|
{% elif DomainRequest.status == 'rejected' %} Rejected
|
||||||
|
{% elif DomainRequest.status == 'submitted' %} Submitted
|
||||||
|
{% elif DomainRequest.status == 'ineligible' %} Ineligible
|
||||||
|
{% else %}ERROR Please contact technical support/dev
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<p> <b class="review__step__name">Last updated:</b> {{DomainRequest.updated_at|date:"F j, Y"}}<br>
|
||||||
|
<b class="review__step__name">Request #:</b> {{DomainRequest.id}}</p>
|
||||||
|
<p>{% include "includes/domain_request.html" %}</p>
|
||||||
|
<p><a href="{% url 'domain-request-withdraw-confirmation' pk=DomainRequest.id %}" class="usa-button usa-button--outline withdraw_outline">
|
||||||
|
Withdraw request</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid-col desktop:grid-offset-2 maxw-tablet">
|
||||||
|
<h2 class="text-primary-darker"> Summary of your domain request </h2>
|
||||||
|
{% with heading_level='h3' %}
|
||||||
|
{% with org_type=DomainRequest.get_organization_type_display %}
|
||||||
|
{% include "includes/summary_item.html" with title='Type of organization' value=org_type heading_level=heading_level %}
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
|
{% if DomainRequest.tribe_name %}
|
||||||
|
{% include "includes/summary_item.html" with title='Tribal government' value=DomainRequest.tribe_name heading_level=heading_level %}
|
||||||
|
|
||||||
|
{% if DomainRequest.federally_recognized_tribe %}
|
||||||
|
<p>Federally-recognized tribe</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if DomainRequest.state_recognized_tribe %}
|
||||||
|
<p>State-recognized tribe</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if DomainRequest.get_federal_type_display %}
|
||||||
|
{% include "includes/summary_item.html" with title='Federal government branch' value=DomainRequest.get_federal_type_display heading_level=heading_level %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if DomainRequest.is_election_board %}
|
||||||
|
{% with value=DomainRequest.is_election_board|yesno:"Yes,No,Incomplete" %}
|
||||||
|
{% include "includes/summary_item.html" with title='Election office' value=value heading_level=heading_level %}
|
||||||
|
{% endwith %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if DomainRequest.organization_name %}
|
||||||
|
{% include "includes/summary_item.html" with title='Organization name and mailing address' value=DomainRequest address='true' heading_level=heading_level %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if DomainRequest.about_your_organization %}
|
||||||
|
{% include "includes/summary_item.html" with title='About your organization' value=DomainRequest.about_your_organization heading_level=heading_level %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if DomainRequest.authorizing_official %}
|
||||||
|
{% include "includes/summary_item.html" with title='Authorizing official' value=DomainRequest.authorizing_official contact='true' heading_level=heading_level %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if DomainRequest.current_websites.all %}
|
||||||
|
{% include "includes/summary_item.html" with title='Current websites' value=DomainRequest.current_websites.all list='true' heading_level=heading_level %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if DomainRequest.requested_domain %}
|
||||||
|
{% include "includes/summary_item.html" with title='.gov domain' value=DomainRequest.requested_domain heading_level=heading_level %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if DomainRequest.alternative_domains.all %}
|
||||||
|
{% include "includes/summary_item.html" with title='Alternative domains' value=DomainRequest.alternative_domains.all list='true' heading_level=heading_level %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if DomainRequest.purpose %}
|
||||||
|
{% include "includes/summary_item.html" with title='Purpose of your domain' value=DomainRequest.purpose heading_level=heading_level %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if DomainRequest.submitter %}
|
||||||
|
{% include "includes/summary_item.html" with title='Your contact information' value=DomainRequest.submitter contact='true' heading_level=heading_level %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if DomainRequest.other_contacts.all %}
|
||||||
|
{% include "includes/summary_item.html" with title='Other employees from your organization' value=DomainRequest.other_contacts.all contact='true' list='true' heading_level=heading_level %}
|
||||||
|
{% else %}
|
||||||
|
{% include "includes/summary_item.html" with title='Other employees from your organization' value=DomainRequest.no_other_contacts_rationale heading_level=heading_level %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% include "includes/summary_item.html" with title='Anything else?' value=DomainRequest.anything_else|default:"No" heading_level=heading_level %}
|
||||||
|
|
||||||
|
{% endwith %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
{% endblock %}
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends 'application_form.html' %}
|
{% extends 'domain_request_form.html' %}
|
||||||
{% load field_helpers %}
|
{% load field_helpers %}
|
||||||
|
|
||||||
{% block form_instructions %}
|
{% block form_instructions %}
|
|
@ -0,0 +1,21 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}Withdraw request for {{ DomainRequest.requested_domain.name }} | {% endblock %}
|
||||||
|
{% load static url_helpers %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="grid-container">
|
||||||
|
<div class="grid-col desktop:grid-offset-2 desktop:grid-col-8">
|
||||||
|
|
||||||
|
|
||||||
|
<h1>Withdraw request for {{ DomainRequest.requested_domain.name }}?</h1>
|
||||||
|
|
||||||
|
<p>If you withdraw your request, we won't review it. Once you withdraw your request, you can edit it and submit it again. </p>
|
||||||
|
|
||||||
|
<p><a href="{% url 'domain-request-withdrawn' DomainRequest.id %}" class="usa-button withdraw">Withdraw request</a>
|
||||||
|
<a href="{% url 'domain-request-status' DomainRequest.id %}">Cancel</a></p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends 'application_form.html' %}
|
{% extends 'domain_request_form.html' %}
|
||||||
{% load field_helpers %}
|
{% load field_helpers %}
|
||||||
|
|
||||||
{% block form_instructions %}
|
{% block form_instructions %}
|
|
@ -1,10 +1,10 @@
|
||||||
{% 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, {{ application.submitter.first_name }}.
|
Hi, {{ domain_request.submitter.first_name }}.
|
||||||
|
|
||||||
Your .gov domain request has been withdrawn and will not be reviewed by our team.
|
Your .gov domain request has been withdrawn and will not be reviewed by our team.
|
||||||
|
|
||||||
DOMAIN REQUESTED: {{ application.requested_domain.name }}
|
DOMAIN REQUESTED: {{ domain_request.requested_domain.name }}
|
||||||
REQUEST RECEIVED ON: {{ application.submission_date|date }}
|
REQUEST RECEIVED ON: {{ domain_request.submission_date|date }}
|
||||||
STATUS: Withdrawn
|
STATUS: Withdrawn
|
||||||
|
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Update on your .gov request: {{ application.requested_domain.name }}
|
Update on your .gov request: {{ domain_request.requested_domain.name }}
|
||||||
|
|
|
@ -1,55 +0,0 @@
|
||||||
SUMMARY OF YOUR DOMAIN REQUEST
|
|
||||||
|
|
||||||
Type of organization:
|
|
||||||
{{ application.get_organization_type_display }}
|
|
||||||
{% if application.show_organization_federal %}
|
|
||||||
Federal government branch:
|
|
||||||
{{ application.get_federal_type_display }}
|
|
||||||
{% elif application.show_tribal_government %}
|
|
||||||
Tribal government:
|
|
||||||
{{ application.tribe_name|default:"Incomplete" }}{% if application.federally_recognized_tribe %}
|
|
||||||
Federally-recognized tribe
|
|
||||||
{% endif %}{% if application.state_recognized_tribe %}
|
|
||||||
State-recognized tribe
|
|
||||||
{% endif %}{% endif %}{% if application.show_organization_election %}
|
|
||||||
Election office:
|
|
||||||
{{ application.is_election_board|yesno:"Yes,No,Incomplete" }}
|
|
||||||
{% endif %}
|
|
||||||
Organization name and mailing address:
|
|
||||||
{% spaceless %}{{ application.federal_agency }}
|
|
||||||
{{ application.organization_name }}
|
|
||||||
{{ application.address_line1 }}{% if application.address_line2 %}
|
|
||||||
{{ application.address_line2 }}{% endif %}
|
|
||||||
{{ application.city }}, {{ application.state_territory }}
|
|
||||||
{{ application.zipcode }}{% if application.urbanization %}
|
|
||||||
{{ application.urbanization }}{% endif %}{% endspaceless %}
|
|
||||||
{% if application.about_your_organization %}{# if block makes one newline if it's false #}
|
|
||||||
About your organization:
|
|
||||||
{% spaceless %}{{ application.about_your_organization }}{% endspaceless %}
|
|
||||||
{% endif %}
|
|
||||||
Authorizing official:
|
|
||||||
{% spaceless %}{% include "emails/includes/contact.txt" with contact=application.authorizing_official %}{% endspaceless %}
|
|
||||||
{% if application.current_websites.exists %}{# if block makes a newline #}
|
|
||||||
Current websites: {% for site in application.current_websites.all %}
|
|
||||||
{% spaceless %}{{ site.website }}{% endspaceless %}
|
|
||||||
{% endfor %}{% endif %}
|
|
||||||
.gov domain:
|
|
||||||
{{ application.requested_domain.name }}
|
|
||||||
{% if application.alternative_domains.all %}
|
|
||||||
Alternative domains:
|
|
||||||
{% for site in application.alternative_domains.all %}{% spaceless %}{{ site.website }}{% endspaceless %}
|
|
||||||
{% endfor %}{% endif %}
|
|
||||||
Purpose of your domain:
|
|
||||||
{{ application.purpose }}
|
|
||||||
|
|
||||||
Your contact information:
|
|
||||||
{% spaceless %}{% include "emails/includes/contact.txt" with contact=application.submitter %}{% endspaceless %}
|
|
||||||
|
|
||||||
Other employees from your organization:{% for other in application.other_contacts.all %}
|
|
||||||
{% spaceless %}{% include "emails/includes/contact.txt" with contact=other %}{% endspaceless %}
|
|
||||||
{% empty %}
|
|
||||||
{{ application.no_other_contacts_rationale }}
|
|
||||||
{% endfor %}{% if application.anything_else %}
|
|
||||||
Anything else?
|
|
||||||
{{ application.anything_else }}
|
|
||||||
{% endif %}
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
SUMMARY OF YOUR DOMAIN REQUEST
|
||||||
|
|
||||||
|
Type of organization:
|
||||||
|
{{ domain_request.get_organization_type_display }}
|
||||||
|
{% if domain_request.show_organization_federal %}
|
||||||
|
Federal government branch:
|
||||||
|
{{ domain_request.get_federal_type_display }}
|
||||||
|
{% elif domain_request.show_tribal_government %}
|
||||||
|
Tribal government:
|
||||||
|
{{ domain_request.tribe_name|default:"Incomplete" }}{% if domain_request.federally_recognized_tribe %}
|
||||||
|
Federally-recognized tribe
|
||||||
|
{% endif %}{% if domain_request.state_recognized_tribe %}
|
||||||
|
State-recognized tribe
|
||||||
|
{% endif %}{% endif %}{% if domain_request.show_organization_election %}
|
||||||
|
Election office:
|
||||||
|
{{ domain_request.is_election_board|yesno:"Yes,No,Incomplete" }}
|
||||||
|
{% endif %}
|
||||||
|
Organization name and mailing address:
|
||||||
|
{% spaceless %}{{ domain_request.federal_agency }}
|
||||||
|
{{ domain_request.organization_name }}
|
||||||
|
{{ domain_request.address_line1 }}{% if domain_request.address_line2 %}
|
||||||
|
{{ domain_request.address_line2 }}{% endif %}
|
||||||
|
{{ domain_request.city }}, {{ domain_request.state_territory }}
|
||||||
|
{{ domain_request.zipcode }}{% if domain_request.urbanization %}
|
||||||
|
{{ domain_request.urbanization }}{% endif %}{% endspaceless %}
|
||||||
|
{% if domain_request.about_your_organization %}{# if block makes one newline if it's false #}
|
||||||
|
About your organization:
|
||||||
|
{% spaceless %}{{ domain_request.about_your_organization }}{% endspaceless %}
|
||||||
|
{% endif %}
|
||||||
|
Authorizing official:
|
||||||
|
{% spaceless %}{% include "emails/includes/contact.txt" with contact=domain_request.authorizing_official %}{% endspaceless %}
|
||||||
|
{% if domain_request.current_websites.exists %}{# if block makes a newline #}
|
||||||
|
Current websites: {% for site in domain_request.current_websites.all %}
|
||||||
|
{% spaceless %}{{ site.website }}{% endspaceless %}
|
||||||
|
{% endfor %}{% endif %}
|
||||||
|
.gov domain:
|
||||||
|
{{ domain_request.requested_domain.name }}
|
||||||
|
{% if domain_request.alternative_domains.all %}
|
||||||
|
Alternative domains:
|
||||||
|
{% for site in domain_request.alternative_domains.all %}{% spaceless %}{{ site.website }}{% endspaceless %}
|
||||||
|
{% endfor %}{% endif %}
|
||||||
|
Purpose of your domain:
|
||||||
|
{{ domain_request.purpose }}
|
||||||
|
|
||||||
|
Your contact information:
|
||||||
|
{% spaceless %}{% include "emails/includes/contact.txt" with contact=domain_request.submitter %}{% endspaceless %}
|
||||||
|
|
||||||
|
Other employees from your organization:{% for other in domain_request.other_contacts.all %}
|
||||||
|
{% spaceless %}{% include "emails/includes/contact.txt" with contact=other %}{% endspaceless %}
|
||||||
|
{% empty %}
|
||||||
|
{{ domain_request.no_other_contacts_rationale }}
|
||||||
|
{% endfor %}{% if domain_request.anything_else %}
|
||||||
|
Anything else?
|
||||||
|
{{ domain_request.anything_else }}
|
||||||
|
{% endif %}
|
|
@ -1,10 +1,10 @@
|
||||||
{% 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, {{ application.submitter.first_name }}.
|
Hi, {{ domain_request.submitter.first_name }}.
|
||||||
|
|
||||||
Congratulations! Your .gov domain request has been approved.
|
Congratulations! Your .gov domain request has been approved.
|
||||||
|
|
||||||
DOMAIN REQUESTED: {{ application.requested_domain.name }}
|
DOMAIN REQUESTED: {{ domain_request.requested_domain.name }}
|
||||||
REQUEST RECEIVED ON: {{ application.submission_date|date }}
|
REQUEST RECEIVED ON: {{ domain_request.submission_date|date }}
|
||||||
STATUS: Approved
|
STATUS: Approved
|
||||||
|
|
||||||
You can manage your approved domain on the .gov registrar <https://manage.get.gov>.
|
You can manage your approved domain on the .gov registrar <https://manage.get.gov>.
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Update on your .gov request: {{ application.requested_domain.name }}
|
Update on your .gov request: {{ domain_request.requested_domain.name }}
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
{% 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, {{ application.submitter.first_name }}.
|
Hi, {{ domain_request.submitter.first_name }}.
|
||||||
|
|
||||||
Your .gov domain request has been rejected.
|
Your .gov domain request has been rejected.
|
||||||
|
|
||||||
DOMAIN REQUESTED: {{ application.requested_domain.name }}
|
DOMAIN REQUESTED: {{ domain_request.requested_domain.name }}
|
||||||
REQUEST RECEIVED ON: {{ application.submission_date|date }}
|
REQUEST RECEIVED ON: {{ domain_request.submission_date|date }}
|
||||||
STATUS: Rejected
|
STATUS: Rejected
|
||||||
|
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
{% if application.rejection_reason != 'other' %}
|
{% if domain_request.rejection_reason != 'other' %}
|
||||||
REJECTION REASON{% endif %}{% if application.rejection_reason == 'purpose_not_met' %}
|
REJECTION REASON{% endif %}{% if domain_request.rejection_reason == 'purpose_not_met' %}
|
||||||
Your domain request was rejected because the purpose you provided did not meet our
|
Your domain request was rejected because the purpose you provided did not meet our
|
||||||
requirements. You didn’t provide enough information about how you intend to use the
|
requirements. You didn’t provide enough information about how you intend to use the
|
||||||
domain.
|
domain.
|
||||||
|
@ -18,16 +18,16 @@ Learn more about:
|
||||||
- Eligibility for a .gov domain <https://get.gov/domains/eligibility>
|
- Eligibility for a .gov domain <https://get.gov/domains/eligibility>
|
||||||
- What you can and can’t do with .gov domains <https://get.gov/domains/requirements/>
|
- What you can and can’t do with .gov domains <https://get.gov/domains/requirements/>
|
||||||
|
|
||||||
If you have questions or comments, reply to this email.{% elif application.rejection_reason == 'requestor_not_eligible' %}
|
If you have questions or comments, reply to this email.{% elif domain_request.rejection_reason == 'requestor_not_eligible' %}
|
||||||
Your domain request was rejected because we don’t believe you’re eligible to request a
|
Your domain request was rejected because we don’t believe you’re eligible to request a
|
||||||
.gov domain on behalf of {{ application.organization_name }}. You must be a government employee, or be
|
.gov domain on behalf of {{ domain_request.organization_name }}. You must be a government employee, or be
|
||||||
working on behalf of a government organization, to request a .gov domain.
|
working on behalf of a government organization, to request a .gov domain.
|
||||||
|
|
||||||
|
|
||||||
DEMONSTRATE ELIGIBILITY
|
DEMONSTRATE ELIGIBILITY
|
||||||
If you can provide more information that demonstrates your eligibility, or you want to
|
If you can provide more information that demonstrates your eligibility, or you want to
|
||||||
discuss further, reply to this email.{% elif application.rejection_reason == 'org_has_domain' %}
|
discuss further, reply to this email.{% elif domain_request.rejection_reason == 'org_has_domain' %}
|
||||||
Your domain request was rejected because {{ application.organization_name }} has a .gov domain. Our
|
Your domain request was rejected because {{ domain_request.organization_name }} has a .gov domain. Our
|
||||||
practice is to approve one domain per online service per government organization. We
|
practice is to approve one domain per online service per government organization. We
|
||||||
evaluate additional requests on a case-by-case basis. You did not provide sufficient
|
evaluate additional requests on a case-by-case basis. You did not provide sufficient
|
||||||
justification for an additional domain.
|
justification for an additional domain.
|
||||||
|
@ -35,10 +35,10 @@ justification for an additional domain.
|
||||||
Read more about our practice of approving one domain per online service
|
Read more about our practice of approving one domain per online service
|
||||||
<https://get.gov/domains/before/#one-domain-per-service>.
|
<https://get.gov/domains/before/#one-domain-per-service>.
|
||||||
|
|
||||||
If you have questions or comments, reply to this email.{% elif application.rejection_reason == 'contacts_not_verified' %}
|
If you have questions or comments, reply to this email.{% elif domain_request.rejection_reason == 'contacts_not_verified' %}
|
||||||
Your domain request was rejected because we could not verify the organizational
|
Your domain request was rejected because we could not verify the organizational
|
||||||
contacts you provided. If you have questions or comments, reply to this email.{% elif application.rejection_reason == 'org_not_eligible' %}
|
contacts you provided. If you have questions or comments, reply to this email.{% elif domain_request.rejection_reason == 'org_not_eligible' %}
|
||||||
Your domain request was rejected because we determined that {{ application.organization_name }} is not
|
Your domain request was rejected because we determined that {{ domain_request.organization_name }} is not
|
||||||
eligible for a .gov domain. .Gov domains are only available to official U.S.-based
|
eligible for a .gov domain. .Gov domains are only available to official U.S.-based
|
||||||
government organizations.
|
government organizations.
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ If you can provide documentation that demonstrates your eligibility, reply to th
|
||||||
This can include links to (or copies of) your authorizing legislation, your founding
|
This can include links to (or copies of) your authorizing legislation, your founding
|
||||||
charter or bylaws, or other similar documentation. Without this, we can’t approve a
|
charter or bylaws, or other similar documentation. Without this, we can’t approve a
|
||||||
.gov domain for your organization. Learn more about eligibility for .gov domains
|
.gov domain for your organization. Learn more about eligibility for .gov domains
|
||||||
<https://get.gov/domains/eligibility/>.{% elif application.rejection_reason == 'naming_not_met' %}
|
<https://get.gov/domains/eligibility/>.{% elif domain_request.rejection_reason == 'naming_not_met' %}
|
||||||
Your domain request was rejected because it does not meet our naming requirements.
|
Your domain request was rejected because it does not meet our naming requirements.
|
||||||
Domains should uniquely identify a government organization and be clear to the
|
Domains should uniquely identify a government organization and be clear to the
|
||||||
general public. Learn more about naming requirements for your type of organization
|
general public. Learn more about naming requirements for your type of organization
|
||||||
|
@ -57,7 +57,7 @@ general public. Learn more about naming requirements for your type of organizati
|
||||||
|
|
||||||
YOU CAN SUBMIT A NEW REQUEST
|
YOU CAN SUBMIT A NEW REQUEST
|
||||||
We encourage you to request a domain that meets our requirements. If you have
|
We encourage you to request a domain that meets our requirements. If you have
|
||||||
questions or want to discuss potential domain names, reply to this email.{% elif application.rejection_reason == 'other' %}
|
questions or want to discuss potential domain names, reply to this email.{% elif domain_request.rejection_reason == 'other' %}
|
||||||
YOU CAN SUBMIT A NEW REQUEST
|
YOU CAN SUBMIT A NEW REQUEST
|
||||||
If your organization is eligible for a .gov domain and you meet our other requirements, you can submit a new request.
|
If your organization is eligible for a .gov domain and you meet our other requirements, you can submit a new request.
|
||||||
|
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Update on your .gov request: {{ application.requested_domain.name }}
|
Update on your .gov request: {{ domain_request.requested_domain.name }}
|
||||||
|
|
|
@ -1,21 +1,23 @@
|
||||||
{% 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, {{ application.submitter.first_name }}.
|
Hi, {{ domain_request.submitter.first_name }}.
|
||||||
|
|
||||||
We received your .gov domain request.
|
We received your .gov domain request.
|
||||||
|
|
||||||
DOMAIN REQUESTED: {{ application.requested_domain.name }}
|
DOMAIN REQUESTED: {{ domain_request.requested_domain.name }}
|
||||||
REQUEST RECEIVED ON: {{ application.submission_date|date }}
|
REQUEST RECEIVED ON: {{ domain_request.submission_date|date }}
|
||||||
STATUS: Submitted
|
STATUS: Submitted
|
||||||
|
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
|
|
||||||
NEXT STEPS
|
NEXT STEPS
|
||||||
We’ll review your request. This usually takes 20 business days. During this review we’ll verify that:
|
We’ll review your request. This review period can take 30 business days. Due to the volume of requests, the wait time is longer than usual. We appreciate your patience.
|
||||||
|
|
||||||
|
During our review we’ll verify that:
|
||||||
- Your organization is eligible for a .gov domain
|
- Your organization is eligible for a .gov domain
|
||||||
- You work at the organization and/or can make requests on its behalf
|
- You work at the organization and/or can make requests on its behalf
|
||||||
- Your requested domain meets our naming requirements
|
- Your requested domain meets our naming requirements
|
||||||
|
|
||||||
We’ll email you if we have questions and when we complete our review. You can check the status of your request at any time on the registrar homepage. <https://manage.get.gov>
|
We’ll email you if we have questions. We’ll also email you as soon as we complete our review. You can check the status of your request at any time on the registrar homepage. <https://manage.get.gov>
|
||||||
|
|
||||||
|
|
||||||
NEED TO MAKE CHANGES?
|
NEED TO MAKE CHANGES?
|
||||||
|
@ -29,7 +31,7 @@ THANK YOU
|
||||||
|
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
|
|
||||||
{% include 'emails/includes/application_summary.txt' %}
|
{% include 'emails/includes/domain_request_summary.txt' %}
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
|
|
||||||
The .gov team
|
The .gov team
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Update on your .gov request: {{ application.requested_domain.name }}
|
Update on your .gov request: {{ domain_request.requested_domain.name }}
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
|
|
||||||
|
|
||||||
<p class="margin-top-4">
|
<p class="margin-top-4">
|
||||||
<a href="{% url 'application:' %}" class="usa-button"
|
<a href="{% url 'domain-request:' %}" class="usa-button"
|
||||||
>
|
>
|
||||||
Start a new domain request
|
Start a new domain request
|
||||||
</a>
|
</a>
|
||||||
|
@ -103,7 +103,7 @@
|
||||||
|
|
||||||
<section class="section--outlined">
|
<section class="section--outlined">
|
||||||
<h2>Domain requests</h2>
|
<h2>Domain requests</h2>
|
||||||
{% if domain_applications %}
|
{% if domain_requests %}
|
||||||
<table class="usa-table usa-table--borderless usa-table--stacked dotgov-table dotgov-table--stacked">
|
<table class="usa-table usa-table--borderless usa-table--stacked dotgov-table dotgov-table--stacked">
|
||||||
<caption class="sr-only">Your domain requests</caption>
|
<caption class="sr-only">Your domain requests</caption>
|
||||||
<thead>
|
<thead>
|
||||||
|
@ -112,61 +112,61 @@
|
||||||
<th data-sortable scope="col" role="columnheader">Date submitted</th>
|
<th data-sortable scope="col" role="columnheader">Date submitted</th>
|
||||||
<th data-sortable scope="col" role="columnheader">Status</th>
|
<th data-sortable scope="col" role="columnheader">Status</th>
|
||||||
<th scope="col" role="columnheader"><span class="usa-sr-only">Action</span></th>
|
<th scope="col" role="columnheader"><span class="usa-sr-only">Action</span></th>
|
||||||
{% if has_deletable_applications %}
|
{% if has_deletable_domain_requests %}
|
||||||
<th scope="col" role="columnheader"><span class="usa-sr-only">Delete Action</span></th>
|
<th scope="col" role="columnheader"><span class="usa-sr-only">Delete Action</span></th>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for application in domain_applications %}
|
{% for domain_request in domain_requests %}
|
||||||
<tr>
|
<tr>
|
||||||
<th th scope="row" role="rowheader" data-label="Domain name">
|
<th th scope="row" role="rowheader" data-label="Domain name">
|
||||||
{% if application.requested_domain is None %}
|
{% if domain_request.requested_domain is None %}
|
||||||
New domain request
|
New domain request
|
||||||
{# Add a breakpoint #}
|
{# Add a breakpoint #}
|
||||||
<div aria-hidden="true"></div>
|
<div aria-hidden="true"></div>
|
||||||
<span class="text-base font-body-xs">({{ application.created_at }} UTC)</span>
|
<span class="text-base font-body-xs">({{ domain_request.created_at }} UTC)</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ application.requested_domain.name }}
|
{{ domain_request.requested_domain.name }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</th>
|
</th>
|
||||||
<td data-sort-value="{{ application.submission_date|date:"U" }}" data-label="Date submitted">
|
<td data-sort-value="{{ domain_request.submission_date|date:"U" }}" data-label="Date submitted">
|
||||||
{% if application.submission_date %}
|
{% if domain_request.submission_date %}
|
||||||
{{ application.submission_date|date }}
|
{{ domain_request.submission_date|date }}
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-base">Not submitted</span>
|
<span class="text-base">Not submitted</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td data-label="Status">{{ application.get_status_display }}</td>
|
<td data-label="Status">{{ domain_request.get_status_display }}</td>
|
||||||
<td>
|
<td>
|
||||||
{% with prefix="New domain request ("%}
|
{% with prefix="New domain request ("%}
|
||||||
{% with date=application.created_at|date:"DATETIME_FORMAT"%}
|
{% with date=domain_request.created_at|date:"DATETIME_FORMAT"%}
|
||||||
{% with name_default=prefix|add:date|add:" UTC)"%}
|
{% with name_default=prefix|add:date|add:" UTC)"%}
|
||||||
{% if application.status == application.ApplicationStatus.STARTED or application.status == application.ApplicationStatus.ACTION_NEEDED or application.status == application.ApplicationStatus.WITHDRAWN %}
|
{% if domain_request.status == domain_request.DomainRequestStatus.STARTED or domain_request.status == domain_request.DomainRequestStatus.ACTION_NEEDED or domain_request.status == domain_request.DomainRequestStatus.WITHDRAWN %}
|
||||||
<a href="{% url 'edit-application' application.pk %}">
|
<a href="{% url 'edit-domain-request' domain_request.pk %}">
|
||||||
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24">
|
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24">
|
||||||
<use xlink:href="{%static 'img/sprite.svg'%}#edit"></use>
|
<use xlink:href="{%static 'img/sprite.svg'%}#edit"></use>
|
||||||
</svg>
|
</svg>
|
||||||
{% if application.requested_domain is not None%}
|
{% if domain_request.requested_domain is not None%}
|
||||||
Edit <span class="usa-sr-only">{{ application.requested_domain.name }}</span>
|
Edit <span class="usa-sr-only">{{ domain_request.requested_domain.name }}</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
Edit <span class="usa-sr-only">{{ name_default }}</span>
|
Edit <span class="usa-sr-only">{{ name_default }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<a href="{% url 'application-status' application.pk %}">
|
<a href="{% url 'domain-request-status' domain_request.pk %}">
|
||||||
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24">
|
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24">
|
||||||
<use xlink:href="{%static 'img/sprite.svg'%}#settings"></use>
|
<use xlink:href="{%static 'img/sprite.svg'%}#settings"></use>
|
||||||
</svg>
|
</svg>
|
||||||
Manage <span class="usa-sr-only">{{ application.requested_domain.name|default:name_default }}</span>
|
Manage <span class="usa-sr-only">{{ domain_request.requested_domain.name|default:name_default }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
{% if has_deletable_applications %}
|
{% if has_deletable_domain_requests %}
|
||||||
<td>
|
<td>
|
||||||
{% if application.status == "started" or application.status == "withdrawn" %}
|
{% if domain_request.status == "started" or domain_request.status == "withdrawn" %}
|
||||||
<a
|
<a
|
||||||
role="button"
|
role="button"
|
||||||
id="button-toggle-delete-domain-alert-{{ forloop.counter }}"
|
id="button-toggle-delete-domain-alert-{{ forloop.counter }}"
|
||||||
|
@ -179,10 +179,10 @@
|
||||||
<use xlink:href="{%static 'img/sprite.svg'%}#delete"></use>
|
<use xlink:href="{%static 'img/sprite.svg'%}#delete"></use>
|
||||||
</svg>
|
</svg>
|
||||||
{% with prefix="New domain request ("%}
|
{% with prefix="New domain request ("%}
|
||||||
{% with date=application.created_at|date:"DATETIME_FORMAT"%}
|
{% with date=domain_request.created_at|date:"DATETIME_FORMAT"%}
|
||||||
{% with name_default=prefix|add:date|add:" UTC)"%}
|
{% with name_default=prefix|add:date|add:" UTC)"%}
|
||||||
{% if application.requested_domain is not None %}
|
{% if domain_request.requested_domain is not None %}
|
||||||
Delete <span class="usa-sr-only">{{ application.requested_domain.name }}</span>
|
Delete <span class="usa-sr-only">{{ domain_request.requested_domain.name }}</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
Delete <span class="usa-sr-only">{{ name_default }}</span>
|
Delete <span class="usa-sr-only">{{ name_default }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -198,11 +198,11 @@
|
||||||
aria-describedby="Domain will be removed"
|
aria-describedby="Domain will be removed"
|
||||||
data-force-action
|
data-force-action
|
||||||
>
|
>
|
||||||
<form method="POST" action="{% url "application-delete" pk=application.id %}">
|
<form method="POST" action="{% url "domain-request-delete" pk=domain_request.id %}">
|
||||||
{% if application.requested_domain is None %}
|
{% if domain_request.requested_domain is None %}
|
||||||
{% if application.created_at %}
|
{% if domain_request.created_at %}
|
||||||
{% with prefix="(created " %}
|
{% with prefix="(created " %}
|
||||||
{% with formatted_date=application.created_at|date:"DATETIME_FORMAT" %}
|
{% with formatted_date=domain_request.created_at|date:"DATETIME_FORMAT" %}
|
||||||
{% with modal_content=prefix|add:formatted_date|add:" UTC)" %}
|
{% with modal_content=prefix|add:formatted_date|add:" UTC)" %}
|
||||||
{% include 'includes/modal.html' with modal_heading="Are you sure you want to delete this domain request?" modal_description="This will remove the domain request "|add:modal_content|add:" from the .gov registrar. This action cannot be undone." modal_button=modal_button|safe %}
|
{% include 'includes/modal.html' with modal_heading="Are you sure you want to delete this domain request?" modal_description="This will remove the domain request "|add:modal_content|add:" from the .gov registrar. This action cannot be undone." modal_button=modal_button|safe %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
@ -212,7 +212,7 @@
|
||||||
{% include 'includes/modal.html' with modal_heading="Are you sure you want to delete New domain request?" modal_description="This will remove the domain request from the .gov registrar. This action cannot be undone." modal_button=modal_button|safe %}
|
{% include 'includes/modal.html' with modal_heading="Are you sure you want to delete New domain request?" modal_description="This will remove the domain request from the .gov registrar. This action cannot be undone." modal_button=modal_button|safe %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% with modal_heading_value=application.requested_domain.name|add:"?" %}
|
{% with modal_heading_value=domain_request.requested_domain.name|add:"?" %}
|
||||||
{% include 'includes/modal.html' with modal_heading="Are you sure you want to delete" heading_value=modal_heading_value modal_description="This will remove the domain request from the .gov registrar. This action cannot be undone." modal_button=modal_button|safe %}
|
{% include 'includes/modal.html' with modal_heading="Are you sure you want to delete" heading_value=modal_heading_value modal_description="This will remove the domain request from the .gov registrar. This action cannot be undone." modal_button=modal_button|safe %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -231,7 +231,7 @@
|
||||||
></div>
|
></div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>You haven't requested any domains.</p>
|
<p>You haven't requested any domains.</p>
|
||||||
<!-- <p><a href="{% url 'application:' %}" class="usa-button">Start a new domain request</a></p> -->
|
<!-- <p><a href="{% url 'domain-request:' %}" class="usa-button">Start a new domain request</a></p> -->
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import logging
|
import logging
|
||||||
from django import template
|
from django import template
|
||||||
import re
|
import re
|
||||||
from registrar.models.domain_application import DomainApplication
|
from registrar.models.domain_request import DomainRequest
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -55,7 +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(DomainApplication.OrganizationChoicesVerbose.choices)
|
organization_choices_dict = dict(DomainRequest.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")
|
||||||
|
|
|
@ -18,7 +18,7 @@ from registrar.models import (
|
||||||
Contact,
|
Contact,
|
||||||
DraftDomain,
|
DraftDomain,
|
||||||
Website,
|
Website,
|
||||||
DomainApplication,
|
DomainRequest,
|
||||||
DomainInvitation,
|
DomainInvitation,
|
||||||
User,
|
User,
|
||||||
UserGroup,
|
UserGroup,
|
||||||
|
@ -241,7 +241,7 @@ class AuditedAdminMockData:
|
||||||
|
|
||||||
# Constants for different domain object types
|
# Constants for different domain object types
|
||||||
INFORMATION = "information"
|
INFORMATION = "information"
|
||||||
APPLICATION = "application"
|
DOMAIN_REQUEST = "domain_request"
|
||||||
INVITATION = "invitation"
|
INVITATION = "invitation"
|
||||||
|
|
||||||
def dummy_user(self, item_name, short_hand):
|
def dummy_user(self, item_name, short_hand):
|
||||||
|
@ -385,24 +385,24 @@ class AuditedAdminMockData:
|
||||||
self,
|
self,
|
||||||
domain_type,
|
domain_type,
|
||||||
item_name,
|
item_name,
|
||||||
status=DomainApplication.ApplicationStatus.STARTED,
|
status=DomainRequest.DomainRequestStatus.STARTED,
|
||||||
org_type="federal",
|
org_type="federal",
|
||||||
federal_type="executive",
|
federal_type="executive",
|
||||||
purpose="Purpose of the site",
|
purpose="Purpose of the site",
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Returns a prebuilt kwarg dictionary for DomainApplication,
|
Returns a prebuilt kwarg dictionary for DomainRequest,
|
||||||
DomainInformation, or DomainInvitation.
|
DomainInformation, or DomainInvitation.
|
||||||
Args:
|
Args:
|
||||||
domain_type (str): is either 'application', 'information',
|
domain_type (str): is either 'domain_request', 'information',
|
||||||
or 'invitation'.
|
or 'invitation'.
|
||||||
|
|
||||||
item_name (str): A shared str value appended to first_name, last_name,
|
item_name (str): A shared str value appended to first_name, last_name,
|
||||||
organization_name, address_line1, address_line2,
|
organization_name, address_line1, address_line2,
|
||||||
title, email, and username.
|
title, email, and username.
|
||||||
|
|
||||||
status (str - optional): Defines the status for DomainApplication,
|
status (str - optional): Defines the status for DomainRequest,
|
||||||
e.g. DomainApplication.ApplicationStatus.STARTED
|
e.g. DomainRequest.DomainRequestStatus.STARTED
|
||||||
|
|
||||||
org_type (str - optional): Sets a domains org_type
|
org_type (str - optional): Sets a domains org_type
|
||||||
|
|
||||||
|
@ -411,13 +411,13 @@ class AuditedAdminMockData:
|
||||||
purpose (str - optional): Sets a domains purpose
|
purpose (str - optional): Sets a domains purpose
|
||||||
Returns:
|
Returns:
|
||||||
dict: Returns a dictionary structurally consistent with the expected input
|
dict: Returns a dictionary structurally consistent with the expected input
|
||||||
of either DomainApplication, DomainInvitation, or DomainInformation
|
of either DomainRequest, 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(item_name, org_type, federal_type, purpose)
|
common_args = self.get_common_domain_arg_dictionary(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.DOMAIN_REQUEST:
|
||||||
full_arg_dict = dict(
|
full_arg_dict = dict(
|
||||||
**common_args,
|
**common_args,
|
||||||
requested_domain=self.dummy_draft_domain(item_name),
|
requested_domain=self.dummy_draft_domain(item_name),
|
||||||
|
@ -425,11 +425,11 @@ class AuditedAdminMockData:
|
||||||
status=status,
|
status=status,
|
||||||
)
|
)
|
||||||
case self.INFORMATION:
|
case self.INFORMATION:
|
||||||
domain_app = self.create_full_dummy_domain_application(item_name)
|
domain_req = self.create_full_dummy_domain_request(item_name)
|
||||||
full_arg_dict = dict(
|
full_arg_dict = dict(
|
||||||
**common_args,
|
**common_args,
|
||||||
domain=self.dummy_domain(item_name, True),
|
domain=self.dummy_domain(item_name, True),
|
||||||
domain_application=domain_app,
|
domain_request=domain_req,
|
||||||
)
|
)
|
||||||
case self.INVITATION:
|
case self.INVITATION:
|
||||||
full_arg_dict = dict(
|
full_arg_dict = dict(
|
||||||
|
@ -439,24 +439,24 @@ class AuditedAdminMockData:
|
||||||
)
|
)
|
||||||
return full_arg_dict
|
return full_arg_dict
|
||||||
|
|
||||||
def create_full_dummy_domain_application(self, item_name, status=DomainApplication.ApplicationStatus.STARTED):
|
def create_full_dummy_domain_request(self, item_name, status=DomainRequest.DomainRequestStatus.STARTED):
|
||||||
"""Creates a dummy domain application object"""
|
"""Creates a dummy domain request object"""
|
||||||
domain_application_kwargs = self.dummy_kwarg_boilerplate(self.APPLICATION, item_name, status)
|
domain_request_kwargs = self.dummy_kwarg_boilerplate(self.DOMAIN_REQUEST, item_name, status)
|
||||||
application = DomainApplication.objects.get_or_create(**domain_application_kwargs)[0]
|
domain_request = DomainRequest.objects.get_or_create(**domain_request_kwargs)[0]
|
||||||
return application
|
return domain_request
|
||||||
|
|
||||||
def create_full_dummy_domain_information(self, item_name, status=DomainApplication.ApplicationStatus.STARTED):
|
def create_full_dummy_domain_information(self, item_name, status=DomainRequest.DomainRequestStatus.STARTED):
|
||||||
"""Creates a dummy domain information object"""
|
"""Creates a dummy domain information object"""
|
||||||
domain_application_kwargs = self.dummy_kwarg_boilerplate(self.INFORMATION, item_name, status)
|
domain_request_kwargs = self.dummy_kwarg_boilerplate(self.INFORMATION, item_name, status)
|
||||||
application = DomainInformation.objects.get_or_create(**domain_application_kwargs)[0]
|
domain_request = DomainInformation.objects.get_or_create(**domain_request_kwargs)[0]
|
||||||
return application
|
return domain_request
|
||||||
|
|
||||||
def create_full_dummy_domain_invitation(self, item_name, status=DomainApplication.ApplicationStatus.STARTED):
|
def create_full_dummy_domain_invitation(self, item_name, status=DomainRequest.DomainRequestStatus.STARTED):
|
||||||
"""Creates a dummy domain invitation object"""
|
"""Creates a dummy domain invitation object"""
|
||||||
domain_application_kwargs = self.dummy_kwarg_boilerplate(self.INVITATION, item_name, status)
|
domain_request_kwargs = self.dummy_kwarg_boilerplate(self.INVITATION, item_name, status)
|
||||||
application = DomainInvitation.objects.get_or_create(**domain_application_kwargs)[0]
|
domain_request = DomainInvitation.objects.get_or_create(**domain_request_kwargs)[0]
|
||||||
|
|
||||||
return application
|
return domain_request
|
||||||
|
|
||||||
def create_full_dummy_domain_object(
|
def create_full_dummy_domain_object(
|
||||||
self,
|
self,
|
||||||
|
@ -465,37 +465,36 @@ class AuditedAdminMockData:
|
||||||
has_other_contacts=True,
|
has_other_contacts=True,
|
||||||
has_current_website=True,
|
has_current_website=True,
|
||||||
has_alternative_gov_domain=True,
|
has_alternative_gov_domain=True,
|
||||||
status=DomainApplication.ApplicationStatus.STARTED,
|
status=DomainRequest.DomainRequestStatus.STARTED,
|
||||||
):
|
):
|
||||||
"""A helper to create a dummy domain application object"""
|
"""A helper to create a dummy domain request object"""
|
||||||
application = None
|
domain_request = None
|
||||||
match domain_type:
|
match domain_type:
|
||||||
case self.APPLICATION:
|
case self.DOMAIN_REQUEST:
|
||||||
application = self.create_full_dummy_domain_application(item_name, status)
|
domain_request = self.create_full_dummy_domain_request(item_name, status)
|
||||||
case self.INVITATION:
|
case self.INVITATION:
|
||||||
application = self.create_full_dummy_domain_invitation(item_name, status)
|
domain_request = self.create_full_dummy_domain_invitation(item_name, status)
|
||||||
case self.INFORMATION:
|
case self.INFORMATION:
|
||||||
application = self.create_full_dummy_domain_information(item_name, status)
|
domain_request = self.create_full_dummy_domain_information(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")
|
||||||
|
|
||||||
if has_other_contacts and domain_type != self.INVITATION:
|
if has_other_contacts and domain_type != self.INVITATION:
|
||||||
other = self.dummy_contact(item_name, "other")
|
other = self.dummy_contact(item_name, "other")
|
||||||
application.other_contacts.add(other)
|
domain_request.other_contacts.add(other)
|
||||||
if has_current_website and domain_type == self.APPLICATION:
|
if has_current_website and domain_type == self.DOMAIN_REQUEST:
|
||||||
current = self.dummy_current(item_name)
|
current = self.dummy_current(item_name)
|
||||||
application.current_websites.add(current)
|
domain_request.current_websites.add(current)
|
||||||
if has_alternative_gov_domain and domain_type == self.APPLICATION:
|
if has_alternative_gov_domain and domain_type == self.DOMAIN_REQUEST:
|
||||||
alt = self.dummy_alt(item_name)
|
alt = self.dummy_alt(item_name)
|
||||||
application.alternative_domains.add(alt)
|
domain_request.alternative_domains.add(alt)
|
||||||
|
|
||||||
return application
|
return domain_request
|
||||||
|
|
||||||
|
|
||||||
def mock_user():
|
def mock_user():
|
||||||
"""A simple user."""
|
"""A simple user."""
|
||||||
user_kwargs = dict(
|
user_kwargs = dict(
|
||||||
id=4,
|
|
||||||
first_name="Jeff",
|
first_name="Jeff",
|
||||||
last_name="Lebowski",
|
last_name="Lebowski",
|
||||||
)
|
)
|
||||||
|
@ -540,18 +539,18 @@ def create_ready_domain():
|
||||||
return domain
|
return domain
|
||||||
|
|
||||||
|
|
||||||
def completed_application(
|
def completed_domain_request(
|
||||||
has_other_contacts=True,
|
has_other_contacts=True,
|
||||||
has_current_website=True,
|
has_current_website=True,
|
||||||
has_alternative_gov_domain=True,
|
has_alternative_gov_domain=True,
|
||||||
has_about_your_organization=True,
|
has_about_your_organization=True,
|
||||||
has_anything_else=True,
|
has_anything_else=True,
|
||||||
status=DomainApplication.ApplicationStatus.STARTED,
|
status=DomainRequest.DomainRequestStatus.STARTED,
|
||||||
user=False,
|
user=False,
|
||||||
submitter=False,
|
submitter=False,
|
||||||
name="city.gov",
|
name="city.gov",
|
||||||
):
|
):
|
||||||
"""A completed domain application."""
|
"""A completed domain request."""
|
||||||
if not user:
|
if not user:
|
||||||
user = get_user_model().objects.create(username="username" + str(uuid.uuid4())[:8])
|
user = get_user_model().objects.create(username="username" + str(uuid.uuid4())[:8])
|
||||||
ao, _ = Contact.objects.get_or_create(
|
ao, _ = Contact.objects.get_or_create(
|
||||||
|
@ -579,7 +578,7 @@ def completed_application(
|
||||||
email="testy2@town.com",
|
email="testy2@town.com",
|
||||||
phone="(555) 555 5557",
|
phone="(555) 555 5557",
|
||||||
)
|
)
|
||||||
domain_application_kwargs = dict(
|
domain_request_kwargs = dict(
|
||||||
organization_type="federal",
|
organization_type="federal",
|
||||||
federal_type="executive",
|
federal_type="executive",
|
||||||
purpose="Purpose of the site",
|
purpose="Purpose of the site",
|
||||||
|
@ -596,43 +595,43 @@ def completed_application(
|
||||||
status=status,
|
status=status,
|
||||||
)
|
)
|
||||||
if has_about_your_organization:
|
if has_about_your_organization:
|
||||||
domain_application_kwargs["about_your_organization"] = "e-Government"
|
domain_request_kwargs["about_your_organization"] = "e-Government"
|
||||||
if has_anything_else:
|
if has_anything_else:
|
||||||
domain_application_kwargs["anything_else"] = "There is more"
|
domain_request_kwargs["anything_else"] = "There is more"
|
||||||
|
|
||||||
application, _ = DomainApplication.objects.get_or_create(**domain_application_kwargs)
|
domain_request, _ = DomainRequest.objects.get_or_create(**domain_request_kwargs)
|
||||||
|
|
||||||
if has_other_contacts:
|
if has_other_contacts:
|
||||||
application.other_contacts.add(other)
|
domain_request.other_contacts.add(other)
|
||||||
if has_current_website:
|
if has_current_website:
|
||||||
application.current_websites.add(current)
|
domain_request.current_websites.add(current)
|
||||||
if has_alternative_gov_domain:
|
if has_alternative_gov_domain:
|
||||||
application.alternative_domains.add(alt)
|
domain_request.alternative_domains.add(alt)
|
||||||
|
|
||||||
return application
|
return domain_request
|
||||||
|
|
||||||
|
|
||||||
def multiple_unalphabetical_domain_objects(
|
def multiple_unalphabetical_domain_objects(
|
||||||
domain_type=AuditedAdminMockData.APPLICATION,
|
domain_type=AuditedAdminMockData.DOMAIN_REQUEST,
|
||||||
):
|
):
|
||||||
"""Returns a list of generic domain objects for testing purposes"""
|
"""Returns a list of generic domain objects for testing purposes"""
|
||||||
applications = []
|
domain_requests = []
|
||||||
list_of_letters = list(ascii_uppercase)
|
list_of_letters = list(ascii_uppercase)
|
||||||
random.shuffle(list_of_letters)
|
random.shuffle(list_of_letters)
|
||||||
|
|
||||||
mock = AuditedAdminMockData()
|
mock = AuditedAdminMockData()
|
||||||
for object_name in list_of_letters:
|
for object_name in list_of_letters:
|
||||||
application = mock.create_full_dummy_domain_object(domain_type, object_name)
|
domain_request = mock.create_full_dummy_domain_object(domain_type, object_name)
|
||||||
applications.append(application)
|
domain_requests.append(domain_request)
|
||||||
return applications
|
return domain_requests
|
||||||
|
|
||||||
|
|
||||||
def generic_domain_object(domain_type, object_name):
|
def generic_domain_object(domain_type, object_name):
|
||||||
"""Returns a generic domain object of
|
"""Returns a generic domain object of
|
||||||
domain_type 'application', 'information', or 'invitation'"""
|
domain_type 'domain_request', 'information', or 'invitation'"""
|
||||||
mock = AuditedAdminMockData()
|
mock = AuditedAdminMockData()
|
||||||
application = mock.create_full_dummy_domain_object(domain_type, object_name)
|
domain_request = mock.create_full_dummy_domain_object(domain_type, object_name)
|
||||||
return application
|
return domain_request
|
||||||
|
|
||||||
|
|
||||||
class MockEppLib(TestCase):
|
class MockEppLib(TestCase):
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -3,7 +3,7 @@
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from .common import completed_application, less_console_noise
|
from .common import completed_domain_request, less_console_noise
|
||||||
|
|
||||||
|
|
||||||
import boto3_mocking # type: ignore
|
import boto3_mocking # type: ignore
|
||||||
|
@ -17,11 +17,11 @@ class TestEmails(TestCase):
|
||||||
@boto3_mocking.patching
|
@boto3_mocking.patching
|
||||||
def test_submission_confirmation(self):
|
def test_submission_confirmation(self):
|
||||||
"""Submission confirmation email works."""
|
"""Submission confirmation email works."""
|
||||||
application = completed_application()
|
domain_request = completed_domain_request()
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
application.submit()
|
domain_request.submit()
|
||||||
|
|
||||||
# check that an email was sent
|
# check that an email was sent
|
||||||
self.assertTrue(self.mock_client.send_email.called)
|
self.assertTrue(self.mock_client.send_email.called)
|
||||||
|
@ -55,10 +55,10 @@ class TestEmails(TestCase):
|
||||||
@boto3_mocking.patching
|
@boto3_mocking.patching
|
||||||
def test_submission_confirmation_no_current_website_spacing(self):
|
def test_submission_confirmation_no_current_website_spacing(self):
|
||||||
"""Test line spacing without current_website."""
|
"""Test line spacing without current_website."""
|
||||||
application = completed_application(has_current_website=False)
|
domain_request = completed_domain_request(has_current_website=False)
|
||||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
application.submit()
|
domain_request.submit()
|
||||||
_, kwargs = self.mock_client.send_email.call_args
|
_, kwargs = self.mock_client.send_email.call_args
|
||||||
body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"]
|
body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"]
|
||||||
self.assertNotIn("Current websites:", body)
|
self.assertNotIn("Current websites:", body)
|
||||||
|
@ -68,10 +68,10 @@ class TestEmails(TestCase):
|
||||||
@boto3_mocking.patching
|
@boto3_mocking.patching
|
||||||
def test_submission_confirmation_current_website_spacing(self):
|
def test_submission_confirmation_current_website_spacing(self):
|
||||||
"""Test line spacing with current_website."""
|
"""Test line spacing with current_website."""
|
||||||
application = completed_application(has_current_website=True)
|
domain_request = completed_domain_request(has_current_website=True)
|
||||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
application.submit()
|
domain_request.submit()
|
||||||
_, kwargs = self.mock_client.send_email.call_args
|
_, kwargs = self.mock_client.send_email.call_args
|
||||||
body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"]
|
body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"]
|
||||||
self.assertIn("Current websites:", body)
|
self.assertIn("Current websites:", body)
|
||||||
|
@ -82,10 +82,10 @@ class TestEmails(TestCase):
|
||||||
@boto3_mocking.patching
|
@boto3_mocking.patching
|
||||||
def test_submission_confirmation_other_contacts_spacing(self):
|
def test_submission_confirmation_other_contacts_spacing(self):
|
||||||
"""Test line spacing with other contacts."""
|
"""Test line spacing with other contacts."""
|
||||||
application = completed_application(has_other_contacts=True)
|
domain_request = completed_domain_request(has_other_contacts=True)
|
||||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
application.submit()
|
domain_request.submit()
|
||||||
_, kwargs = self.mock_client.send_email.call_args
|
_, kwargs = self.mock_client.send_email.call_args
|
||||||
body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"]
|
body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"]
|
||||||
self.assertIn("Other employees from your organization:", body)
|
self.assertIn("Other employees from your organization:", body)
|
||||||
|
@ -96,10 +96,10 @@ class TestEmails(TestCase):
|
||||||
@boto3_mocking.patching
|
@boto3_mocking.patching
|
||||||
def test_submission_confirmation_no_other_contacts_spacing(self):
|
def test_submission_confirmation_no_other_contacts_spacing(self):
|
||||||
"""Test line spacing without other contacts."""
|
"""Test line spacing without other contacts."""
|
||||||
application = completed_application(has_other_contacts=False)
|
domain_request = completed_domain_request(has_other_contacts=False)
|
||||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
application.submit()
|
domain_request.submit()
|
||||||
_, kwargs = self.mock_client.send_email.call_args
|
_, kwargs = self.mock_client.send_email.call_args
|
||||||
body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"]
|
body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"]
|
||||||
# spacing should be right between adjacent elements
|
# spacing should be right between adjacent elements
|
||||||
|
@ -109,10 +109,10 @@ class TestEmails(TestCase):
|
||||||
@boto3_mocking.patching
|
@boto3_mocking.patching
|
||||||
def test_submission_confirmation_alternative_govdomain_spacing(self):
|
def test_submission_confirmation_alternative_govdomain_spacing(self):
|
||||||
"""Test line spacing with alternative .gov domain."""
|
"""Test line spacing with alternative .gov domain."""
|
||||||
application = completed_application(has_alternative_gov_domain=True)
|
domain_request = completed_domain_request(has_alternative_gov_domain=True)
|
||||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
application.submit()
|
domain_request.submit()
|
||||||
_, kwargs = self.mock_client.send_email.call_args
|
_, kwargs = self.mock_client.send_email.call_args
|
||||||
body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"]
|
body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"]
|
||||||
self.assertIn("city1.gov", body)
|
self.assertIn("city1.gov", body)
|
||||||
|
@ -122,10 +122,10 @@ class TestEmails(TestCase):
|
||||||
@boto3_mocking.patching
|
@boto3_mocking.patching
|
||||||
def test_submission_confirmation_no_alternative_govdomain_spacing(self):
|
def test_submission_confirmation_no_alternative_govdomain_spacing(self):
|
||||||
"""Test line spacing without alternative .gov domain."""
|
"""Test line spacing without alternative .gov domain."""
|
||||||
application = completed_application(has_alternative_gov_domain=False)
|
domain_request = completed_domain_request(has_alternative_gov_domain=False)
|
||||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
application.submit()
|
domain_request.submit()
|
||||||
_, kwargs = self.mock_client.send_email.call_args
|
_, kwargs = self.mock_client.send_email.call_args
|
||||||
body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"]
|
body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"]
|
||||||
self.assertNotIn("city1.gov", body)
|
self.assertNotIn("city1.gov", body)
|
||||||
|
@ -135,10 +135,10 @@ class TestEmails(TestCase):
|
||||||
@boto3_mocking.patching
|
@boto3_mocking.patching
|
||||||
def test_submission_confirmation_about_your_organization_spacing(self):
|
def test_submission_confirmation_about_your_organization_spacing(self):
|
||||||
"""Test line spacing with about your organization."""
|
"""Test line spacing with about your organization."""
|
||||||
application = completed_application(has_about_your_organization=True)
|
domain_request = completed_domain_request(has_about_your_organization=True)
|
||||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
application.submit()
|
domain_request.submit()
|
||||||
_, kwargs = self.mock_client.send_email.call_args
|
_, kwargs = self.mock_client.send_email.call_args
|
||||||
body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"]
|
body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"]
|
||||||
self.assertIn("About your organization:", body)
|
self.assertIn("About your organization:", body)
|
||||||
|
@ -148,10 +148,10 @@ class TestEmails(TestCase):
|
||||||
@boto3_mocking.patching
|
@boto3_mocking.patching
|
||||||
def test_submission_confirmation_no_about_your_organization_spacing(self):
|
def test_submission_confirmation_no_about_your_organization_spacing(self):
|
||||||
"""Test line spacing without about your organization."""
|
"""Test line spacing without about your organization."""
|
||||||
application = completed_application(has_about_your_organization=False)
|
domain_request = completed_domain_request(has_about_your_organization=False)
|
||||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
application.submit()
|
domain_request.submit()
|
||||||
_, kwargs = self.mock_client.send_email.call_args
|
_, kwargs = self.mock_client.send_email.call_args
|
||||||
body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"]
|
body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"]
|
||||||
self.assertNotIn("About your organization:", body)
|
self.assertNotIn("About your organization:", body)
|
||||||
|
@ -161,10 +161,10 @@ class TestEmails(TestCase):
|
||||||
@boto3_mocking.patching
|
@boto3_mocking.patching
|
||||||
def test_submission_confirmation_anything_else_spacing(self):
|
def test_submission_confirmation_anything_else_spacing(self):
|
||||||
"""Test line spacing with anything else."""
|
"""Test line spacing with anything else."""
|
||||||
application = completed_application(has_anything_else=True)
|
domain_request = completed_domain_request(has_anything_else=True)
|
||||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
application.submit()
|
domain_request.submit()
|
||||||
_, kwargs = self.mock_client.send_email.call_args
|
_, kwargs = self.mock_client.send_email.call_args
|
||||||
body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"]
|
body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"]
|
||||||
# spacing should be right between adjacent elements
|
# spacing should be right between adjacent elements
|
||||||
|
@ -173,10 +173,10 @@ class TestEmails(TestCase):
|
||||||
@boto3_mocking.patching
|
@boto3_mocking.patching
|
||||||
def test_submission_confirmation_no_anything_else_spacing(self):
|
def test_submission_confirmation_no_anything_else_spacing(self):
|
||||||
"""Test line spacing without anything else."""
|
"""Test line spacing without anything else."""
|
||||||
application = completed_application(has_anything_else=False)
|
domain_request = completed_domain_request(has_anything_else=False)
|
||||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
application.submit()
|
domain_request.submit()
|
||||||
_, kwargs = self.mock_client.send_email.call_args
|
_, kwargs = self.mock_client.send_email.call_args
|
||||||
body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"]
|
body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"]
|
||||||
self.assertNotIn("Anything else", body)
|
self.assertNotIn("Anything else", body)
|
||||||
|
|
|
@ -4,7 +4,7 @@ import json
|
||||||
from django.test import TestCase, RequestFactory
|
from django.test import TestCase, RequestFactory
|
||||||
from api.views import available
|
from api.views import available
|
||||||
|
|
||||||
from registrar.forms.application_wizard import (
|
from registrar.forms.domain_request_wizard import (
|
||||||
AlternativeDomainForm,
|
AlternativeDomainForm,
|
||||||
CurrentSitesForm,
|
CurrentSitesForm,
|
||||||
DotGovDomainForm,
|
DotGovDomainForm,
|
||||||
|
|
|
@ -34,10 +34,10 @@ class TestGroups(TestCase):
|
||||||
"view_logentry",
|
"view_logentry",
|
||||||
"change_contact",
|
"change_contact",
|
||||||
"view_domain",
|
"view_domain",
|
||||||
"change_domainapplication",
|
|
||||||
"change_domaininformation",
|
"change_domaininformation",
|
||||||
"add_domaininvitation",
|
"add_domaininvitation",
|
||||||
"view_domaininvitation",
|
"view_domaininvitation",
|
||||||
|
"change_domainrequest",
|
||||||
"change_draftdomain",
|
"change_draftdomain",
|
||||||
"analyst_access_permission",
|
"analyst_access_permission",
|
||||||
"change_user",
|
"change_user",
|
||||||
|
|
|
@ -4,7 +4,7 @@ from unittest.mock import patch
|
||||||
|
|
||||||
from registrar.models import (
|
from registrar.models import (
|
||||||
Contact,
|
Contact,
|
||||||
DomainApplication,
|
DomainRequest,
|
||||||
DomainInformation,
|
DomainInformation,
|
||||||
User,
|
User,
|
||||||
Website,
|
Website,
|
||||||
|
@ -17,39 +17,39 @@ from registrar.models import (
|
||||||
import boto3_mocking
|
import boto3_mocking
|
||||||
from registrar.models.transition_domain import TransitionDomain
|
from registrar.models.transition_domain import TransitionDomain
|
||||||
from registrar.models.verified_by_staff import VerifiedByStaff # type: ignore
|
from registrar.models.verified_by_staff import VerifiedByStaff # type: ignore
|
||||||
from .common import MockSESClient, less_console_noise, completed_application
|
from .common import MockSESClient, less_console_noise, completed_domain_request
|
||||||
from django_fsm import TransitionNotAllowed
|
from django_fsm import TransitionNotAllowed
|
||||||
|
|
||||||
|
|
||||||
# Test comment for push -- will remove
|
# Test comment for push -- will remove
|
||||||
# The DomainApplication submit method has a side effect of sending an email
|
# The DomainRequest submit method has a side effect of sending an email
|
||||||
# with AWS SES, so mock that out in all of these test cases
|
# with AWS SES, so mock that out in all of these test cases
|
||||||
@boto3_mocking.patching
|
@boto3_mocking.patching
|
||||||
class TestDomainApplication(TestCase):
|
class TestDomainRequest(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.started_application = completed_application(
|
self.started_domain_request = completed_domain_request(
|
||||||
status=DomainApplication.ApplicationStatus.STARTED, name="started.gov"
|
status=DomainRequest.DomainRequestStatus.STARTED, name="started.gov"
|
||||||
)
|
)
|
||||||
self.submitted_application = completed_application(
|
self.submitted_domain_request = completed_domain_request(
|
||||||
status=DomainApplication.ApplicationStatus.SUBMITTED, name="submitted.gov"
|
status=DomainRequest.DomainRequestStatus.SUBMITTED, name="submitted.gov"
|
||||||
)
|
)
|
||||||
self.in_review_application = completed_application(
|
self.in_review_domain_request = completed_domain_request(
|
||||||
status=DomainApplication.ApplicationStatus.IN_REVIEW, name="in-review.gov"
|
status=DomainRequest.DomainRequestStatus.IN_REVIEW, name="in-review.gov"
|
||||||
)
|
)
|
||||||
self.action_needed_application = completed_application(
|
self.action_needed_domain_request = completed_domain_request(
|
||||||
status=DomainApplication.ApplicationStatus.ACTION_NEEDED, name="action-needed.gov"
|
status=DomainRequest.DomainRequestStatus.ACTION_NEEDED, name="action-needed.gov"
|
||||||
)
|
)
|
||||||
self.approved_application = completed_application(
|
self.approved_domain_request = completed_domain_request(
|
||||||
status=DomainApplication.ApplicationStatus.APPROVED, name="approved.gov"
|
status=DomainRequest.DomainRequestStatus.APPROVED, name="approved.gov"
|
||||||
)
|
)
|
||||||
self.withdrawn_application = completed_application(
|
self.withdrawn_domain_request = completed_domain_request(
|
||||||
status=DomainApplication.ApplicationStatus.WITHDRAWN, name="withdrawn.gov"
|
status=DomainRequest.DomainRequestStatus.WITHDRAWN, name="withdrawn.gov"
|
||||||
)
|
)
|
||||||
self.rejected_application = completed_application(
|
self.rejected_domain_request = completed_domain_request(
|
||||||
status=DomainApplication.ApplicationStatus.REJECTED, name="rejected.gov"
|
status=DomainRequest.DomainRequestStatus.REJECTED, name="rejected.gov"
|
||||||
)
|
)
|
||||||
self.ineligible_application = completed_application(
|
self.ineligible_domain_request = completed_domain_request(
|
||||||
status=DomainApplication.ApplicationStatus.INELIGIBLE, name="ineligible.gov"
|
status=DomainRequest.DomainRequestStatus.INELIGIBLE, name="ineligible.gov"
|
||||||
)
|
)
|
||||||
|
|
||||||
self.mock_client = MockSESClient()
|
self.mock_client = MockSESClient()
|
||||||
|
@ -64,19 +64,19 @@ class TestDomainApplication(TestCase):
|
||||||
return self.assertRaises(Exception, None, exception_type)
|
return self.assertRaises(Exception, None, exception_type)
|
||||||
|
|
||||||
def test_empty_create_fails(self):
|
def test_empty_create_fails(self):
|
||||||
"""Can't create a completely empty domain application.
|
"""Can't create a completely empty domain request.
|
||||||
NOTE: something about theexception this test raises messes up with the
|
NOTE: something about theexception this test raises messes up with the
|
||||||
atomic block in a custom tearDown method for the parent test class."""
|
atomic block in a custom tearDown method for the parent test class."""
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
with self.assertRaisesRegex(IntegrityError, "creator"):
|
with self.assertRaisesRegex(IntegrityError, "creator"):
|
||||||
DomainApplication.objects.create()
|
DomainRequest.objects.create()
|
||||||
|
|
||||||
def test_minimal_create(self):
|
def test_minimal_create(self):
|
||||||
"""Can create with just a creator."""
|
"""Can create with just a creator."""
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
user, _ = User.objects.get_or_create(username="testy")
|
user, _ = User.objects.get_or_create(username="testy")
|
||||||
application = DomainApplication.objects.create(creator=user)
|
domain_request = DomainRequest.objects.create(creator=user)
|
||||||
self.assertEqual(application.status, DomainApplication.ApplicationStatus.STARTED)
|
self.assertEqual(domain_request.status, DomainRequest.DomainRequestStatus.STARTED)
|
||||||
|
|
||||||
def test_full_create(self):
|
def test_full_create(self):
|
||||||
"""Can create with all fields."""
|
"""Can create with all fields."""
|
||||||
|
@ -86,11 +86,11 @@ class TestDomainApplication(TestCase):
|
||||||
com_website, _ = Website.objects.get_or_create(website="igorville.com")
|
com_website, _ = Website.objects.get_or_create(website="igorville.com")
|
||||||
gov_website, _ = Website.objects.get_or_create(website="igorville.gov")
|
gov_website, _ = Website.objects.get_or_create(website="igorville.gov")
|
||||||
domain, _ = DraftDomain.objects.get_or_create(name="igorville.gov")
|
domain, _ = DraftDomain.objects.get_or_create(name="igorville.gov")
|
||||||
application = DomainApplication.objects.create(
|
domain_request = DomainRequest.objects.create(
|
||||||
creator=user,
|
creator=user,
|
||||||
investigator=user,
|
investigator=user,
|
||||||
organization_type=DomainApplication.OrganizationChoices.FEDERAL,
|
organization_type=DomainRequest.OrganizationChoices.FEDERAL,
|
||||||
federal_type=DomainApplication.BranchChoices.EXECUTIVE,
|
federal_type=DomainRequest.BranchChoices.EXECUTIVE,
|
||||||
is_election_board=False,
|
is_election_board=False,
|
||||||
organization_name="Test",
|
organization_name="Test",
|
||||||
address_line1="100 Main St.",
|
address_line1="100 Main St.",
|
||||||
|
@ -104,10 +104,10 @@ class TestDomainApplication(TestCase):
|
||||||
anything_else="All of Igorville loves the dotgov program.",
|
anything_else="All of Igorville loves the dotgov program.",
|
||||||
is_policy_acknowledged=True,
|
is_policy_acknowledged=True,
|
||||||
)
|
)
|
||||||
application.current_websites.add(com_website)
|
domain_request.current_websites.add(com_website)
|
||||||
application.alternative_domains.add(gov_website)
|
domain_request.alternative_domains.add(gov_website)
|
||||||
application.other_contacts.add(contact)
|
domain_request.other_contacts.add(contact)
|
||||||
application.save()
|
domain_request.save()
|
||||||
|
|
||||||
def test_domain_info(self):
|
def test_domain_info(self):
|
||||||
"""Can create domain info with all fields."""
|
"""Can create domain info with all fields."""
|
||||||
|
@ -140,35 +140,35 @@ class TestDomainApplication(TestCase):
|
||||||
def test_status_fsm_submit_fail(self):
|
def test_status_fsm_submit_fail(self):
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
user, _ = User.objects.get_or_create(username="testy")
|
user, _ = User.objects.get_or_create(username="testy")
|
||||||
application = DomainApplication.objects.create(creator=user)
|
domain_request = DomainRequest.objects.create(creator=user)
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
# can't submit an application with a null domain name
|
# can't submit a domain request with a null domain name
|
||||||
application.submit()
|
domain_request.submit()
|
||||||
|
|
||||||
def test_status_fsm_submit_succeed(self):
|
def test_status_fsm_submit_succeed(self):
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
user, _ = User.objects.get_or_create(username="testy")
|
user, _ = User.objects.get_or_create(username="testy")
|
||||||
site = DraftDomain.objects.create(name="igorville.gov")
|
site = DraftDomain.objects.create(name="igorville.gov")
|
||||||
application = DomainApplication.objects.create(creator=user, requested_domain=site)
|
domain_request = DomainRequest.objects.create(creator=user, requested_domain=site)
|
||||||
|
|
||||||
# no submitter email so this emits a log warning
|
# no submitter email so this emits a log warning
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
application.submit()
|
domain_request.submit()
|
||||||
self.assertEqual(application.status, application.ApplicationStatus.SUBMITTED)
|
self.assertEqual(domain_request.status, domain_request.DomainRequestStatus.SUBMITTED)
|
||||||
|
|
||||||
def check_email_sent(self, application, msg, action, expected_count):
|
def check_email_sent(self, domain_request, msg, action, expected_count):
|
||||||
"""Check if an email was sent after performing an action."""
|
"""Check if an email was sent after performing an action."""
|
||||||
|
|
||||||
with self.subTest(msg=msg, action=action):
|
with self.subTest(msg=msg, action=action):
|
||||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
# Perform the specified action
|
# Perform the specified action
|
||||||
action_method = getattr(application, action)
|
action_method = getattr(domain_request, action)
|
||||||
action_method()
|
action_method()
|
||||||
|
|
||||||
# Check if an email was sent
|
# Check if an email was sent
|
||||||
|
@ -180,62 +180,62 @@ class TestDomainApplication(TestCase):
|
||||||
self.assertEqual(len(sent_emails), expected_count)
|
self.assertEqual(len(sent_emails), expected_count)
|
||||||
|
|
||||||
def test_submit_from_started_sends_email(self):
|
def test_submit_from_started_sends_email(self):
|
||||||
msg = "Create an application and submit it and see if email was sent."
|
msg = "Create a domain request and submit it and see if email was sent."
|
||||||
application = completed_application()
|
domain_request = completed_domain_request()
|
||||||
self.check_email_sent(application, msg, "submit", 1)
|
self.check_email_sent(domain_request, msg, "submit", 1)
|
||||||
|
|
||||||
def test_submit_from_withdrawn_sends_email(self):
|
def test_submit_from_withdrawn_sends_email(self):
|
||||||
msg = "Create a withdrawn application and submit it and see if email was sent."
|
msg = "Create a withdrawn domain request and submit it and see if email was sent."
|
||||||
application = completed_application(status=DomainApplication.ApplicationStatus.WITHDRAWN)
|
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.WITHDRAWN)
|
||||||
self.check_email_sent(application, msg, "submit", 1)
|
self.check_email_sent(domain_request, msg, "submit", 1)
|
||||||
|
|
||||||
def test_submit_from_action_needed_does_not_send_email(self):
|
def test_submit_from_action_needed_does_not_send_email(self):
|
||||||
msg = "Create an application with ACTION_NEEDED status and submit it, check if email was not sent."
|
msg = "Create a domain request with ACTION_NEEDED status and submit it, check if email was not sent."
|
||||||
application = completed_application(status=DomainApplication.ApplicationStatus.ACTION_NEEDED)
|
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.ACTION_NEEDED)
|
||||||
self.check_email_sent(application, msg, "submit", 0)
|
self.check_email_sent(domain_request, msg, "submit", 0)
|
||||||
|
|
||||||
def test_submit_from_in_review_does_not_send_email(self):
|
def test_submit_from_in_review_does_not_send_email(self):
|
||||||
msg = "Create a withdrawn application and submit it and see if email was sent."
|
msg = "Create a withdrawn domain request and submit it and see if email was sent."
|
||||||
application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW)
|
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
|
||||||
self.check_email_sent(application, msg, "submit", 0)
|
self.check_email_sent(domain_request, msg, "submit", 0)
|
||||||
|
|
||||||
def test_approve_sends_email(self):
|
def test_approve_sends_email(self):
|
||||||
msg = "Create an application and approve it and see if email was sent."
|
msg = "Create a domain request and approve it and see if email was sent."
|
||||||
application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW)
|
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
|
||||||
self.check_email_sent(application, msg, "approve", 1)
|
self.check_email_sent(domain_request, msg, "approve", 1)
|
||||||
|
|
||||||
def test_withdraw_sends_email(self):
|
def test_withdraw_sends_email(self):
|
||||||
msg = "Create an application and withdraw it and see if email was sent."
|
msg = "Create a domain request and withdraw it and see if email was sent."
|
||||||
application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW)
|
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
|
||||||
self.check_email_sent(application, msg, "withdraw", 1)
|
self.check_email_sent(domain_request, msg, "withdraw", 1)
|
||||||
|
|
||||||
def test_reject_sends_email(self):
|
def test_reject_sends_email(self):
|
||||||
msg = "Create an application and reject it and see if email was sent."
|
msg = "Create a domain request and reject it and see if email was sent."
|
||||||
application = completed_application(status=DomainApplication.ApplicationStatus.APPROVED)
|
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.APPROVED)
|
||||||
self.check_email_sent(application, msg, "reject", 1)
|
self.check_email_sent(domain_request, msg, "reject", 1)
|
||||||
|
|
||||||
def test_reject_with_prejudice_does_not_send_email(self):
|
def test_reject_with_prejudice_does_not_send_email(self):
|
||||||
msg = "Create an application and reject it with prejudice and see if email was sent."
|
msg = "Create a domain request and reject it with prejudice and see if email was sent."
|
||||||
application = completed_application(status=DomainApplication.ApplicationStatus.APPROVED)
|
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.APPROVED)
|
||||||
self.check_email_sent(application, msg, "reject_with_prejudice", 0)
|
self.check_email_sent(domain_request, msg, "reject_with_prejudice", 0)
|
||||||
|
|
||||||
def test_submit_transition_allowed(self):
|
def test_submit_transition_allowed(self):
|
||||||
"""
|
"""
|
||||||
Test that calling submit from allowable statuses does raises TransitionNotAllowed.
|
Test that calling submit from allowable statuses does raises TransitionNotAllowed.
|
||||||
"""
|
"""
|
||||||
test_cases = [
|
test_cases = [
|
||||||
(self.started_application, TransitionNotAllowed),
|
(self.started_domain_request, TransitionNotAllowed),
|
||||||
(self.in_review_application, TransitionNotAllowed),
|
(self.in_review_domain_request, TransitionNotAllowed),
|
||||||
(self.action_needed_application, TransitionNotAllowed),
|
(self.action_needed_domain_request, TransitionNotAllowed),
|
||||||
(self.withdrawn_application, TransitionNotAllowed),
|
(self.withdrawn_domain_request, TransitionNotAllowed),
|
||||||
]
|
]
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
for application, exception_type in test_cases:
|
for domain_request, exception_type in test_cases:
|
||||||
with self.subTest(application=application, exception_type=exception_type):
|
with self.subTest(domain_request=domain_request, exception_type=exception_type):
|
||||||
try:
|
try:
|
||||||
application.submit()
|
domain_request.submit()
|
||||||
except TransitionNotAllowed:
|
except TransitionNotAllowed:
|
||||||
self.fail("TransitionNotAllowed was raised, but it was not expected.")
|
self.fail("TransitionNotAllowed was raised, but it was not expected.")
|
||||||
|
|
||||||
|
@ -244,37 +244,37 @@ class TestDomainApplication(TestCase):
|
||||||
Test that calling submit against transition rules raises TransitionNotAllowed.
|
Test that calling submit against transition rules raises TransitionNotAllowed.
|
||||||
"""
|
"""
|
||||||
test_cases = [
|
test_cases = [
|
||||||
(self.submitted_application, TransitionNotAllowed),
|
(self.submitted_domain_request, TransitionNotAllowed),
|
||||||
(self.approved_application, TransitionNotAllowed),
|
(self.approved_domain_request, TransitionNotAllowed),
|
||||||
(self.rejected_application, TransitionNotAllowed),
|
(self.rejected_domain_request, TransitionNotAllowed),
|
||||||
(self.ineligible_application, TransitionNotAllowed),
|
(self.ineligible_domain_request, TransitionNotAllowed),
|
||||||
]
|
]
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
for application, exception_type in test_cases:
|
for domain_request, exception_type in test_cases:
|
||||||
with self.subTest(application=application, exception_type=exception_type):
|
with self.subTest(domain_request=domain_request, exception_type=exception_type):
|
||||||
with self.assertRaises(exception_type):
|
with self.assertRaises(exception_type):
|
||||||
application.submit()
|
domain_request.submit()
|
||||||
|
|
||||||
def test_in_review_transition_allowed(self):
|
def test_in_review_transition_allowed(self):
|
||||||
"""
|
"""
|
||||||
Test that calling in_review from allowable statuses does raises TransitionNotAllowed.
|
Test that calling in_review from allowable statuses does raises TransitionNotAllowed.
|
||||||
"""
|
"""
|
||||||
test_cases = [
|
test_cases = [
|
||||||
(self.submitted_application, TransitionNotAllowed),
|
(self.submitted_domain_request, TransitionNotAllowed),
|
||||||
(self.action_needed_application, TransitionNotAllowed),
|
(self.action_needed_domain_request, TransitionNotAllowed),
|
||||||
(self.approved_application, TransitionNotAllowed),
|
(self.approved_domain_request, TransitionNotAllowed),
|
||||||
(self.rejected_application, TransitionNotAllowed),
|
(self.rejected_domain_request, TransitionNotAllowed),
|
||||||
(self.ineligible_application, TransitionNotAllowed),
|
(self.ineligible_domain_request, TransitionNotAllowed),
|
||||||
]
|
]
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
for application, exception_type in test_cases:
|
for domain_request, exception_type in test_cases:
|
||||||
with self.subTest(application=application, exception_type=exception_type):
|
with self.subTest(domain_request=domain_request, exception_type=exception_type):
|
||||||
try:
|
try:
|
||||||
application.in_review()
|
domain_request.in_review()
|
||||||
except TransitionNotAllowed:
|
except TransitionNotAllowed:
|
||||||
self.fail("TransitionNotAllowed was raised, but it was not expected.")
|
self.fail("TransitionNotAllowed was raised, but it was not expected.")
|
||||||
|
|
||||||
|
@ -283,33 +283,33 @@ class TestDomainApplication(TestCase):
|
||||||
Test that calling in_review against transition rules raises TransitionNotAllowed.
|
Test that calling in_review against transition rules raises TransitionNotAllowed.
|
||||||
"""
|
"""
|
||||||
test_cases = [
|
test_cases = [
|
||||||
(self.started_application, TransitionNotAllowed),
|
(self.started_domain_request, TransitionNotAllowed),
|
||||||
(self.in_review_application, TransitionNotAllowed),
|
(self.in_review_domain_request, TransitionNotAllowed),
|
||||||
(self.withdrawn_application, TransitionNotAllowed),
|
(self.withdrawn_domain_request, TransitionNotAllowed),
|
||||||
]
|
]
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
for application, exception_type in test_cases:
|
for domain_request, exception_type in test_cases:
|
||||||
with self.subTest(application=application, exception_type=exception_type):
|
with self.subTest(domain_request=domain_request, exception_type=exception_type):
|
||||||
with self.assertRaises(exception_type):
|
with self.assertRaises(exception_type):
|
||||||
application.in_review()
|
domain_request.in_review()
|
||||||
|
|
||||||
def test_action_needed_transition_allowed(self):
|
def test_action_needed_transition_allowed(self):
|
||||||
"""
|
"""
|
||||||
Test that calling action_needed from allowable statuses does raises TransitionNotAllowed.
|
Test that calling action_needed from allowable statuses does raises TransitionNotAllowed.
|
||||||
"""
|
"""
|
||||||
test_cases = [
|
test_cases = [
|
||||||
(self.in_review_application, TransitionNotAllowed),
|
(self.in_review_domain_request, TransitionNotAllowed),
|
||||||
(self.approved_application, TransitionNotAllowed),
|
(self.approved_domain_request, TransitionNotAllowed),
|
||||||
(self.rejected_application, TransitionNotAllowed),
|
(self.rejected_domain_request, TransitionNotAllowed),
|
||||||
(self.ineligible_application, TransitionNotAllowed),
|
(self.ineligible_domain_request, TransitionNotAllowed),
|
||||||
]
|
]
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
for application, exception_type in test_cases:
|
for domain_request, exception_type in test_cases:
|
||||||
with self.subTest(application=application, exception_type=exception_type):
|
with self.subTest(domain_request=domain_request, exception_type=exception_type):
|
||||||
try:
|
try:
|
||||||
application.action_needed()
|
domain_request.action_needed()
|
||||||
except TransitionNotAllowed:
|
except TransitionNotAllowed:
|
||||||
self.fail("TransitionNotAllowed was raised, but it was not expected.")
|
self.fail("TransitionNotAllowed was raised, but it was not expected.")
|
||||||
|
|
||||||
|
@ -318,34 +318,34 @@ class TestDomainApplication(TestCase):
|
||||||
Test that calling action_needed against transition rules raises TransitionNotAllowed.
|
Test that calling action_needed against transition rules raises TransitionNotAllowed.
|
||||||
"""
|
"""
|
||||||
test_cases = [
|
test_cases = [
|
||||||
(self.started_application, TransitionNotAllowed),
|
(self.started_domain_request, TransitionNotAllowed),
|
||||||
(self.submitted_application, TransitionNotAllowed),
|
(self.submitted_domain_request, TransitionNotAllowed),
|
||||||
(self.action_needed_application, TransitionNotAllowed),
|
(self.action_needed_domain_request, TransitionNotAllowed),
|
||||||
(self.withdrawn_application, TransitionNotAllowed),
|
(self.withdrawn_domain_request, TransitionNotAllowed),
|
||||||
]
|
]
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
for application, exception_type in test_cases:
|
for domain_request, exception_type in test_cases:
|
||||||
with self.subTest(application=application, exception_type=exception_type):
|
with self.subTest(domain_request=domain_request, exception_type=exception_type):
|
||||||
with self.assertRaises(exception_type):
|
with self.assertRaises(exception_type):
|
||||||
application.action_needed()
|
domain_request.action_needed()
|
||||||
|
|
||||||
def test_approved_transition_allowed(self):
|
def test_approved_transition_allowed(self):
|
||||||
"""
|
"""
|
||||||
Test that calling action_needed from allowable statuses does raises TransitionNotAllowed.
|
Test that calling action_needed from allowable statuses does raises TransitionNotAllowed.
|
||||||
"""
|
"""
|
||||||
test_cases = [
|
test_cases = [
|
||||||
(self.submitted_application, TransitionNotAllowed),
|
(self.submitted_domain_request, TransitionNotAllowed),
|
||||||
(self.in_review_application, TransitionNotAllowed),
|
(self.in_review_domain_request, TransitionNotAllowed),
|
||||||
(self.action_needed_application, TransitionNotAllowed),
|
(self.action_needed_domain_request, TransitionNotAllowed),
|
||||||
(self.rejected_application, TransitionNotAllowed),
|
(self.rejected_domain_request, TransitionNotAllowed),
|
||||||
]
|
]
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
for application, exception_type in test_cases:
|
for domain_request, exception_type in test_cases:
|
||||||
with self.subTest(application=application, exception_type=exception_type):
|
with self.subTest(domain_request=domain_request, exception_type=exception_type):
|
||||||
try:
|
try:
|
||||||
application.approve()
|
domain_request.approve()
|
||||||
except TransitionNotAllowed:
|
except TransitionNotAllowed:
|
||||||
self.fail("TransitionNotAllowed was raised, but it was not expected.")
|
self.fail("TransitionNotAllowed was raised, but it was not expected.")
|
||||||
|
|
||||||
|
@ -357,7 +357,7 @@ class TestDomainApplication(TestCase):
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
self.submitted_application.approve(send_email=False)
|
self.submitted_domain_request.approve(send_email=False)
|
||||||
|
|
||||||
# Assert that no emails were sent
|
# Assert that no emails were sent
|
||||||
self.assertEqual(len(self.mock_client.EMAILS_SENT), 0)
|
self.assertEqual(len(self.mock_client.EMAILS_SENT), 0)
|
||||||
|
@ -367,35 +367,35 @@ class TestDomainApplication(TestCase):
|
||||||
Test that calling action_needed against transition rules raises TransitionNotAllowed.
|
Test that calling action_needed against transition rules raises TransitionNotAllowed.
|
||||||
"""
|
"""
|
||||||
test_cases = [
|
test_cases = [
|
||||||
(self.started_application, TransitionNotAllowed),
|
(self.started_domain_request, TransitionNotAllowed),
|
||||||
(self.approved_application, TransitionNotAllowed),
|
(self.approved_domain_request, TransitionNotAllowed),
|
||||||
(self.withdrawn_application, TransitionNotAllowed),
|
(self.withdrawn_domain_request, TransitionNotAllowed),
|
||||||
(self.ineligible_application, TransitionNotAllowed),
|
(self.ineligible_domain_request, TransitionNotAllowed),
|
||||||
]
|
]
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
for application, exception_type in test_cases:
|
for domain_request, exception_type in test_cases:
|
||||||
with self.subTest(application=application, exception_type=exception_type):
|
with self.subTest(domain_request=domain_request, exception_type=exception_type):
|
||||||
with self.assertRaises(exception_type):
|
with self.assertRaises(exception_type):
|
||||||
application.approve()
|
domain_request.approve()
|
||||||
|
|
||||||
def test_withdraw_transition_allowed(self):
|
def test_withdraw_transition_allowed(self):
|
||||||
"""
|
"""
|
||||||
Test that calling action_needed from allowable statuses does raises TransitionNotAllowed.
|
Test that calling action_needed from allowable statuses does raises TransitionNotAllowed.
|
||||||
"""
|
"""
|
||||||
test_cases = [
|
test_cases = [
|
||||||
(self.submitted_application, TransitionNotAllowed),
|
(self.submitted_domain_request, TransitionNotAllowed),
|
||||||
(self.in_review_application, TransitionNotAllowed),
|
(self.in_review_domain_request, TransitionNotAllowed),
|
||||||
(self.action_needed_application, TransitionNotAllowed),
|
(self.action_needed_domain_request, TransitionNotAllowed),
|
||||||
]
|
]
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
for application, exception_type in test_cases:
|
for domain_request, exception_type in test_cases:
|
||||||
with self.subTest(application=application, exception_type=exception_type):
|
with self.subTest(domain_request=domain_request, exception_type=exception_type):
|
||||||
try:
|
try:
|
||||||
application.withdraw()
|
domain_request.withdraw()
|
||||||
except TransitionNotAllowed:
|
except TransitionNotAllowed:
|
||||||
self.fail("TransitionNotAllowed was raised, but it was not expected.")
|
self.fail("TransitionNotAllowed was raised, but it was not expected.")
|
||||||
|
|
||||||
|
@ -404,36 +404,36 @@ class TestDomainApplication(TestCase):
|
||||||
Test that calling action_needed against transition rules raises TransitionNotAllowed.
|
Test that calling action_needed against transition rules raises TransitionNotAllowed.
|
||||||
"""
|
"""
|
||||||
test_cases = [
|
test_cases = [
|
||||||
(self.started_application, TransitionNotAllowed),
|
(self.started_domain_request, TransitionNotAllowed),
|
||||||
(self.approved_application, TransitionNotAllowed),
|
(self.approved_domain_request, TransitionNotAllowed),
|
||||||
(self.withdrawn_application, TransitionNotAllowed),
|
(self.withdrawn_domain_request, TransitionNotAllowed),
|
||||||
(self.rejected_application, TransitionNotAllowed),
|
(self.rejected_domain_request, TransitionNotAllowed),
|
||||||
(self.ineligible_application, TransitionNotAllowed),
|
(self.ineligible_domain_request, TransitionNotAllowed),
|
||||||
]
|
]
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
for application, exception_type in test_cases:
|
for domain_request, exception_type in test_cases:
|
||||||
with self.subTest(application=application, exception_type=exception_type):
|
with self.subTest(domain_request=domain_request, exception_type=exception_type):
|
||||||
with self.assertRaises(exception_type):
|
with self.assertRaises(exception_type):
|
||||||
application.withdraw()
|
domain_request.withdraw()
|
||||||
|
|
||||||
def test_reject_transition_allowed(self):
|
def test_reject_transition_allowed(self):
|
||||||
"""
|
"""
|
||||||
Test that calling action_needed from allowable statuses does raises TransitionNotAllowed.
|
Test that calling action_needed from allowable statuses does raises TransitionNotAllowed.
|
||||||
"""
|
"""
|
||||||
test_cases = [
|
test_cases = [
|
||||||
(self.in_review_application, TransitionNotAllowed),
|
(self.in_review_domain_request, TransitionNotAllowed),
|
||||||
(self.action_needed_application, TransitionNotAllowed),
|
(self.action_needed_domain_request, TransitionNotAllowed),
|
||||||
(self.approved_application, TransitionNotAllowed),
|
(self.approved_domain_request, TransitionNotAllowed),
|
||||||
]
|
]
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
for application, exception_type in test_cases:
|
for domain_request, exception_type in test_cases:
|
||||||
with self.subTest(application=application, exception_type=exception_type):
|
with self.subTest(domain_request=domain_request, exception_type=exception_type):
|
||||||
try:
|
try:
|
||||||
application.reject()
|
domain_request.reject()
|
||||||
except TransitionNotAllowed:
|
except TransitionNotAllowed:
|
||||||
self.fail("TransitionNotAllowed was raised, but it was not expected.")
|
self.fail("TransitionNotAllowed was raised, but it was not expected.")
|
||||||
|
|
||||||
|
@ -442,37 +442,37 @@ class TestDomainApplication(TestCase):
|
||||||
Test that calling action_needed against transition rules raises TransitionNotAllowed.
|
Test that calling action_needed against transition rules raises TransitionNotAllowed.
|
||||||
"""
|
"""
|
||||||
test_cases = [
|
test_cases = [
|
||||||
(self.started_application, TransitionNotAllowed),
|
(self.started_domain_request, TransitionNotAllowed),
|
||||||
(self.submitted_application, TransitionNotAllowed),
|
(self.submitted_domain_request, TransitionNotAllowed),
|
||||||
(self.withdrawn_application, TransitionNotAllowed),
|
(self.withdrawn_domain_request, TransitionNotAllowed),
|
||||||
(self.rejected_application, TransitionNotAllowed),
|
(self.rejected_domain_request, TransitionNotAllowed),
|
||||||
(self.ineligible_application, TransitionNotAllowed),
|
(self.ineligible_domain_request, TransitionNotAllowed),
|
||||||
]
|
]
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
for application, exception_type in test_cases:
|
for domain_request, exception_type in test_cases:
|
||||||
with self.subTest(application=application, exception_type=exception_type):
|
with self.subTest(domain_request=domain_request, exception_type=exception_type):
|
||||||
with self.assertRaises(exception_type):
|
with self.assertRaises(exception_type):
|
||||||
application.reject()
|
domain_request.reject()
|
||||||
|
|
||||||
def test_reject_with_prejudice_transition_allowed(self):
|
def test_reject_with_prejudice_transition_allowed(self):
|
||||||
"""
|
"""
|
||||||
Test that calling action_needed from allowable statuses does raises TransitionNotAllowed.
|
Test that calling action_needed from allowable statuses does raises TransitionNotAllowed.
|
||||||
"""
|
"""
|
||||||
test_cases = [
|
test_cases = [
|
||||||
(self.in_review_application, TransitionNotAllowed),
|
(self.in_review_domain_request, TransitionNotAllowed),
|
||||||
(self.action_needed_application, TransitionNotAllowed),
|
(self.action_needed_domain_request, TransitionNotAllowed),
|
||||||
(self.approved_application, TransitionNotAllowed),
|
(self.approved_domain_request, TransitionNotAllowed),
|
||||||
(self.rejected_application, TransitionNotAllowed),
|
(self.rejected_domain_request, TransitionNotAllowed),
|
||||||
]
|
]
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
for application, exception_type in test_cases:
|
for domain_request, exception_type in test_cases:
|
||||||
with self.subTest(application=application, exception_type=exception_type):
|
with self.subTest(domain_request=domain_request, exception_type=exception_type):
|
||||||
try:
|
try:
|
||||||
application.reject_with_prejudice()
|
domain_request.reject_with_prejudice()
|
||||||
except TransitionNotAllowed:
|
except TransitionNotAllowed:
|
||||||
self.fail("TransitionNotAllowed was raised, but it was not expected.")
|
self.fail("TransitionNotAllowed was raised, but it was not expected.")
|
||||||
|
|
||||||
|
@ -481,26 +481,26 @@ class TestDomainApplication(TestCase):
|
||||||
Test that calling action_needed against transition rules raises TransitionNotAllowed.
|
Test that calling action_needed against transition rules raises TransitionNotAllowed.
|
||||||
"""
|
"""
|
||||||
test_cases = [
|
test_cases = [
|
||||||
(self.started_application, TransitionNotAllowed),
|
(self.started_domain_request, TransitionNotAllowed),
|
||||||
(self.submitted_application, TransitionNotAllowed),
|
(self.submitted_domain_request, TransitionNotAllowed),
|
||||||
(self.withdrawn_application, TransitionNotAllowed),
|
(self.withdrawn_domain_request, TransitionNotAllowed),
|
||||||
(self.ineligible_application, TransitionNotAllowed),
|
(self.ineligible_domain_request, TransitionNotAllowed),
|
||||||
]
|
]
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
for application, exception_type in test_cases:
|
for domain_request, exception_type in test_cases:
|
||||||
with self.subTest(application=application, exception_type=exception_type):
|
with self.subTest(domain_request=domain_request, exception_type=exception_type):
|
||||||
with self.assertRaises(exception_type):
|
with self.assertRaises(exception_type):
|
||||||
application.reject_with_prejudice()
|
domain_request.reject_with_prejudice()
|
||||||
|
|
||||||
def test_transition_not_allowed_approved_in_review_when_domain_is_active(self):
|
def test_transition_not_allowed_approved_in_review_when_domain_is_active(self):
|
||||||
"""Create an application with status approved, create a matching domain that
|
"""Create a domain request with status approved, create a matching domain that
|
||||||
is active, and call in_review against transition rules"""
|
is active, and call in_review against transition rules"""
|
||||||
|
|
||||||
domain = Domain.objects.create(name=self.approved_application.requested_domain.name)
|
domain = Domain.objects.create(name=self.approved_domain_request.requested_domain.name)
|
||||||
self.approved_application.approved_domain = domain
|
self.approved_domain_request.approved_domain = domain
|
||||||
self.approved_application.save()
|
self.approved_domain_request.save()
|
||||||
|
|
||||||
# Define a custom implementation for is_active
|
# Define a custom implementation for is_active
|
||||||
def custom_is_active(self):
|
def custom_is_active(self):
|
||||||
|
@ -512,15 +512,15 @@ class TestDomainApplication(TestCase):
|
||||||
with patch.object(Domain, "is_active", custom_is_active):
|
with patch.object(Domain, "is_active", custom_is_active):
|
||||||
# Now, when you call is_active on Domain, it will return True
|
# Now, when you call is_active on Domain, it will return True
|
||||||
with self.assertRaises(TransitionNotAllowed):
|
with self.assertRaises(TransitionNotAllowed):
|
||||||
self.approved_application.in_review()
|
self.approved_domain_request.in_review()
|
||||||
|
|
||||||
def test_transition_not_allowed_approved_action_needed_when_domain_is_active(self):
|
def test_transition_not_allowed_approved_action_needed_when_domain_is_active(self):
|
||||||
"""Create an application with status approved, create a matching domain that
|
"""Create a domain request with status approved, create a matching domain that
|
||||||
is active, and call action_needed against transition rules"""
|
is active, and call action_needed against transition rules"""
|
||||||
|
|
||||||
domain = Domain.objects.create(name=self.approved_application.requested_domain.name)
|
domain = Domain.objects.create(name=self.approved_domain_request.requested_domain.name)
|
||||||
self.approved_application.approved_domain = domain
|
self.approved_domain_request.approved_domain = domain
|
||||||
self.approved_application.save()
|
self.approved_domain_request.save()
|
||||||
|
|
||||||
# Define a custom implementation for is_active
|
# Define a custom implementation for is_active
|
||||||
def custom_is_active(self):
|
def custom_is_active(self):
|
||||||
|
@ -532,15 +532,15 @@ class TestDomainApplication(TestCase):
|
||||||
with patch.object(Domain, "is_active", custom_is_active):
|
with patch.object(Domain, "is_active", custom_is_active):
|
||||||
# Now, when you call is_active on Domain, it will return True
|
# Now, when you call is_active on Domain, it will return True
|
||||||
with self.assertRaises(TransitionNotAllowed):
|
with self.assertRaises(TransitionNotAllowed):
|
||||||
self.approved_application.action_needed()
|
self.approved_domain_request.action_needed()
|
||||||
|
|
||||||
def test_transition_not_allowed_approved_rejected_when_domain_is_active(self):
|
def test_transition_not_allowed_approved_rejected_when_domain_is_active(self):
|
||||||
"""Create an application with status approved, create a matching domain that
|
"""Create a domain request with status approved, create a matching domain that
|
||||||
is active, and call reject against transition rules"""
|
is active, and call reject against transition rules"""
|
||||||
|
|
||||||
domain = Domain.objects.create(name=self.approved_application.requested_domain.name)
|
domain = Domain.objects.create(name=self.approved_domain_request.requested_domain.name)
|
||||||
self.approved_application.approved_domain = domain
|
self.approved_domain_request.approved_domain = domain
|
||||||
self.approved_application.save()
|
self.approved_domain_request.save()
|
||||||
|
|
||||||
# Define a custom implementation for is_active
|
# Define a custom implementation for is_active
|
||||||
def custom_is_active(self):
|
def custom_is_active(self):
|
||||||
|
@ -552,15 +552,15 @@ class TestDomainApplication(TestCase):
|
||||||
with patch.object(Domain, "is_active", custom_is_active):
|
with patch.object(Domain, "is_active", custom_is_active):
|
||||||
# Now, when you call is_active on Domain, it will return True
|
# Now, when you call is_active on Domain, it will return True
|
||||||
with self.assertRaises(TransitionNotAllowed):
|
with self.assertRaises(TransitionNotAllowed):
|
||||||
self.approved_application.reject()
|
self.approved_domain_request.reject()
|
||||||
|
|
||||||
def test_transition_not_allowed_approved_ineligible_when_domain_is_active(self):
|
def test_transition_not_allowed_approved_ineligible_when_domain_is_active(self):
|
||||||
"""Create an application with status approved, create a matching domain that
|
"""Create a domain request with status approved, create a matching domain that
|
||||||
is active, and call reject_with_prejudice against transition rules"""
|
is active, and call reject_with_prejudice against transition rules"""
|
||||||
|
|
||||||
domain = Domain.objects.create(name=self.approved_application.requested_domain.name)
|
domain = Domain.objects.create(name=self.approved_domain_request.requested_domain.name)
|
||||||
self.approved_application.approved_domain = domain
|
self.approved_domain_request.approved_domain = domain
|
||||||
self.approved_application.save()
|
self.approved_domain_request.save()
|
||||||
|
|
||||||
# Define a custom implementation for is_active
|
# Define a custom implementation for is_active
|
||||||
def custom_is_active(self):
|
def custom_is_active(self):
|
||||||
|
@ -572,83 +572,83 @@ class TestDomainApplication(TestCase):
|
||||||
with patch.object(Domain, "is_active", custom_is_active):
|
with patch.object(Domain, "is_active", custom_is_active):
|
||||||
# Now, when you call is_active on Domain, it will return True
|
# Now, when you call is_active on Domain, it will return True
|
||||||
with self.assertRaises(TransitionNotAllowed):
|
with self.assertRaises(TransitionNotAllowed):
|
||||||
self.approved_application.reject_with_prejudice()
|
self.approved_domain_request.reject_with_prejudice()
|
||||||
|
|
||||||
def test_approve_from_rejected_clears_rejection_reason(self):
|
def test_approve_from_rejected_clears_rejection_reason(self):
|
||||||
"""When transitioning from rejected to approved on a domain request,
|
"""When transitioning from rejected to approved on a domain request,
|
||||||
the rejection_reason is cleared."""
|
the rejection_reason is cleared."""
|
||||||
|
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
# Create a sample application
|
# Create a sample domain request
|
||||||
application = completed_application(status=DomainApplication.ApplicationStatus.REJECTED)
|
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.REJECTED)
|
||||||
application.rejection_reason = DomainApplication.RejectionReasons.DOMAIN_PURPOSE
|
domain_request.rejection_reason = DomainRequest.RejectionReasons.DOMAIN_PURPOSE
|
||||||
|
|
||||||
# Approve
|
# Approve
|
||||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
application.approve()
|
domain_request.approve()
|
||||||
|
|
||||||
self.assertEqual(application.status, DomainApplication.ApplicationStatus.APPROVED)
|
self.assertEqual(domain_request.status, DomainRequest.DomainRequestStatus.APPROVED)
|
||||||
self.assertEqual(application.rejection_reason, None)
|
self.assertEqual(domain_request.rejection_reason, None)
|
||||||
|
|
||||||
def test_in_review_from_rejected_clears_rejection_reason(self):
|
def test_in_review_from_rejected_clears_rejection_reason(self):
|
||||||
"""When transitioning from rejected to in_review on a domain request,
|
"""When transitioning from rejected to in_review on a domain request,
|
||||||
the rejection_reason is cleared."""
|
the rejection_reason is cleared."""
|
||||||
|
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
# Create a sample application
|
# Create a sample domain request
|
||||||
application = completed_application(status=DomainApplication.ApplicationStatus.REJECTED)
|
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.REJECTED)
|
||||||
application.domain_is_not_active = True
|
domain_request.domain_is_not_active = True
|
||||||
application.rejection_reason = DomainApplication.RejectionReasons.DOMAIN_PURPOSE
|
domain_request.rejection_reason = DomainRequest.RejectionReasons.DOMAIN_PURPOSE
|
||||||
|
|
||||||
# Approve
|
# Approve
|
||||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
application.in_review()
|
domain_request.in_review()
|
||||||
|
|
||||||
self.assertEqual(application.status, DomainApplication.ApplicationStatus.IN_REVIEW)
|
self.assertEqual(domain_request.status, DomainRequest.DomainRequestStatus.IN_REVIEW)
|
||||||
self.assertEqual(application.rejection_reason, None)
|
self.assertEqual(domain_request.rejection_reason, None)
|
||||||
|
|
||||||
def test_action_needed_from_rejected_clears_rejection_reason(self):
|
def test_action_needed_from_rejected_clears_rejection_reason(self):
|
||||||
"""When transitioning from rejected to action_needed on a domain request,
|
"""When transitioning from rejected to action_needed on a domain request,
|
||||||
the rejection_reason is cleared."""
|
the rejection_reason is cleared."""
|
||||||
|
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
# Create a sample application
|
# Create a sample domain request
|
||||||
application = completed_application(status=DomainApplication.ApplicationStatus.REJECTED)
|
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.REJECTED)
|
||||||
application.domain_is_not_active = True
|
domain_request.domain_is_not_active = True
|
||||||
application.rejection_reason = DomainApplication.RejectionReasons.DOMAIN_PURPOSE
|
domain_request.rejection_reason = DomainRequest.RejectionReasons.DOMAIN_PURPOSE
|
||||||
|
|
||||||
# Approve
|
# Approve
|
||||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
application.action_needed()
|
domain_request.action_needed()
|
||||||
|
|
||||||
self.assertEqual(application.status, DomainApplication.ApplicationStatus.ACTION_NEEDED)
|
self.assertEqual(domain_request.status, DomainRequest.DomainRequestStatus.ACTION_NEEDED)
|
||||||
self.assertEqual(application.rejection_reason, None)
|
self.assertEqual(domain_request.rejection_reason, None)
|
||||||
|
|
||||||
def test_has_rationale_returns_true(self):
|
def test_has_rationale_returns_true(self):
|
||||||
"""has_rationale() returns true when an application has no_other_contacts_rationale"""
|
"""has_rationale() returns true when a domain request has no_other_contacts_rationale"""
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
self.started_application.no_other_contacts_rationale = "You talkin' to me?"
|
self.started_domain_request.no_other_contacts_rationale = "You talkin' to me?"
|
||||||
self.started_application.save()
|
self.started_domain_request.save()
|
||||||
self.assertEquals(self.started_application.has_rationale(), True)
|
self.assertEquals(self.started_domain_request.has_rationale(), True)
|
||||||
|
|
||||||
def test_has_rationale_returns_false(self):
|
def test_has_rationale_returns_false(self):
|
||||||
"""has_rationale() returns false when an application has no no_other_contacts_rationale"""
|
"""has_rationale() returns false when a domain request has no no_other_contacts_rationale"""
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
self.assertEquals(self.started_application.has_rationale(), False)
|
self.assertEquals(self.started_domain_request.has_rationale(), False)
|
||||||
|
|
||||||
def test_has_other_contacts_returns_true(self):
|
def test_has_other_contacts_returns_true(self):
|
||||||
"""has_other_contacts() returns true when an application has other_contacts"""
|
"""has_other_contacts() returns true when a domain request has other_contacts"""
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
# completed_application has other contacts by default
|
# completed_domain_request has other contacts by default
|
||||||
self.assertEquals(self.started_application.has_other_contacts(), True)
|
self.assertEquals(self.started_domain_request.has_other_contacts(), True)
|
||||||
|
|
||||||
def test_has_other_contacts_returns_false(self):
|
def test_has_other_contacts_returns_false(self):
|
||||||
"""has_other_contacts() returns false when an application has no other_contacts"""
|
"""has_other_contacts() returns false when a domain request has no other_contacts"""
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
application = completed_application(
|
domain_request = completed_domain_request(
|
||||||
status=DomainApplication.ApplicationStatus.STARTED, name="no-others.gov", has_other_contacts=False
|
status=DomainRequest.DomainRequestStatus.STARTED, name="no-others.gov", has_other_contacts=False
|
||||||
)
|
)
|
||||||
self.assertEquals(application.has_other_contacts(), False)
|
self.assertEquals(domain_request.has_other_contacts(), False)
|
||||||
|
|
||||||
|
|
||||||
class TestPermissions(TestCase):
|
class TestPermissions(TestCase):
|
||||||
|
@ -666,13 +666,13 @@ 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(creator=user, requested_domain=draft_domain)
|
domain_request = DomainRequest.objects.create(creator=user, requested_domain=draft_domain)
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
# skip using the submit method
|
# skip using the submit method
|
||||||
application.status = DomainApplication.ApplicationStatus.SUBMITTED
|
domain_request.status = DomainRequest.DomainRequestStatus.SUBMITTED
|
||||||
application.approve()
|
domain_request.approve()
|
||||||
|
|
||||||
# should be a role for this user
|
# should be a role for this user
|
||||||
domain = Domain.objects.get(name="igorville.gov")
|
domain = Domain.objects.get(name="igorville.gov")
|
||||||
|
@ -691,7 +691,7 @@ class TestDomainInformation(TestCase):
|
||||||
self.mock_client.EMAILS_SENT.clear()
|
self.mock_client.EMAILS_SENT.clear()
|
||||||
Domain.objects.all().delete()
|
Domain.objects.all().delete()
|
||||||
DomainInformation.objects.all().delete()
|
DomainInformation.objects.all().delete()
|
||||||
DomainApplication.objects.all().delete()
|
DomainRequest.objects.all().delete()
|
||||||
User.objects.all().delete()
|
User.objects.all().delete()
|
||||||
DraftDomain.objects.all().delete()
|
DraftDomain.objects.all().delete()
|
||||||
|
|
||||||
|
@ -700,13 +700,13 @@ class TestDomainInformation(TestCase):
|
||||||
self.maxDiff = None
|
self.maxDiff = None
|
||||||
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(creator=user, requested_domain=draft_domain, notes="test notes")
|
domain_request = DomainRequest.objects.create(creator=user, requested_domain=draft_domain, notes="test notes")
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
# skip using the submit method
|
# skip using the submit method
|
||||||
application.status = DomainApplication.ApplicationStatus.SUBMITTED
|
domain_request.status = DomainRequest.DomainRequestStatus.SUBMITTED
|
||||||
application.approve()
|
domain_request.approve()
|
||||||
|
|
||||||
# should be an information present for this domain
|
# should be an information present for this domain
|
||||||
domain = Domain.objects.get(name="igorville.gov")
|
domain = Domain.objects.get(name="igorville.gov")
|
||||||
|
@ -719,7 +719,7 @@ class TestDomainInformation(TestCase):
|
||||||
creator=user,
|
creator=user,
|
||||||
domain=domain,
|
domain=domain,
|
||||||
notes="test notes",
|
notes="test notes",
|
||||||
domain_application=application,
|
domain_request=domain_request,
|
||||||
).__dict__
|
).__dict__
|
||||||
|
|
||||||
# Test the two records for consistency
|
# Test the two records for consistency
|
||||||
|
@ -852,11 +852,11 @@ class TestContact(TestCase):
|
||||||
self.contact, _ = Contact.objects.get_or_create(user=self.user)
|
self.contact, _ = Contact.objects.get_or_create(user=self.user)
|
||||||
|
|
||||||
self.contact_as_ao, _ = Contact.objects.get_or_create(email="newguy@igorville.gov")
|
self.contact_as_ao, _ = Contact.objects.get_or_create(email="newguy@igorville.gov")
|
||||||
self.application = DomainApplication.objects.create(creator=self.user, authorizing_official=self.contact_as_ao)
|
self.domain_request = DomainRequest.objects.create(creator=self.user, authorizing_official=self.contact_as_ao)
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
super().tearDown()
|
super().tearDown()
|
||||||
DomainApplication.objects.all().delete()
|
DomainRequest.objects.all().delete()
|
||||||
Contact.objects.all().delete()
|
Contact.objects.all().delete()
|
||||||
User.objects.all().delete()
|
User.objects.all().delete()
|
||||||
|
|
||||||
|
@ -936,6 +936,6 @@ class TestContact(TestCase):
|
||||||
# test for a contact which has one user defined
|
# test for a contact which has one user defined
|
||||||
self.assertFalse(self.contact.has_more_than_one_join("user"))
|
self.assertFalse(self.contact.has_more_than_one_join("user"))
|
||||||
self.assertTrue(self.contact.has_more_than_one_join("authorizing_official"))
|
self.assertTrue(self.contact.has_more_than_one_join("authorizing_official"))
|
||||||
# test for a contact which is assigned as an authorizing official on an application
|
# test for a contact which is assigned as an authorizing official on a domain request
|
||||||
self.assertFalse(self.contact_as_ao.has_more_than_one_join("authorizing_official"))
|
self.assertFalse(self.contact_as_ao.has_more_than_one_join("authorizing_official"))
|
||||||
self.assertTrue(self.contact_as_ao.has_more_than_one_join("submitted_applications"))
|
self.assertTrue(self.contact_as_ao.has_more_than_one_join("submitted_domain_requests"))
|
||||||
|
|
|
@ -12,7 +12,7 @@ from django.utils.timezone import make_aware
|
||||||
from registrar.models import Domain, Host, HostIP
|
from registrar.models import Domain, Host, HostIP
|
||||||
|
|
||||||
from unittest import skip
|
from unittest import skip
|
||||||
from registrar.models.domain_application import DomainApplication
|
from registrar.models.domain_request import DomainRequest
|
||||||
from registrar.models.domain_information import DomainInformation
|
from registrar.models.domain_information import DomainInformation
|
||||||
from registrar.models.draft_domain import DraftDomain
|
from registrar.models.draft_domain import DraftDomain
|
||||||
from registrar.models.public_contact import PublicContact
|
from registrar.models.public_contact import PublicContact
|
||||||
|
@ -311,27 +311,27 @@ class TestDomainCache(MockEppLib):
|
||||||
|
|
||||||
|
|
||||||
class TestDomainCreation(MockEppLib):
|
class TestDomainCreation(MockEppLib):
|
||||||
"""Rule: An approved domain application must result in a domain"""
|
"""Rule: An approved domain request must result in a domain"""
|
||||||
|
|
||||||
@boto3_mocking.patching
|
@boto3_mocking.patching
|
||||||
def test_approved_application_creates_domain_locally(self):
|
def test_approved_domain_request_creates_domain_locally(self):
|
||||||
"""
|
"""
|
||||||
Scenario: Analyst approves a domain application
|
Scenario: Analyst approves a domain request
|
||||||
When the DomainApplication transitions to approved
|
When the DomainRequest transitions to approved
|
||||||
Then a Domain exists in the database with the same `name`
|
Then a Domain exists in the database with the same `name`
|
||||||
But a domain object does not exist in the registry
|
But a domain object does not exist in the registry
|
||||||
"""
|
"""
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
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(creator=user, requested_domain=draft_domain)
|
domain_request = DomainRequest.objects.create(creator=user, requested_domain=draft_domain)
|
||||||
|
|
||||||
mock_client = MockSESClient()
|
mock_client = MockSESClient()
|
||||||
with boto3_mocking.clients.handler_for("sesv2", mock_client):
|
with boto3_mocking.clients.handler_for("sesv2", mock_client):
|
||||||
# skip using the submit method
|
# skip using the submit method
|
||||||
application.status = DomainApplication.ApplicationStatus.SUBMITTED
|
domain_request.status = DomainRequest.DomainRequestStatus.SUBMITTED
|
||||||
# transition to approve state
|
# transition to approve state
|
||||||
application.approve()
|
domain_request.approve()
|
||||||
# should have information present for this domain
|
# should have information present for this domain
|
||||||
domain = Domain.objects.get(name="igorville.gov")
|
domain = Domain.objects.get(name="igorville.gov")
|
||||||
self.assertTrue(domain)
|
self.assertTrue(domain)
|
||||||
|
@ -395,7 +395,7 @@ class TestDomainCreation(MockEppLib):
|
||||||
|
|
||||||
def tearDown(self) -> None:
|
def tearDown(self) -> None:
|
||||||
DomainInformation.objects.all().delete()
|
DomainInformation.objects.all().delete()
|
||||||
DomainApplication.objects.all().delete()
|
DomainRequest.objects.all().delete()
|
||||||
PublicContact.objects.all().delete()
|
PublicContact.objects.all().delete()
|
||||||
Domain.objects.all().delete()
|
Domain.objects.all().delete()
|
||||||
User.objects.all().delete()
|
User.objects.all().delete()
|
||||||
|
|
|
@ -27,7 +27,7 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class TestProcessedMigrations(TestCase):
|
class TestProcessedMigrations(TestCase):
|
||||||
"""This test case class is designed to verify the idempotency of migrations
|
"""This test case class is designed to verify the idempotency of migrations
|
||||||
related to domain transitions in the application."""
|
related to domain transitions in the domain_request."""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
"""Defines the file name of migration_json and the folder its contained in"""
|
"""Defines the file name of migration_json and the folder its contained in"""
|
||||||
|
|
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