mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-08-05 17:28:31 +02:00
Merge remote-tracking branch 'origin/main' into rjm/415-character-limit-validation
This commit is contained in:
commit
01770d4216
24 changed files with 715 additions and 14 deletions
12
.github/workflows/test.yaml
vendored
12
.github/workflows/test.yaml
vendored
|
@ -32,6 +32,17 @@ jobs:
|
|||
working-directory: ./src
|
||||
run: docker compose run app python manage.py test
|
||||
|
||||
django-migrations-complete:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Check for complete migrations
|
||||
working-directory: ./src
|
||||
run: |
|
||||
docker compose run app ./manage.py makemigrations --dry-run --verbosity 3 && \
|
||||
docker compose run app ./manage.py makemigrations --check
|
||||
|
||||
pa11y-scan:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
|
@ -53,5 +64,6 @@ jobs:
|
|||
- name: run pa11y
|
||||
working-directory: ./src
|
||||
run: |
|
||||
sleep 10;
|
||||
npm i -g pa11y-ci
|
||||
pa11y-ci
|
||||
|
|
|
@ -22,6 +22,18 @@ Visit the running application at [http://localhost:8080](http://localhost:8080).
|
|||
|
||||
We use the branch convention of `initials/branch-topic` (ex: `lmm/fix-footer`). This allows for automated deployment to a developer sandbox namespaced to the initials.
|
||||
|
||||
## Merging and PRs
|
||||
|
||||
To bring your feature branch up-to-date wih main:
|
||||
|
||||
```
|
||||
git checkout main
|
||||
git pull
|
||||
git checkout <feature-branch>
|
||||
git merge orgin/main
|
||||
git push
|
||||
```
|
||||
|
||||
## Setting Vars
|
||||
|
||||
Non-secret environment variables for local development are set in [src/docker-compose.yml](../../src/docker-compose.yml).
|
||||
|
@ -34,6 +46,28 @@ DJANGO_SECRET_LOGIN_KEY="<...>"
|
|||
|
||||
You'll need to create the `.env` file yourself. Get the secrets from Cloud.gov by running `cf env getgov-YOURSANDBOX`. More information is available in [rotate_application_secrets.md](../operations/runbooks/rotate_application_secrets.md).
|
||||
|
||||
## Adding user to /admin
|
||||
|
||||
The endpoint /admin can be used to view and manage site content, including but not limited to user information and the list of current applications in the database. To be able to view and use /admin locally:
|
||||
|
||||
1. Login via login.gov
|
||||
2. Go to the home page and make sure you can see the part where you can submit an application
|
||||
3. Go to /admin and it will tell you that UUID is not authorized, copy that UUID for use in 4
|
||||
4. in src/registrar/fixtures.py add to the ADMINS list in that file by adding your UUID as your username along with your first and last name. See below:
|
||||
|
||||
```
|
||||
ADMINS = [
|
||||
{
|
||||
"username": "<UUID here>",
|
||||
"first_name": "",
|
||||
"last_name": "",
|
||||
},
|
||||
...
|
||||
]
|
||||
```
|
||||
|
||||
5. In the browser, navigate to /admins. To verify that all is working correctly, under "domain applications" you should see fake domains with various fake statuses.
|
||||
|
||||
## Viewing Logs
|
||||
|
||||
If you run via `docker-compose up`, you'll see the logs in your terminal.
|
||||
|
|
|
@ -21,4 +21,4 @@ CISA lacks a scalable, efficient, and secure method of managing the .gov TLD pro
|
|||
| **Growth and use:** Regular growth in the overall number of .gov domains registered, with clear increases in election orgs, major metro areas, and state legislatures/courts | - Raw count of registered .gov domains increases <br /> - Number of YoY applications per month increases <br /> - Percent of 100 most populous cities, counties, etc. (per Census data) using .gov domains increases |
|
||||
| **Data:** The program maintains authoritative contacts at, metadata about, and hostname information for all registered .gov domains, and is able to track that .gov domains are actually used | - Time-to-generate internal reports decreases <br /> - Results of periodic data quality audit show improvements month-over-month |
|
||||
| **User satisfaction:** Getting a .gov domain is as easy and intuitive as possible | - Completion rate of form improves <br /> - Time from domain request to approval decreases <br /> - Number of domains requiring analyst data changes decreases |
|
||||
| **Program reputation and experience:** The .gov program is viewed as trustworthy and responsive | - Response time for inquiries decreases <br /> - Resolution time decreases <br /> - Rate of repeat issues for tickets decreases <br /> - Number of SLTT organizations in CoP increases |
|
||||
| **Program reputation and experience:** The .gov program is viewed as trustworthy and responsive | - Response time for inquiries decreases <br /> - Resolution time decreases <br /> - Rate of repeat issues for tickets decreases <br /> - Number of SLTT organizations in Community of Practice increases |
|
||||
|
|
|
@ -6,7 +6,7 @@ applications:
|
|||
path: ../../src
|
||||
instances: 1
|
||||
memory: 512M
|
||||
stack: cflinuxfs3
|
||||
stack: cflinuxfs4
|
||||
timeout: 180
|
||||
command: ./run.sh
|
||||
health-check-type: http
|
||||
|
|
|
@ -6,7 +6,7 @@ applications:
|
|||
path: ../../src
|
||||
instances: 1
|
||||
memory: 512M
|
||||
stack: cflinuxfs3
|
||||
stack: cflinuxfs4
|
||||
timeout: 180
|
||||
command: ./run.sh
|
||||
health-check-type: http
|
||||
|
|
|
@ -6,7 +6,7 @@ applications:
|
|||
path: ../../src
|
||||
instances: 1
|
||||
memory: 512M
|
||||
stack: cflinuxfs3
|
||||
stack: cflinuxfs4
|
||||
timeout: 180
|
||||
command: ./run.sh
|
||||
health-check-type: http
|
||||
|
|
|
@ -6,7 +6,7 @@ applications:
|
|||
path: ../../src
|
||||
instances: 1
|
||||
memory: 512M
|
||||
stack: cflinuxfs3
|
||||
stack: cflinuxfs4
|
||||
timeout: 180
|
||||
command: ./run.sh
|
||||
health-check-type: http
|
||||
|
|
|
@ -6,7 +6,7 @@ applications:
|
|||
path: ../../src
|
||||
instances: 1
|
||||
memory: 512M
|
||||
stack: cflinuxfs3
|
||||
stack: cflinuxfs4
|
||||
timeout: 180
|
||||
command: ./run.sh
|
||||
health-check-type: http
|
||||
|
|
|
@ -6,7 +6,7 @@ applications:
|
|||
path: ../../src
|
||||
instances: 1
|
||||
memory: 512M
|
||||
stack: cflinuxfs3
|
||||
stack: cflinuxfs4
|
||||
timeout: 180
|
||||
command: ./run.sh
|
||||
health-check-type: http
|
||||
|
|
|
@ -6,7 +6,7 @@ applications:
|
|||
path: ../../src
|
||||
instances: 1
|
||||
memory: 512M
|
||||
stack: cflinuxfs3
|
||||
stack: cflinuxfs4
|
||||
timeout: 180
|
||||
command: ./run.sh
|
||||
health-check-type: http
|
||||
|
|
|
@ -6,7 +6,7 @@ applications:
|
|||
path: ../../src
|
||||
instances: 1
|
||||
memory: 512M
|
||||
stack: cflinuxfs3
|
||||
stack: cflinuxfs4
|
||||
timeout: 180
|
||||
command: ./run.sh
|
||||
health-check-type: http
|
||||
|
|
|
@ -6,7 +6,7 @@ applications:
|
|||
path: ../../src
|
||||
instances: 1
|
||||
memory: 512M
|
||||
stack: cflinuxfs3
|
||||
stack: cflinuxfs4
|
||||
timeout: 180
|
||||
command: ./run.sh
|
||||
health-check-type: http
|
||||
|
|
|
@ -6,7 +6,7 @@ applications:
|
|||
path: ../../src
|
||||
instances: 1
|
||||
memory: 512M
|
||||
stack: cflinuxfs3
|
||||
stack: cflinuxfs4
|
||||
timeout: 180
|
||||
command: ./run.sh
|
||||
health-check-type: http
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"defaults": {
|
||||
"concurrency": 1,
|
||||
"timeout": 10000,
|
||||
"timeout": 30000,
|
||||
"hideElements": "a[href='/whoami/']"
|
||||
|
||||
},
|
||||
|
|
|
@ -55,6 +55,7 @@ admin.site.register(models.UserDomainRole, AuditedAdmin)
|
|||
admin.site.register(models.Contact, AuditedAdmin)
|
||||
admin.site.register(models.DomainInvitation, AuditedAdmin)
|
||||
admin.site.register(models.DomainApplication, AuditedAdmin)
|
||||
admin.site.register(models.DomainInformation, AuditedAdmin)
|
||||
admin.site.register(models.Domain, AuditedAdmin)
|
||||
admin.site.register(models.Host, MyHostAdmin)
|
||||
admin.site.register(models.Nameserver, MyHostAdmin)
|
||||
|
|
|
@ -417,6 +417,10 @@ footer {
|
|||
color: color('primary');
|
||||
}
|
||||
|
||||
.usa-identifier__logo {
|
||||
height: units(7);
|
||||
}
|
||||
|
||||
abbr[title] {
|
||||
// workaround for underlining abbr element
|
||||
border-bottom: none;
|
||||
|
|
|
@ -44,6 +44,11 @@ class UserFixture:
|
|||
"first_name": "Neil",
|
||||
"last_name": "Martinsen-Burrell",
|
||||
},
|
||||
{
|
||||
"username": "7185e6cd-d3c8-4adc-90a3-ceddba71d24f",
|
||||
"first_name": "Jon",
|
||||
"last_name": "Roberts",
|
||||
},
|
||||
{
|
||||
"username": "5f283494-31bd-49b5-b024-a7e7cae00848",
|
||||
"first_name": "Rachid",
|
||||
|
|
273
src/registrar/migrations/0018_domaininformation.py
Normal file
273
src/registrar/migrations/0018_domaininformation.py
Normal file
|
@ -0,0 +1,273 @@
|
|||
# Generated by Django 4.1.6 on 2023-05-08 15:30
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("registrar", "0017_alter_domainapplication_status_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="DomainInformation",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||
("updated_at", models.DateTimeField(auto_now=True)),
|
||||
(
|
||||
"organization_type",
|
||||
models.CharField(
|
||||
blank=True,
|
||||
choices=[
|
||||
(
|
||||
"federal",
|
||||
"Federal: an agency of the U.S. government's executive, legislative, or judicial branches",
|
||||
),
|
||||
(
|
||||
"interstate",
|
||||
"Interstate: an organization of two or more states",
|
||||
),
|
||||
(
|
||||
"state_or_territory",
|
||||
"State or territory: one of the 50 U.S. states, the District of Columbia, American Samoa, Guam, Northern Mariana Islands, Puerto Rico, or the U.S. Virgin Islands",
|
||||
),
|
||||
(
|
||||
"tribal",
|
||||
"Tribal: a tribal government recognized by the federal or a state government",
|
||||
),
|
||||
("county", "County: a county, parish, or borough"),
|
||||
("city", "City: a city, town, township, village, etc."),
|
||||
(
|
||||
"special_district",
|
||||
"Special district: an independent organization within a single state",
|
||||
),
|
||||
(
|
||||
"school_district",
|
||||
"School district: a school district that is not part of a local government",
|
||||
),
|
||||
],
|
||||
help_text="Type of Organization",
|
||||
max_length=255,
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
(
|
||||
"federally_recognized_tribe",
|
||||
models.BooleanField(
|
||||
help_text="Is the tribe federally recognized", null=True
|
||||
),
|
||||
),
|
||||
(
|
||||
"state_recognized_tribe",
|
||||
models.BooleanField(
|
||||
help_text="Is the tribe recognized by a state", null=True
|
||||
),
|
||||
),
|
||||
(
|
||||
"tribe_name",
|
||||
models.TextField(blank=True, help_text="Name of tribe", null=True),
|
||||
),
|
||||
(
|
||||
"federal_agency",
|
||||
models.TextField(blank=True, help_text="Federal agency", null=True),
|
||||
),
|
||||
(
|
||||
"federal_type",
|
||||
models.CharField(
|
||||
blank=True,
|
||||
choices=[
|
||||
("executive", "Executive"),
|
||||
("judicial", "Judicial"),
|
||||
("legislative", "Legislative"),
|
||||
],
|
||||
help_text="Federal government branch",
|
||||
max_length=50,
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
(
|
||||
"is_election_board",
|
||||
models.BooleanField(
|
||||
blank=True,
|
||||
help_text="Is your organization an election office?",
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
(
|
||||
"organization_name",
|
||||
models.TextField(
|
||||
blank=True,
|
||||
db_index=True,
|
||||
help_text="Organization name",
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
(
|
||||
"address_line1",
|
||||
models.TextField(blank=True, help_text="Street address", null=True),
|
||||
),
|
||||
(
|
||||
"address_line2",
|
||||
models.CharField(
|
||||
blank=True,
|
||||
help_text="Street address line 2",
|
||||
max_length=15,
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
("city", models.TextField(blank=True, help_text="City", null=True)),
|
||||
(
|
||||
"state_territory",
|
||||
models.CharField(
|
||||
blank=True,
|
||||
help_text="State, territory, or military post",
|
||||
max_length=2,
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
(
|
||||
"zipcode",
|
||||
models.CharField(
|
||||
blank=True,
|
||||
db_index=True,
|
||||
help_text="Zip code",
|
||||
max_length=10,
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
(
|
||||
"urbanization",
|
||||
models.TextField(
|
||||
blank=True,
|
||||
help_text="Urbanization (Puerto Rico only)",
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
(
|
||||
"type_of_work",
|
||||
models.TextField(
|
||||
blank=True,
|
||||
help_text="Type of work of the organization",
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
(
|
||||
"more_organization_information",
|
||||
models.TextField(
|
||||
blank=True,
|
||||
help_text="Further information about the government organization",
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
(
|
||||
"purpose",
|
||||
models.TextField(
|
||||
blank=True, help_text="Purpose of your domain", null=True
|
||||
),
|
||||
),
|
||||
(
|
||||
"no_other_contacts_rationale",
|
||||
models.TextField(
|
||||
blank=True,
|
||||
help_text="Reason for listing no additional contacts",
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
(
|
||||
"anything_else",
|
||||
models.TextField(
|
||||
blank=True, help_text="Anything else we should know?", null=True
|
||||
),
|
||||
),
|
||||
(
|
||||
"is_policy_acknowledged",
|
||||
models.BooleanField(
|
||||
blank=True,
|
||||
help_text="Acknowledged .gov acceptable use policy",
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
(
|
||||
"security_email",
|
||||
models.EmailField(
|
||||
blank=True,
|
||||
help_text="Security email for public use",
|
||||
max_length=320,
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
(
|
||||
"authorizing_official",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
related_name="information_authorizing_official",
|
||||
to="registrar.contact",
|
||||
),
|
||||
),
|
||||
(
|
||||
"creator",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
related_name="information_created",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
(
|
||||
"domain",
|
||||
models.OneToOneField(
|
||||
blank=True,
|
||||
help_text="Domain to which this information belongs",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
related_name="domain_info",
|
||||
to="registrar.domain",
|
||||
),
|
||||
),
|
||||
(
|
||||
"domain_application",
|
||||
models.OneToOneField(
|
||||
blank=True,
|
||||
help_text="Associated domain application",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
related_name="domainapplication_info",
|
||||
to="registrar.domainapplication",
|
||||
),
|
||||
),
|
||||
(
|
||||
"other_contacts",
|
||||
models.ManyToManyField(
|
||||
blank=True,
|
||||
related_name="contact_applications_information",
|
||||
to="registrar.contact",
|
||||
),
|
||||
),
|
||||
(
|
||||
"submitter",
|
||||
models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
related_name="submitted_applications_information",
|
||||
to="registrar.contact",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name_plural": "Domain Information",
|
||||
},
|
||||
),
|
||||
]
|
|
@ -0,0 +1,47 @@
|
|||
# Generated by Django 4.1.6 on 2023-05-09 19:50
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("registrar", "0018_domaininformation"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="domainapplication",
|
||||
name="organization_type",
|
||||
field=models.CharField(
|
||||
blank=True,
|
||||
choices=[
|
||||
(
|
||||
"federal",
|
||||
"Federal: an agency of the U.S. government's executive, legislative, or judicial branches",
|
||||
),
|
||||
("interstate", "Interstate: an organization of two or more states"),
|
||||
(
|
||||
"state_or_territory",
|
||||
"State or territory: one of the 50 U.S. states, the District of Columbia, American Samoa, Guam, Northern Mariana Islands, Puerto Rico, or the U.S. Virgin Islands",
|
||||
),
|
||||
(
|
||||
"tribal",
|
||||
"Tribal: a tribal government recognized by the federal or a state government",
|
||||
),
|
||||
("county", "County: a county, parish, or borough"),
|
||||
("city", "City: a city, town, township, village, etc."),
|
||||
(
|
||||
"special_district",
|
||||
"Special district: an independent organization within a single state",
|
||||
),
|
||||
(
|
||||
"school_district",
|
||||
"School district: a school district that is not part of a local government",
|
||||
),
|
||||
],
|
||||
help_text="Type of organization",
|
||||
max_length=255,
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
]
|
|
@ -2,6 +2,7 @@ from auditlog.registry import auditlog # type: ignore
|
|||
|
||||
from .contact import Contact
|
||||
from .domain_application import DomainApplication
|
||||
from .domain_information import DomainInformation
|
||||
from .domain import Domain
|
||||
from .host_ip import HostIP
|
||||
from .host import Host
|
||||
|
@ -15,6 +16,7 @@ from .website import Website
|
|||
__all__ = [
|
||||
"Contact",
|
||||
"DomainApplication",
|
||||
"DomainInformation",
|
||||
"Domain",
|
||||
"DomainInvitation",
|
||||
"HostIP",
|
||||
|
|
|
@ -9,7 +9,7 @@ from django_fsm import FSMField, transition # type: ignore
|
|||
|
||||
from .utility.time_stamped_model import TimeStampedModel
|
||||
from ..utility.email import send_templated_email, EmailSendingError
|
||||
|
||||
from itertools import chain
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -520,6 +520,10 @@ class DomainApplication(TimeStampedModel):
|
|||
Domain = apps.get_model("registrar.Domain")
|
||||
created_domain, _ = Domain.objects.get_or_create(name=self.requested_domain)
|
||||
|
||||
# copy the information from domainapplication into domaininformation
|
||||
DomainInformation = apps.get_model("registrar.DomainInformation")
|
||||
DomainInformation.create_from_da(self)
|
||||
|
||||
# create the permission for the user
|
||||
UserDomainRole = apps.get_model("registrar.UserDomainRole")
|
||||
UserDomainRole.objects.get_or_create(
|
||||
|
@ -577,3 +581,26 @@ class DomainApplication(TimeStampedModel):
|
|||
if self.organization_type == DomainApplication.OrganizationChoices.FEDERAL:
|
||||
return True
|
||||
return False
|
||||
|
||||
def to_dict(self):
|
||||
"""This is to process to_dict for Domain Information, making it friendly
|
||||
to "copy" it
|
||||
|
||||
More information can be found at this- (This used #5)
|
||||
https://stackoverflow.com/questions/21925671/convert-django-model-object-to-dict-with-all-of-the-fields-intact/29088221#29088221
|
||||
""" # noqa 590
|
||||
opts = self._meta
|
||||
data = {}
|
||||
for field in chain(opts.concrete_fields, opts.private_fields):
|
||||
if field.get_internal_type() in ("ForeignKey", "OneToOneField"):
|
||||
# get the related instance of the FK value
|
||||
fk_id = field.value_from_object(self)
|
||||
if fk_id:
|
||||
data[field.name] = field.related_model.objects.get(id=fk_id)
|
||||
else:
|
||||
data[field.name] = None
|
||||
else:
|
||||
data[field.name] = field.value_from_object(self)
|
||||
for field in opts.many_to_many:
|
||||
data[field.name] = field.value_from_object(self)
|
||||
return data
|
||||
|
|
250
src/registrar/models/domain_information.py
Normal file
250
src/registrar/models/domain_information.py
Normal file
|
@ -0,0 +1,250 @@
|
|||
from __future__ import annotations
|
||||
from .domain_application import DomainApplication
|
||||
from .utility.time_stamped_model import TimeStampedModel
|
||||
|
||||
import logging
|
||||
|
||||
from django.db import models
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DomainInformation(TimeStampedModel):
|
||||
|
||||
"""A registrant's domain information for that domain, exported from
|
||||
DomainApplication. We use these field from DomainApplication with few exceptation
|
||||
which are 'removed' via pop at the bottom of this file. Most of design for domain
|
||||
management's user information are based on application, but we cannot change
|
||||
the application once approved, so copying them that way we can make changes
|
||||
after its approved. Most fields here are copied from Application."""
|
||||
|
||||
StateTerritoryChoices = DomainApplication.StateTerritoryChoices
|
||||
|
||||
OrganizationChoices = DomainApplication.OrganizationChoices
|
||||
|
||||
BranchChoices = DomainApplication.BranchChoices
|
||||
|
||||
AGENCY_CHOICES = DomainApplication.AGENCY_CHOICES
|
||||
|
||||
# This is the application user who created this application. The contact
|
||||
# information that they gave is in the `submitter` field
|
||||
creator = models.ForeignKey(
|
||||
"registrar.User",
|
||||
on_delete=models.PROTECT,
|
||||
related_name="information_created",
|
||||
)
|
||||
|
||||
domain_application = models.OneToOneField(
|
||||
"registrar.DomainApplication",
|
||||
on_delete=models.PROTECT,
|
||||
blank=True,
|
||||
null=True,
|
||||
related_name="domainapplication_info",
|
||||
help_text="Associated domain application",
|
||||
unique=True,
|
||||
)
|
||||
|
||||
# ##### data fields from the initial form #####
|
||||
organization_type = models.CharField(
|
||||
max_length=255,
|
||||
choices=OrganizationChoices.choices,
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="Type of Organization",
|
||||
)
|
||||
|
||||
federally_recognized_tribe = models.BooleanField(
|
||||
null=True,
|
||||
help_text="Is the tribe federally recognized",
|
||||
)
|
||||
|
||||
state_recognized_tribe = models.BooleanField(
|
||||
null=True,
|
||||
help_text="Is the tribe recognized by a state",
|
||||
)
|
||||
|
||||
tribe_name = models.TextField(
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="Name of tribe",
|
||||
)
|
||||
|
||||
federal_agency = models.TextField(
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="Federal agency",
|
||||
)
|
||||
|
||||
federal_type = models.CharField(
|
||||
max_length=50,
|
||||
choices=BranchChoices.choices,
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="Federal government branch",
|
||||
)
|
||||
|
||||
is_election_board = models.BooleanField(
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="Is your organization an election office?",
|
||||
)
|
||||
|
||||
organization_name = models.TextField(
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="Organization name",
|
||||
db_index=True,
|
||||
)
|
||||
address_line1 = models.TextField(
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="Street address",
|
||||
)
|
||||
address_line2 = models.CharField(
|
||||
max_length=15,
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="Street address line 2",
|
||||
)
|
||||
city = models.TextField(
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="City",
|
||||
)
|
||||
state_territory = models.CharField(
|
||||
max_length=2,
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="State, territory, or military post",
|
||||
)
|
||||
zipcode = models.CharField(
|
||||
max_length=10,
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="Zip code",
|
||||
db_index=True,
|
||||
)
|
||||
urbanization = models.TextField(
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="Urbanization (Puerto Rico only)",
|
||||
)
|
||||
|
||||
type_of_work = models.TextField(
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="Type of work of the organization",
|
||||
)
|
||||
|
||||
more_organization_information = models.TextField(
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="Further information about the government organization",
|
||||
)
|
||||
|
||||
authorizing_official = models.ForeignKey(
|
||||
"registrar.Contact",
|
||||
null=True,
|
||||
blank=True,
|
||||
related_name="information_authorizing_official",
|
||||
on_delete=models.PROTECT,
|
||||
)
|
||||
|
||||
domain = models.OneToOneField(
|
||||
"registrar.Domain",
|
||||
on_delete=models.PROTECT,
|
||||
blank=True,
|
||||
null=True,
|
||||
# Access this information via Domain as "domain.domain_info"
|
||||
related_name="domain_info",
|
||||
help_text="Domain to which this information belongs",
|
||||
)
|
||||
|
||||
# This is the contact information provided by the applicant. The
|
||||
# application user who created it is in the `creator` field.
|
||||
submitter = models.ForeignKey(
|
||||
"registrar.Contact",
|
||||
null=True,
|
||||
blank=True,
|
||||
related_name="submitted_applications_information",
|
||||
on_delete=models.PROTECT,
|
||||
)
|
||||
|
||||
purpose = models.TextField(
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="Purpose of your domain",
|
||||
)
|
||||
|
||||
other_contacts = models.ManyToManyField(
|
||||
"registrar.Contact",
|
||||
blank=True,
|
||||
related_name="contact_applications_information",
|
||||
)
|
||||
|
||||
no_other_contacts_rationale = models.TextField(
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="Reason for listing no additional contacts",
|
||||
)
|
||||
|
||||
anything_else = models.TextField(
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="Anything else we should know?",
|
||||
)
|
||||
|
||||
is_policy_acknowledged = models.BooleanField(
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="Acknowledged .gov acceptable use policy",
|
||||
)
|
||||
security_email = models.EmailField(
|
||||
max_length=320,
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="Security email for public use",
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
try:
|
||||
if self.domain and self.domain.name:
|
||||
return self.domain.name
|
||||
else:
|
||||
return f"domain info set up and created by {self.creator}"
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
@classmethod
|
||||
def create_from_da(cls, domain_application):
|
||||
"""Takes in a DomainApplication dict and converts it into DomainInformation"""
|
||||
da_dict = domain_application.to_dict()
|
||||
# remove the id so one can be assinged on creation
|
||||
da_id = da_dict.pop("id")
|
||||
# check if we have a record that corresponds with the domain
|
||||
# application, if so short circuit the create
|
||||
domain_info = cls.objects.filter(domain_application__id=da_id).first()
|
||||
if domain_info:
|
||||
return domain_info
|
||||
# the following information below is not needed in the domain information:
|
||||
da_dict.pop("status")
|
||||
da_dict.pop("current_websites")
|
||||
da_dict.pop("investigator")
|
||||
da_dict.pop("alternative_domains")
|
||||
# use the requested_domain to create information for this domain
|
||||
da_dict["domain"] = da_dict.pop("requested_domain")
|
||||
other_contacts = da_dict.pop("other_contacts")
|
||||
domain_info = cls(**da_dict)
|
||||
domain_info.domain_application = domain_application
|
||||
# Save so the object now have PK
|
||||
# (needed to process the manytomany below before, first)
|
||||
domain_info.save()
|
||||
|
||||
# Process the remaining "many to many" stuff
|
||||
domain_info.other_contacts.add(*other_contacts)
|
||||
domain_info.save()
|
||||
return domain_info
|
||||
|
||||
class Meta:
|
||||
verbose_name_plural = "Domain Information"
|
|
@ -52,7 +52,7 @@
|
|||
src="{% static 'img/CISA_logo.png' %}"
|
||||
alt="CISA logo"
|
||||
role="img"
|
||||
width="48px"
|
||||
width="56px"
|
||||
/></a>
|
||||
</div>
|
||||
<section
|
||||
|
|
|
@ -4,6 +4,7 @@ from django.db.utils import IntegrityError
|
|||
from registrar.models import (
|
||||
Contact,
|
||||
DomainApplication,
|
||||
DomainInformation,
|
||||
User,
|
||||
Website,
|
||||
Domain,
|
||||
|
@ -63,6 +64,33 @@ class TestDomainApplication(TestCase):
|
|||
application.other_contacts.add(contact)
|
||||
application.save()
|
||||
|
||||
def test_domain_info(self):
|
||||
"""Can create domain info with all fields."""
|
||||
user, _ = User.objects.get_or_create()
|
||||
contact = Contact.objects.create()
|
||||
domain, _ = Domain.objects.get_or_create(name="igorville.gov")
|
||||
information = DomainInformation.objects.create(
|
||||
creator=user,
|
||||
organization_type=DomainInformation.OrganizationChoices.FEDERAL,
|
||||
federal_type=DomainInformation.BranchChoices.EXECUTIVE,
|
||||
is_election_board=False,
|
||||
organization_name="Test",
|
||||
address_line1="100 Main St.",
|
||||
address_line2="APT 1A",
|
||||
state_territory="CA",
|
||||
zipcode="12345-6789",
|
||||
authorizing_official=contact,
|
||||
submitter=contact,
|
||||
purpose="Igorville rules!",
|
||||
anything_else="All of Igorville loves the dotgov program.",
|
||||
is_policy_acknowledged=True,
|
||||
domain=domain,
|
||||
)
|
||||
information.other_contacts.add(contact)
|
||||
information.save()
|
||||
self.assertEqual(information.domain.id, domain.id)
|
||||
self.assertEqual(information.id, domain.domain_info.id)
|
||||
|
||||
def test_status_fsm_submit_fail(self):
|
||||
user, _ = User.objects.get_or_create()
|
||||
application = DomainApplication.objects.create(creator=user)
|
||||
|
@ -166,6 +194,24 @@ class TestPermissions(TestCase):
|
|||
self.assertTrue(UserDomainRole.objects.get(user=user, domain=domain))
|
||||
|
||||
|
||||
class TestDomainInfo(TestCase):
|
||||
|
||||
"""Test creation of Domain Information when approved."""
|
||||
|
||||
def test_approval_creates_info(self):
|
||||
domain, _ = Domain.objects.get_or_create(name="igorville.gov")
|
||||
user, _ = User.objects.get_or_create()
|
||||
application = DomainApplication.objects.create(
|
||||
creator=user, requested_domain=domain
|
||||
)
|
||||
# skip using the submit method
|
||||
application.status = DomainApplication.SUBMITTED
|
||||
application.approve()
|
||||
|
||||
# should be an information present for this domain
|
||||
self.assertTrue(DomainInformation.objects.get(domain=domain))
|
||||
|
||||
|
||||
class TestInvitations(TestCase):
|
||||
|
||||
"""Test the retrieval of invitations."""
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue