mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-08-12 04:29:44 +02:00
merged and resolved conflict
This commit is contained in:
commit
b6926537d8
17 changed files with 463 additions and 200 deletions
1
.github/workflows/deploy-sandbox.yaml
vendored
1
.github/workflows/deploy-sandbox.yaml
vendored
|
@ -17,6 +17,7 @@ jobs:
|
||||||
|| startsWith(github.head_ref, 'ab/')
|
|| startsWith(github.head_ref, 'ab/')
|
||||||
|| startsWith(github.head_ref, 'bl/')
|
|| startsWith(github.head_ref, 'bl/')
|
||||||
|| startsWith(github.head_ref, 'rjm/')
|
|| startsWith(github.head_ref, 'rjm/')
|
||||||
|
|| startsWith(github.head_ref, 'rb/')
|
||||||
|| startsWith(github.head_ref, 'ko/')
|
|| startsWith(github.head_ref, 'ko/')
|
||||||
|| startsWith(github.head_ref, 'gd/')
|
|| startsWith(github.head_ref, 'gd/')
|
||||||
outputs:
|
outputs:
|
||||||
|
|
1
.github/workflows/migrate.yaml
vendored
1
.github/workflows/migrate.yaml
vendored
|
@ -15,6 +15,7 @@ on:
|
||||||
options:
|
options:
|
||||||
- stable
|
- stable
|
||||||
- gd
|
- gd
|
||||||
|
- rb
|
||||||
- ko
|
- ko
|
||||||
- ab
|
- ab
|
||||||
- bl
|
- bl
|
||||||
|
|
1
.github/workflows/reset-db.yaml
vendored
1
.github/workflows/reset-db.yaml
vendored
|
@ -16,6 +16,7 @@ on:
|
||||||
options:
|
options:
|
||||||
- stable
|
- stable
|
||||||
- gd
|
- gd
|
||||||
|
- rb
|
||||||
- ko
|
- ko
|
||||||
- ab
|
- ab
|
||||||
- bl
|
- bl
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
# Operations
|
# Operations
|
||||||
========================
|
|
||||||
|
|
||||||
Some basic information and setup steps are included in this README.
|
Some basic information and setup steps are included in this README.
|
||||||
|
|
||||||
|
@ -46,3 +45,114 @@ Your sandbox space should've been setup as part of the onboarding process. If th
|
||||||
We are using [WhiteNoise](http://whitenoise.evans.io/en/stable/index.html) plugin to serve our static assets on cloud.gov. This plugin is added to the `MIDDLEWARE` list in our apps `settings.py`.
|
We are using [WhiteNoise](http://whitenoise.evans.io/en/stable/index.html) plugin to serve our static assets on cloud.gov. This plugin is added to the `MIDDLEWARE` list in our apps `settings.py`.
|
||||||
|
|
||||||
Note that it’s a good idea to run `collectstatic` locally or in the docker container before pushing files up to your sandbox. This is because `collectstatic` relies on timestamps when deciding to whether to overwrite the existing assets in `/public`. Due the way files are uploaded, the compiled css in the `/assets/css` folder on your sandbox will have a slightly earlier timestamp than the files in `/public/css`, and consequently running `collectstatic` on your sandbox will not update `public/css` as you may expect. For convenience, both the `deploy.sh` and `build.sh` scripts will take care of that.
|
Note that it’s a good idea to run `collectstatic` locally or in the docker container before pushing files up to your sandbox. This is because `collectstatic` relies on timestamps when deciding to whether to overwrite the existing assets in `/public`. Due the way files are uploaded, the compiled css in the `/assets/css` folder on your sandbox will have a slightly earlier timestamp than the files in `/public/css`, and consequently running `collectstatic` on your sandbox will not update `public/css` as you may expect. For convenience, both the `deploy.sh` and `build.sh` scripts will take care of that.
|
||||||
|
|
||||||
|
# Debugging
|
||||||
|
|
||||||
|
Debugging errors observed in applications running on Cloud.gov requires being
|
||||||
|
able to see the log information from the environment that the application is
|
||||||
|
running in. There are (at least) three different ways to see that information:
|
||||||
|
Cloud.gov dashboard, CloudFoundry CLI application, and Cloud.gov Kibana logging
|
||||||
|
queries. There is also SSH access into Cloud.gov containers and Github Actions
|
||||||
|
that can be used for specific tasks.
|
||||||
|
|
||||||
|
## Cloud.gov dashboard
|
||||||
|
|
||||||
|
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
|
||||||
|
goes to a screen for that individual application, e.g.
|
||||||
|
<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.
|
||||||
|
<https://dashboard.fr.cloud.gov/applications/2oBn9LBurIXUNpfmtZCQTCHnxUM/53b88024-1492-46aa-8fb6-1429bdb35f95/log-stream>.
|
||||||
|
That log stream shows a stream of Cloud.gov log messages. Cloud.gov has
|
||||||
|
different layers that log requests. One is `RTR` which is the router within
|
||||||
|
Cloud.gov. Messages from our Django app are prefixed with `APP/PROC/WEB`. While
|
||||||
|
it is possible to search inside the browser for particular log messages, this
|
||||||
|
is not a sophisticated interface for querying logs.
|
||||||
|
|
||||||
|
## CloudFoundry CLI
|
||||||
|
|
||||||
|
When logged in with the CloudFoundry CLI (see
|
||||||
|
[above](#authenticating-to-cloudgov-via-the-command-line)) Cloudfoundry
|
||||||
|
application logs can be viewed with the `cf logs <application>` where
|
||||||
|
`<application>` is the name of the application in the currently targeted space.
|
||||||
|
By default `cf logs` starts a streaming view of log messages from the
|
||||||
|
application. It appears to show the same information as the dashboard web
|
||||||
|
application, but in the terminal. There is a `--recent` option that will dump
|
||||||
|
things that happened prior to the current time rather than starting a stream of
|
||||||
|
the present log messages, but that is also not a full log archive and search
|
||||||
|
system.
|
||||||
|
|
||||||
|
CloudFoundry also offers a `run-task` command that can be used to run a single
|
||||||
|
command in the running Cloud.gov container. For example, to run our Django
|
||||||
|
admin command that loads test fixture data:
|
||||||
|
|
||||||
|
```
|
||||||
|
cf run-task getgov-{environment} --command "./manage.py load" --name fixtures
|
||||||
|
```
|
||||||
|
|
||||||
|
However, this task runs asynchronously in the background without any command
|
||||||
|
output, so it can sometimes be hard to know if the command has completed and if
|
||||||
|
so, if it was successful.
|
||||||
|
|
||||||
|
## Cloud.gov Kibana
|
||||||
|
|
||||||
|
Cloud.gov provides an instance of the log query program Kibana at
|
||||||
|
<https://logs.fr.cloud.gov>. Kibana is powerful, but also complicated software
|
||||||
|
that can take time to learn how to use most effectively. A few hints:
|
||||||
|
|
||||||
|
- Set the timeframe of the display appropriately, the default is the last
|
||||||
|
15 minutes which may not show any results in some environments.
|
||||||
|
|
||||||
|
- Kibana queries and filters can be used to narrow in on particular
|
||||||
|
environments. Try the query `@source.type:APP` to focus on messages from the
|
||||||
|
Django application or `@cf.app:"getgov-{environment}"` to see results from a single
|
||||||
|
environment.
|
||||||
|
|
||||||
|
Currently, our application emits Python's default log format which is textual
|
||||||
|
and not record-based. In particular, tracebacks are on multiple lines and show
|
||||||
|
up in Kibana as multiple records that are not necessarily connected. As the
|
||||||
|
application gets closer to production, we may want to switch to a JSON log format
|
||||||
|
where errors will be captured by Kibana as a single message, however with a
|
||||||
|
slightly more difficult developer experience when reading logs by eyeball.
|
||||||
|
|
||||||
|
|
||||||
|
## SSH access
|
||||||
|
|
||||||
|
The CloudFoundry CLI provides SSH access to the running container of an
|
||||||
|
application. Use `cf ssh <application>` to SSH into the container. To make sure
|
||||||
|
that your shell is seeing the same configuration as the running application, be
|
||||||
|
sure to run `/tmp/lifecycle/shell` very first.
|
||||||
|
|
||||||
|
Inside the container, the python code should be in `/app` and you can check
|
||||||
|
there to see if the expected version of code is deployed in a particular file.
|
||||||
|
There is no hot-reloading inside the container, so it isn't possible to make
|
||||||
|
code changes there and see the results reflected in the running application.
|
||||||
|
(Templates may be read directly from disk every page load so it is possible
|
||||||
|
that you could change a page template and see the result in the application.)
|
||||||
|
|
||||||
|
Inside the container, it can be useful to run various Django admin commands
|
||||||
|
using `./manage.py`. For example, `./manage.py shell` can be used to give a
|
||||||
|
python interpreter where code can be run to modify objects in the database, say
|
||||||
|
to make a user an administrator.
|
||||||
|
|
||||||
|
## Github Actions
|
||||||
|
|
||||||
|
In order to allow some ops activities by people without CloudFoundry on a
|
||||||
|
laptop, we have some ops-related actions under
|
||||||
|
<https://github.com/cisagov/getgov/actions>.
|
||||||
|
|
||||||
|
### Migrate data
|
||||||
|
|
||||||
|
This Github action runs Django's `manage.py migrate` command on the specified
|
||||||
|
environment. **This is the first thing to try when fixing 500 errors from an
|
||||||
|
application environment**. The migrations should be idempotent, so running the
|
||||||
|
same migrations more than once should never cause an additional problem.
|
||||||
|
|
||||||
|
### Reset database
|
||||||
|
|
||||||
|
Very occasionally, there are migrations that don't succeed when run against a
|
||||||
|
database with data already in it. This action drops the database and re-creates
|
||||||
|
it with the latest model schema. Once launched, this should never be used on
|
||||||
|
the `stable` environment, but during development, it may be useful on the
|
||||||
|
various sandbox environments. After launch, some schema changes may take the
|
||||||
|
involvement of a skilled DBA to fix problems like this.
|
||||||
|
|
29
ops/manifests/manifest-rb.yaml
Normal file
29
ops/manifests/manifest-rb.yaml
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
---
|
||||||
|
applications:
|
||||||
|
- name: getgov-rb
|
||||||
|
buildpacks:
|
||||||
|
- python_buildpack
|
||||||
|
path: ../../src
|
||||||
|
instances: 1
|
||||||
|
memory: 512M
|
||||||
|
stack: cflinuxfs4
|
||||||
|
timeout: 180
|
||||||
|
command: ./run.sh
|
||||||
|
health-check-type: http
|
||||||
|
health-check-http-endpoint: /health
|
||||||
|
env:
|
||||||
|
# Send stdout and stderr straight to the terminal without buffering
|
||||||
|
PYTHONUNBUFFERED: yup
|
||||||
|
# Tell Django where to find its configuration
|
||||||
|
DJANGO_SETTINGS_MODULE: registrar.config.settings
|
||||||
|
# Tell Django where it is being hosted
|
||||||
|
DJANGO_BASE_URL: https://getgov-rb.app.cloud.gov
|
||||||
|
# Tell Django how much stuff to log
|
||||||
|
DJANGO_LOG_LEVEL: INFO
|
||||||
|
# default public site location
|
||||||
|
GETGOV_PUBLIC_SITE_URL: https://beta.get.gov
|
||||||
|
routes:
|
||||||
|
- route: getgov-rb.app.cloud.gov
|
||||||
|
services:
|
||||||
|
- getgov-credentials
|
||||||
|
- getgov-rb-database
|
|
@ -1,11 +1,13 @@
|
||||||
|
import logging
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.contrib.auth.admin import UserAdmin
|
from django.contrib.auth.admin import UserAdmin
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.http.response import HttpResponseRedirect
|
from django.http.response import HttpResponseRedirect
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
from . import models
|
from . import models
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class AuditedAdmin(admin.ModelAdmin):
|
class AuditedAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
|
@ -50,13 +52,37 @@ class MyHostAdmin(AuditedAdmin):
|
||||||
inlines = [HostIPInline]
|
inlines = [HostIPInline]
|
||||||
|
|
||||||
|
|
||||||
|
class DomainApplicationAdmin(AuditedAdmin):
|
||||||
|
|
||||||
|
"""Customize the applications listing view."""
|
||||||
|
|
||||||
|
# Trigger action when a fieldset is changed
|
||||||
|
def save_model(self, request, obj, form, change):
|
||||||
|
if change: # Check if the application is being edited
|
||||||
|
# Get the original application from the database
|
||||||
|
original_obj = models.DomainApplication.objects.get(pk=obj.pk)
|
||||||
|
|
||||||
|
if (
|
||||||
|
obj.status != original_obj.status
|
||||||
|
and obj.status == models.DomainApplication.INVESTIGATING
|
||||||
|
):
|
||||||
|
# This is a transition annotated method in model which will throw an
|
||||||
|
# error if the condition is violated. To make this work, we need to
|
||||||
|
# call it on the original object which has the right status value,
|
||||||
|
# but pass the current object which contains the up-to-date data
|
||||||
|
# for the email.
|
||||||
|
original_obj.in_review(obj)
|
||||||
|
|
||||||
|
super().save_model(request, obj, form, change)
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(models.User, MyUserAdmin)
|
admin.site.register(models.User, MyUserAdmin)
|
||||||
admin.site.register(models.UserDomainRole, AuditedAdmin)
|
admin.site.register(models.UserDomainRole, AuditedAdmin)
|
||||||
admin.site.register(models.Contact, AuditedAdmin)
|
admin.site.register(models.Contact, AuditedAdmin)
|
||||||
admin.site.register(models.DomainInvitation, AuditedAdmin)
|
admin.site.register(models.DomainInvitation, AuditedAdmin)
|
||||||
admin.site.register(models.DomainApplication, AuditedAdmin)
|
|
||||||
admin.site.register(models.DomainInformation, AuditedAdmin)
|
admin.site.register(models.DomainInformation, AuditedAdmin)
|
||||||
admin.site.register(models.Domain, AuditedAdmin)
|
admin.site.register(models.Domain, AuditedAdmin)
|
||||||
admin.site.register(models.Host, MyHostAdmin)
|
admin.site.register(models.Host, MyHostAdmin)
|
||||||
admin.site.register(models.Nameserver, MyHostAdmin)
|
admin.site.register(models.Nameserver, MyHostAdmin)
|
||||||
admin.site.register(models.Website, AuditedAdmin)
|
admin.site.register(models.Website, AuditedAdmin)
|
||||||
|
admin.site.register(models.DomainApplication, DomainApplicationAdmin)
|
||||||
|
|
|
@ -565,6 +565,7 @@ SECURE_SSL_REDIRECT = True
|
||||||
ALLOWED_HOSTS = [
|
ALLOWED_HOSTS = [
|
||||||
"getgov-stable.app.cloud.gov",
|
"getgov-stable.app.cloud.gov",
|
||||||
"getgov-gd.app.cloud.gov",
|
"getgov-gd.app.cloud.gov",
|
||||||
|
"getgov-rb.app.cloud.gov",
|
||||||
"getgov-ko.app.cloud.gov",
|
"getgov-ko.app.cloud.gov",
|
||||||
"getgov-ab.app.cloud.gov",
|
"getgov-ab.app.cloud.gov",
|
||||||
"getgov-bl.app.cloud.gov",
|
"getgov-bl.app.cloud.gov",
|
||||||
|
|
|
@ -490,9 +490,38 @@ class DomainApplication(TimeStampedModel):
|
||||||
self.submitter.email,
|
self.submitter.email,
|
||||||
context={"application": self},
|
context={"application": self},
|
||||||
)
|
)
|
||||||
|
logger.info(
|
||||||
|
f"Submission confirmation email sent to: {self.submitter.email}"
|
||||||
|
)
|
||||||
except EmailSendingError:
|
except EmailSendingError:
|
||||||
logger.warning("Failed to send confirmation email", exc_info=True)
|
logger.warning("Failed to send confirmation email", exc_info=True)
|
||||||
|
|
||||||
|
def _send_in_review_email(self):
|
||||||
|
"""Send an email that this application is now in review.
|
||||||
|
|
||||||
|
The email goes to the email address that the submitter gave as their
|
||||||
|
contact information. If there is not submitter information, then do
|
||||||
|
nothing.
|
||||||
|
"""
|
||||||
|
if self.submitter is None or self.submitter.email is None:
|
||||||
|
logger.warning(
|
||||||
|
"Cannot send status change (in review) email,"
|
||||||
|
"no submitter email address."
|
||||||
|
)
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
send_templated_email(
|
||||||
|
"emails/status_change_in_review.txt",
|
||||||
|
"emails/status_change_in_review_subject.txt",
|
||||||
|
self.submitter.email,
|
||||||
|
context={"application": self},
|
||||||
|
)
|
||||||
|
logging.info(f"In review email sent to: {self.submitter.email}")
|
||||||
|
except EmailSendingError:
|
||||||
|
logger.warning(
|
||||||
|
"Failed to send status change (in review) email", exc_info=True
|
||||||
|
)
|
||||||
|
|
||||||
@transition(field="status", source=[STARTED, WITHDRAWN], target=SUBMITTED)
|
@transition(field="status", source=[STARTED, WITHDRAWN], target=SUBMITTED)
|
||||||
def submit(self):
|
def submit(self):
|
||||||
"""Submit an application that is started."""
|
"""Submit an application that is started."""
|
||||||
|
@ -541,6 +570,19 @@ class DomainApplication(TimeStampedModel):
|
||||||
user=self.creator, domain=created_domain, role=UserDomainRole.Roles.ADMIN
|
user=self.creator, domain=created_domain, role=UserDomainRole.Roles.ADMIN
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@transition(field="status", source=SUBMITTED, target=INVESTIGATING)
|
||||||
|
def in_review(self, updated_domain_application):
|
||||||
|
"""Investigate an application that has been submitted.
|
||||||
|
|
||||||
|
This method is called in admin.py on the original application
|
||||||
|
which has the correct status value, but is passed the changed
|
||||||
|
application which has the up-to-date data that we'll use
|
||||||
|
in the email."""
|
||||||
|
|
||||||
|
# When an application is moved to in review, we need to send a
|
||||||
|
# confirmation email. This is a side-effect of the state transition
|
||||||
|
updated_domain_application._send_in_review_email()
|
||||||
|
|
||||||
@transition(field="status", source=[SUBMITTED, INVESTIGATING], target=WITHDRAWN)
|
@transition(field="status", source=[SUBMITTED, INVESTIGATING], target=WITHDRAWN)
|
||||||
def withdraw(self):
|
def withdraw(self):
|
||||||
"""Withdraw an application that has been submitted."""
|
"""Withdraw an application that has been submitted."""
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
SUMMARY OF YOUR DOMAIN REQUEST
|
||||||
|
|
||||||
|
Type of organization:
|
||||||
|
{{ application.get_organization_type_display }}
|
||||||
|
|
||||||
|
Organization name and mailing address:
|
||||||
|
{% spaceless %}{{ 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.type_of_work %}{# if block makes one newline if it's false #}
|
||||||
|
Type of work:
|
||||||
|
{% spaceless %}{{ application.type_of_work }}{% 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 website for your organization: {% for site in application.current_websites.all %}
|
||||||
|
{% spaceless %}{{ site.website }}{% endspaceless %}
|
||||||
|
{% endfor %}{% endif %}
|
||||||
|
.gov domain:
|
||||||
|
{{ application.requested_domain.name }}
|
||||||
|
{% for site in application.alternative_domains.all %}{% spaceless %}{{ site.website }}{% endspaceless %}
|
||||||
|
{% endfor %}
|
||||||
|
Purpose of your domain:
|
||||||
|
{{ application.purpose }}
|
||||||
|
|
||||||
|
Your contact information:
|
||||||
|
{% spaceless %}{% include "emails/includes/contact.txt" with contact=application.submitter %}{% endspaceless %}
|
||||||
|
{% if application.other_contacts.all %}
|
||||||
|
Other employees from your organization:
|
||||||
|
{% for other in application.other_contacts.all %}
|
||||||
|
{% spaceless %}{% include "emails/includes/contact.txt" with contact=other %}{% endspaceless %}
|
||||||
|
{% endfor %}{% endif %}{% if application.anything_else %}
|
||||||
|
Anything else we should know?
|
||||||
|
{{ application.anything_else }}
|
||||||
|
{% endif %}
|
43
src/registrar/templates/emails/status_change_in_review.txt
Normal file
43
src/registrar/templates/emails/status_change_in_review.txt
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
{% autoescape off %}{# In a text file, we don't want to have HTML entities escaped #}
|
||||||
|
Hi {{ application.submitter.first_name }}.
|
||||||
|
|
||||||
|
Your .gov domain request is being reviewed.
|
||||||
|
|
||||||
|
DOMAIN REQUESTED: {{ application.requested_domain.name }}
|
||||||
|
REQUEST RECEIVED ON: {{ application.updated_at|date }}
|
||||||
|
REQUEST #: {{ application.id }}
|
||||||
|
STATUS: In review
|
||||||
|
|
||||||
|
|
||||||
|
NEED TO MAKE CHANGES?
|
||||||
|
|
||||||
|
If you need to change your request you have to first withdraw it. Once you
|
||||||
|
withdraw the request you can edit it and submit it again. Changing your request
|
||||||
|
might add to the wait time. Learn more about withdrawing your request.
|
||||||
|
<https://get.gov/help/domain-requests/#withdraw-your-domain-request>.
|
||||||
|
|
||||||
|
|
||||||
|
NEXT STEPS
|
||||||
|
|
||||||
|
- We’re reviewing your request. This usually takes 20 business days.
|
||||||
|
|
||||||
|
- You can check the status of your request at any time.
|
||||||
|
<https://registrar.get.gov/application/{{ application.id }}>
|
||||||
|
|
||||||
|
- We’ll email you with questions or when we complete our review.
|
||||||
|
|
||||||
|
|
||||||
|
THANK YOU
|
||||||
|
|
||||||
|
.Gov helps the public identify official, trusted information. Thank you for
|
||||||
|
requesting a .gov domain.
|
||||||
|
|
||||||
|
----------------------------------------------------------------
|
||||||
|
|
||||||
|
{% include 'emails/includes/application_summary.txt' %}
|
||||||
|
----------------------------------------------------------------
|
||||||
|
|
||||||
|
The .gov team
|
||||||
|
Contact us: <https://get.gov/contact/>
|
||||||
|
Visit <https://get.gov>
|
||||||
|
{% endautoescape %}
|
|
@ -0,0 +1 @@
|
||||||
|
Your .gov domain request is being reviewed
|
|
@ -33,45 +33,7 @@ requesting a .gov domain.
|
||||||
|
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
|
|
||||||
SUMMARY OF YOUR DOMAIN REQUEST
|
{% include 'emails/includes/application_summary.txt' %}
|
||||||
|
|
||||||
Type of organization:
|
|
||||||
{{ application.get_organization_type_display }}
|
|
||||||
|
|
||||||
Organization name and mailing address:
|
|
||||||
{% spaceless %}{{ 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.type_of_work %}{# if block makes one newline if it's false #}
|
|
||||||
Type of work:
|
|
||||||
{% spaceless %}{{ application.type_of_work }}{% 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 website for your organization: {% for site in application.current_websites.all %}
|
|
||||||
{% spaceless %}{{ site.website }}{% endspaceless %}
|
|
||||||
{% endfor %}{% endif %}
|
|
||||||
.gov domain:
|
|
||||||
{{ application.requested_domain.name }}
|
|
||||||
{% for site in application.alternative_domains.all %}{% spaceless %}{{ site.website }}{% endspaceless %}
|
|
||||||
{% endfor %}
|
|
||||||
Purpose of your domain:
|
|
||||||
{{ application.purpose }}
|
|
||||||
|
|
||||||
Your contact information:
|
|
||||||
{% spaceless %}{% include "emails/includes/contact.txt" with contact=application.submitter %}{% endspaceless %}
|
|
||||||
{% if application.other_contacts.all %}
|
|
||||||
Other employees from your organization:
|
|
||||||
{% for other in application.other_contacts.all %}
|
|
||||||
{% spaceless %}{% include "emails/includes/contact.txt" with contact=other %}{% endspaceless %}
|
|
||||||
{% endfor %}{% endif %}{% if application.anything_else %}
|
|
||||||
Anything else we should know?
|
|
||||||
{{ application.anything_else }}
|
|
||||||
{% endif %}
|
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
|
|
||||||
The .gov team
|
The .gov team
|
||||||
|
|
|
@ -44,7 +44,7 @@
|
||||||
<p>Domain requests from state legislatures and courts must be authorized by an agency’s <strong>Chief Information Officer</strong> or <strong>highest-ranking executive</strong>.</p>
|
<p>Domain requests from state legislatures and courts must be authorized by an agency’s <strong>Chief Information Officer</strong> or <strong>highest-ranking executive</strong>.</p>
|
||||||
|
|
||||||
{% elif organization_type == 'tribal' %}
|
{% elif organization_type == 'tribal' %}
|
||||||
<p>Domain requests from federally-recognized tribal governments must be authorized by the leader of the tribe, as recognized by the <a href="https://www.bia.gov/service/tribal-leaders-directory" class="usa-link">Bureau of Indian Affairs.</a></p>
|
<p><strong>Domain requests from federally-recognized tribal governments must be authorized by the leader of the tribe</strong>, as recognized by the <a href="https://www.bia.gov/service/tribal-leaders-directory" class="usa-link">Bureau of Indian Affairs.</a></p>
|
||||||
<p>Domain requests from state-recognized tribal governments must be authorized by the leader of the tribe, as determined by the state’s tribal recognition initiative.</p>
|
<p><strong>Domain requests from state-recognized tribal governments must be authorized by the leader of the tribe</strong>, as determined by the state’s tribal recognition initiative.</p>
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -8,6 +8,8 @@ from typing import List, Dict
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth import get_user_model, login
|
from django.contrib.auth import get_user_model, login
|
||||||
|
|
||||||
|
from registrar.models import Contact, DraftDomain, Website, DomainApplication
|
||||||
|
|
||||||
|
|
||||||
def get_handlers():
|
def get_handlers():
|
||||||
"""Obtain pointers to all StreamHandlers."""
|
"""Obtain pointers to all StreamHandlers."""
|
||||||
|
@ -84,3 +86,74 @@ class MockSESClient(Mock):
|
||||||
|
|
||||||
def send_email(self, *args, **kwargs):
|
def send_email(self, *args, **kwargs):
|
||||||
self.EMAILS_SENT.append({"args": args, "kwargs": kwargs})
|
self.EMAILS_SENT.append({"args": args, "kwargs": kwargs})
|
||||||
|
|
||||||
|
|
||||||
|
def completed_application(
|
||||||
|
has_other_contacts=True,
|
||||||
|
has_current_website=True,
|
||||||
|
has_alternative_gov_domain=True,
|
||||||
|
has_type_of_work=True,
|
||||||
|
has_anything_else=True,
|
||||||
|
status=DomainApplication.STARTED,
|
||||||
|
user=False,
|
||||||
|
):
|
||||||
|
"""A completed domain application."""
|
||||||
|
if not user:
|
||||||
|
user = get_user_model().objects.create(username="username")
|
||||||
|
ao, _ = Contact.objects.get_or_create(
|
||||||
|
first_name="Testy",
|
||||||
|
last_name="Tester",
|
||||||
|
title="Chief Tester",
|
||||||
|
email="testy@town.com",
|
||||||
|
phone="(555) 555 5555",
|
||||||
|
)
|
||||||
|
domain, _ = DraftDomain.objects.get_or_create(name="city.gov")
|
||||||
|
alt, _ = Website.objects.get_or_create(website="city1.gov")
|
||||||
|
current, _ = Website.objects.get_or_create(website="city.com")
|
||||||
|
you, _ = Contact.objects.get_or_create(
|
||||||
|
first_name="Testy you",
|
||||||
|
last_name="Tester you",
|
||||||
|
title="Admin Tester",
|
||||||
|
email="mayor@igorville.gov",
|
||||||
|
phone="(555) 555 5556",
|
||||||
|
)
|
||||||
|
other, _ = Contact.objects.get_or_create(
|
||||||
|
first_name="Testy2",
|
||||||
|
last_name="Tester2",
|
||||||
|
title="Another Tester",
|
||||||
|
email="testy2@town.com",
|
||||||
|
phone="(555) 555 5557",
|
||||||
|
)
|
||||||
|
domain_application_kwargs = dict(
|
||||||
|
organization_type="federal",
|
||||||
|
federal_type="executive",
|
||||||
|
purpose="Purpose of the site",
|
||||||
|
is_policy_acknowledged=True,
|
||||||
|
organization_name="Testorg",
|
||||||
|
address_line1="address 1",
|
||||||
|
address_line2="address 2",
|
||||||
|
state_territory="NY",
|
||||||
|
zipcode="10002",
|
||||||
|
authorizing_official=ao,
|
||||||
|
requested_domain=domain,
|
||||||
|
submitter=you,
|
||||||
|
creator=user,
|
||||||
|
status=status,
|
||||||
|
)
|
||||||
|
if has_type_of_work:
|
||||||
|
domain_application_kwargs["type_of_work"] = "e-Government"
|
||||||
|
if has_anything_else:
|
||||||
|
domain_application_kwargs["anything_else"] = "There is more"
|
||||||
|
|
||||||
|
application, _ = DomainApplication.objects.get_or_create(
|
||||||
|
**domain_application_kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
if has_other_contacts:
|
||||||
|
application.other_contacts.add(other)
|
||||||
|
if has_current_website:
|
||||||
|
application.current_websites.add(current)
|
||||||
|
if has_alternative_gov_domain:
|
||||||
|
application.alternative_domains.add(alt)
|
||||||
|
|
||||||
|
return application
|
||||||
|
|
64
src/registrar/tests/test_admin.py
Normal file
64
src/registrar/tests/test_admin.py
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
from django.test import TestCase, RequestFactory
|
||||||
|
from django.contrib.admin.sites import AdminSite
|
||||||
|
from registrar.admin import DomainApplicationAdmin
|
||||||
|
from registrar.models import DomainApplication, User
|
||||||
|
from .common import completed_application
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
import boto3_mocking # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
class TestDomainApplicationAdmin(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.site = AdminSite()
|
||||||
|
self.factory = RequestFactory()
|
||||||
|
|
||||||
|
@boto3_mocking.patching
|
||||||
|
def test_save_model_sends_email_on_property_change(self):
|
||||||
|
# make sure there is no user with this email
|
||||||
|
EMAIL = "mayor@igorville.gov"
|
||||||
|
User.objects.filter(email=EMAIL).delete()
|
||||||
|
|
||||||
|
mock_client = MagicMock()
|
||||||
|
mock_client_instance = mock_client.return_value
|
||||||
|
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", mock_client):
|
||||||
|
# Create a sample application
|
||||||
|
application = completed_application(status=DomainApplication.SUBMITTED)
|
||||||
|
|
||||||
|
# Create a mock request
|
||||||
|
request = self.factory.post(
|
||||||
|
"/admin/registrar/domainapplication/{}/change/".format(application.pk)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create an instance of the model admin
|
||||||
|
model_admin = DomainApplicationAdmin(DomainApplication, self.site)
|
||||||
|
|
||||||
|
# Modify the application's property
|
||||||
|
application.status = DomainApplication.INVESTIGATING
|
||||||
|
|
||||||
|
# Use the model admin's save_model method
|
||||||
|
model_admin.save_model(request, application, form=None, change=True)
|
||||||
|
|
||||||
|
# Access the arguments passed to send_email
|
||||||
|
call_args = mock_client_instance.send_email.call_args
|
||||||
|
args, kwargs = call_args
|
||||||
|
|
||||||
|
# Retrieve the email details from the arguments
|
||||||
|
from_email = kwargs.get("FromEmailAddress")
|
||||||
|
to_email = kwargs["Destination"]["ToAddresses"][0]
|
||||||
|
email_content = kwargs["Content"]
|
||||||
|
email_body = email_content["Simple"]["Body"]["Text"]["Data"]
|
||||||
|
|
||||||
|
# Assert or perform other checks on the email details
|
||||||
|
expected_string = "Your .gov domain request is being reviewed"
|
||||||
|
self.assertEqual(from_email, settings.DEFAULT_FROM_EMAIL)
|
||||||
|
self.assertEqual(to_email, EMAIL)
|
||||||
|
self.assertIn(expected_string, email_body)
|
||||||
|
|
||||||
|
# Perform assertions on the mock call itself
|
||||||
|
mock_client_instance.send_email.assert_called_once()
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
application.delete()
|
|
@ -2,82 +2,14 @@
|
||||||
|
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
from django.contrib.auth import get_user_model
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
from .common import completed_application
|
||||||
|
|
||||||
from registrar.models import Contact, DraftDomain, Website, DomainApplication
|
|
||||||
|
|
||||||
import boto3_mocking # type: ignore
|
import boto3_mocking # type: ignore
|
||||||
|
|
||||||
|
|
||||||
class TestEmails(TestCase):
|
class TestEmails(TestCase):
|
||||||
def _completed_application(
|
|
||||||
self,
|
|
||||||
has_other_contacts=True,
|
|
||||||
has_current_website=True,
|
|
||||||
has_alternative_gov_domain=True,
|
|
||||||
has_type_of_work=True,
|
|
||||||
has_anything_else=True,
|
|
||||||
):
|
|
||||||
"""A completed domain application."""
|
|
||||||
user = get_user_model().objects.create(username="username")
|
|
||||||
ao, _ = Contact.objects.get_or_create(
|
|
||||||
first_name="Testy",
|
|
||||||
last_name="Tester",
|
|
||||||
title="Chief Tester",
|
|
||||||
email="testy@town.com",
|
|
||||||
phone="(555) 555 5555",
|
|
||||||
)
|
|
||||||
domain, _ = DraftDomain.objects.get_or_create(name="city.gov")
|
|
||||||
alt, _ = Website.objects.get_or_create(website="city1.gov")
|
|
||||||
current, _ = Website.objects.get_or_create(website="city.com")
|
|
||||||
you, _ = Contact.objects.get_or_create(
|
|
||||||
first_name="Testy you",
|
|
||||||
last_name="Tester you",
|
|
||||||
title="Admin Tester",
|
|
||||||
email="testy-admin@town.com",
|
|
||||||
phone="(555) 555 5556",
|
|
||||||
)
|
|
||||||
other, _ = Contact.objects.get_or_create(
|
|
||||||
first_name="Testy2",
|
|
||||||
last_name="Tester2",
|
|
||||||
title="Another Tester",
|
|
||||||
email="testy2@town.com",
|
|
||||||
phone="(555) 555 5557",
|
|
||||||
)
|
|
||||||
domain_application_kwargs = dict(
|
|
||||||
organization_type="federal",
|
|
||||||
federal_type="executive",
|
|
||||||
purpose="Purpose of the site",
|
|
||||||
is_policy_acknowledged=True,
|
|
||||||
organization_name="Testorg",
|
|
||||||
address_line1="address 1",
|
|
||||||
address_line2="address 2",
|
|
||||||
state_territory="NY",
|
|
||||||
zipcode="10002",
|
|
||||||
authorizing_official=ao,
|
|
||||||
requested_domain=domain,
|
|
||||||
submitter=you,
|
|
||||||
creator=user,
|
|
||||||
)
|
|
||||||
if has_type_of_work:
|
|
||||||
domain_application_kwargs["type_of_work"] = "e-Government"
|
|
||||||
if has_anything_else:
|
|
||||||
domain_application_kwargs["anything_else"] = "There is more"
|
|
||||||
|
|
||||||
application, _ = DomainApplication.objects.get_or_create(
|
|
||||||
**domain_application_kwargs
|
|
||||||
)
|
|
||||||
|
|
||||||
if has_other_contacts:
|
|
||||||
application.other_contacts.add(other)
|
|
||||||
if has_current_website:
|
|
||||||
application.current_websites.add(current)
|
|
||||||
if has_alternative_gov_domain:
|
|
||||||
application.alternative_domains.add(alt)
|
|
||||||
|
|
||||||
return application
|
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.mock_client_class = MagicMock()
|
self.mock_client_class = MagicMock()
|
||||||
self.mock_client = self.mock_client_class.return_value
|
self.mock_client = self.mock_client_class.return_value
|
||||||
|
@ -85,7 +17,7 @@ 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 = self._completed_application()
|
application = completed_application()
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
|
||||||
application.submit()
|
application.submit()
|
||||||
|
@ -122,7 +54,7 @@ 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 = self._completed_application(has_current_website=False)
|
application = completed_application(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):
|
||||||
application.submit()
|
application.submit()
|
||||||
_, kwargs = self.mock_client.send_email.call_args
|
_, kwargs = self.mock_client.send_email.call_args
|
||||||
|
@ -134,7 +66,7 @@ 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 = self._completed_application(has_current_website=True)
|
application = completed_application(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):
|
||||||
application.submit()
|
application.submit()
|
||||||
_, kwargs = self.mock_client.send_email.call_args
|
_, kwargs = self.mock_client.send_email.call_args
|
||||||
|
@ -147,7 +79,7 @@ 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 = self._completed_application(has_other_contacts=True)
|
application = completed_application(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):
|
||||||
application.submit()
|
application.submit()
|
||||||
_, kwargs = self.mock_client.send_email.call_args
|
_, kwargs = self.mock_client.send_email.call_args
|
||||||
|
@ -160,7 +92,7 @@ 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 = self._completed_application(has_other_contacts=False)
|
application = completed_application(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):
|
||||||
application.submit()
|
application.submit()
|
||||||
_, kwargs = self.mock_client.send_email.call_args
|
_, kwargs = self.mock_client.send_email.call_args
|
||||||
|
@ -172,7 +104,7 @@ 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 = self._completed_application(has_alternative_gov_domain=True)
|
application = completed_application(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):
|
||||||
application.submit()
|
application.submit()
|
||||||
_, kwargs = self.mock_client.send_email.call_args
|
_, kwargs = self.mock_client.send_email.call_args
|
||||||
|
@ -184,7 +116,7 @@ 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 = self._completed_application(has_alternative_gov_domain=False)
|
application = completed_application(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):
|
||||||
application.submit()
|
application.submit()
|
||||||
_, kwargs = self.mock_client.send_email.call_args
|
_, kwargs = self.mock_client.send_email.call_args
|
||||||
|
@ -196,7 +128,7 @@ class TestEmails(TestCase):
|
||||||
@boto3_mocking.patching
|
@boto3_mocking.patching
|
||||||
def test_submission_confirmation_type_of_work_spacing(self):
|
def test_submission_confirmation_type_of_work_spacing(self):
|
||||||
"""Test line spacing with type of work."""
|
"""Test line spacing with type of work."""
|
||||||
application = self._completed_application(has_type_of_work=True)
|
application = completed_application(has_type_of_work=True)
|
||||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
|
||||||
application.submit()
|
application.submit()
|
||||||
_, kwargs = self.mock_client.send_email.call_args
|
_, kwargs = self.mock_client.send_email.call_args
|
||||||
|
@ -208,7 +140,7 @@ class TestEmails(TestCase):
|
||||||
@boto3_mocking.patching
|
@boto3_mocking.patching
|
||||||
def test_submission_confirmation_no_type_of_work_spacing(self):
|
def test_submission_confirmation_no_type_of_work_spacing(self):
|
||||||
"""Test line spacing without type of work."""
|
"""Test line spacing without type of work."""
|
||||||
application = self._completed_application(has_type_of_work=False)
|
application = completed_application(has_type_of_work=False)
|
||||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class):
|
||||||
application.submit()
|
application.submit()
|
||||||
_, kwargs = self.mock_client.send_email.call_args
|
_, kwargs = self.mock_client.send_email.call_args
|
||||||
|
@ -220,7 +152,7 @@ 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 = self._completed_application(has_anything_else=True)
|
application = completed_application(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):
|
||||||
application.submit()
|
application.submit()
|
||||||
_, kwargs = self.mock_client.send_email.call_args
|
_, kwargs = self.mock_client.send_email.call_args
|
||||||
|
@ -231,7 +163,7 @@ 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 = self._completed_application(has_anything_else=False)
|
application = completed_application(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):
|
||||||
application.submit()
|
application.submit()
|
||||||
_, kwargs = self.mock_client.send_email.call_args
|
_, kwargs = self.mock_client.send_email.call_args
|
||||||
|
|
|
@ -5,6 +5,7 @@ from django.conf import settings
|
||||||
from django.test import Client, TestCase
|
from django.test import Client, TestCase
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
|
from .common import completed_application
|
||||||
|
|
||||||
from django_webtest import WebTest # type: ignore
|
from django_webtest import WebTest # type: ignore
|
||||||
import boto3_mocking # type: ignore
|
import boto3_mocking # type: ignore
|
||||||
|
@ -1407,85 +1408,18 @@ class TestApplicationStatus(TestWithUser, WebTest):
|
||||||
self.app.set_user(self.user.username)
|
self.app.set_user(self.user.username)
|
||||||
self.client.force_login(self.user)
|
self.client.force_login(self.user)
|
||||||
|
|
||||||
def _completed_application(
|
|
||||||
self,
|
|
||||||
has_other_contacts=True,
|
|
||||||
has_current_website=True,
|
|
||||||
has_alternative_gov_domain=True,
|
|
||||||
has_type_of_work=True,
|
|
||||||
has_anything_else=True,
|
|
||||||
):
|
|
||||||
"""A completed domain application."""
|
|
||||||
ao, _ = Contact.objects.get_or_create(
|
|
||||||
first_name="Testy",
|
|
||||||
last_name="Tester",
|
|
||||||
title="Chief Tester",
|
|
||||||
email="testy@town.com",
|
|
||||||
phone="(555) 555 5555",
|
|
||||||
)
|
|
||||||
domain, _ = DraftDomain.objects.get_or_create(name="citystatus.gov")
|
|
||||||
alt, _ = Website.objects.get_or_create(website="city1.gov")
|
|
||||||
current, _ = Website.objects.get_or_create(website="city.com")
|
|
||||||
you, _ = Contact.objects.get_or_create(
|
|
||||||
first_name="Testy you",
|
|
||||||
last_name="Tester you",
|
|
||||||
title="Admin Tester",
|
|
||||||
email="testy-admin@town.com",
|
|
||||||
phone="(555) 555 5556",
|
|
||||||
)
|
|
||||||
other, _ = Contact.objects.get_or_create(
|
|
||||||
first_name="Testy2",
|
|
||||||
last_name="Tester2",
|
|
||||||
title="Another Tester",
|
|
||||||
email="testy2@town.com",
|
|
||||||
phone="(555) 555 5557",
|
|
||||||
)
|
|
||||||
domain_application_kwargs = dict(
|
|
||||||
organization_type="federal",
|
|
||||||
federal_type="executive",
|
|
||||||
purpose="Purpose of the site",
|
|
||||||
is_policy_acknowledged=True,
|
|
||||||
organization_name="Testorg",
|
|
||||||
address_line1="address 1",
|
|
||||||
address_line2="address 2",
|
|
||||||
state_territory="NY",
|
|
||||||
zipcode="10002",
|
|
||||||
authorizing_official=ao,
|
|
||||||
requested_domain=domain,
|
|
||||||
submitter=you,
|
|
||||||
creator=self.user,
|
|
||||||
)
|
|
||||||
if has_type_of_work:
|
|
||||||
domain_application_kwargs["type_of_work"] = "e-Government"
|
|
||||||
if has_anything_else:
|
|
||||||
domain_application_kwargs["anything_else"] = "There is more"
|
|
||||||
|
|
||||||
application, _ = DomainApplication.objects.get_or_create(
|
|
||||||
**domain_application_kwargs
|
|
||||||
)
|
|
||||||
|
|
||||||
application.status = DomainApplication.SUBMITTED
|
|
||||||
application.save()
|
|
||||||
|
|
||||||
if has_other_contacts:
|
|
||||||
application.other_contacts.add(other)
|
|
||||||
if has_current_website:
|
|
||||||
application.current_websites.add(current)
|
|
||||||
if has_alternative_gov_domain:
|
|
||||||
application.alternative_domains.add(alt)
|
|
||||||
|
|
||||||
return application
|
|
||||||
|
|
||||||
def test_application_status(self):
|
def test_application_status(self):
|
||||||
"""Checking application status page"""
|
"""Checking application status page"""
|
||||||
application = self._completed_application()
|
application = completed_application(
|
||||||
|
status=DomainApplication.SUBMITTED, user=self.user
|
||||||
|
)
|
||||||
application.save()
|
application.save()
|
||||||
|
|
||||||
home_page = self.app.get("/")
|
home_page = self.app.get("/")
|
||||||
self.assertContains(home_page, "citystatus.gov")
|
self.assertContains(home_page, "city.gov")
|
||||||
# click the "Manage" link
|
# click the "Manage" link
|
||||||
detail_page = home_page.click("Manage")
|
detail_page = home_page.click("Manage")
|
||||||
self.assertContains(detail_page, "citystatus.gov")
|
self.assertContains(detail_page, "city.gov")
|
||||||
self.assertContains(detail_page, "Chief Tester")
|
self.assertContains(detail_page, "Chief Tester")
|
||||||
self.assertContains(detail_page, "testy@town.com")
|
self.assertContains(detail_page, "testy@town.com")
|
||||||
self.assertContains(detail_page, "Admin Tester")
|
self.assertContains(detail_page, "Admin Tester")
|
||||||
|
@ -1493,14 +1427,16 @@ class TestApplicationStatus(TestWithUser, WebTest):
|
||||||
|
|
||||||
def test_application_withdraw(self):
|
def test_application_withdraw(self):
|
||||||
"""Checking application status page"""
|
"""Checking application status page"""
|
||||||
application = self._completed_application()
|
application = completed_application(
|
||||||
|
status=DomainApplication.SUBMITTED, user=self.user
|
||||||
|
)
|
||||||
application.save()
|
application.save()
|
||||||
|
|
||||||
home_page = self.app.get("/")
|
home_page = self.app.get("/")
|
||||||
self.assertContains(home_page, "citystatus.gov")
|
self.assertContains(home_page, "city.gov")
|
||||||
# click the "Manage" link
|
# click the "Manage" link
|
||||||
detail_page = home_page.click("Manage")
|
detail_page = home_page.click("Manage")
|
||||||
self.assertContains(detail_page, "citystatus.gov")
|
self.assertContains(detail_page, "city.gov")
|
||||||
self.assertContains(detail_page, "Chief Tester")
|
self.assertContains(detail_page, "Chief Tester")
|
||||||
self.assertContains(detail_page, "testy@town.com")
|
self.assertContains(detail_page, "testy@town.com")
|
||||||
self.assertContains(detail_page, "Admin Tester")
|
self.assertContains(detail_page, "Admin Tester")
|
||||||
|
@ -1522,7 +1458,9 @@ class TestApplicationStatus(TestWithUser, WebTest):
|
||||||
|
|
||||||
def test_application_status_no_permissions(self):
|
def test_application_status_no_permissions(self):
|
||||||
"""Can't access applications without being the creator."""
|
"""Can't access applications without being the creator."""
|
||||||
application = self._completed_application()
|
application = completed_application(
|
||||||
|
status=DomainApplication.SUBMITTED, user=self.user
|
||||||
|
)
|
||||||
other_user = User()
|
other_user = User()
|
||||||
other_user.save()
|
other_user.save()
|
||||||
application.creator = other_user
|
application.creator = other_user
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue