Merge branch 'main' into nmb/nameservers

This commit is contained in:
Neil Martinsen-Burrell 2023-05-12 10:38:35 -05:00
commit d60fca1c7f
No known key found for this signature in database
GPG key ID: 6A3C818CC10D0184
19 changed files with 899 additions and 44 deletions

View file

@ -40,7 +40,7 @@ jobs:
docker compose run node npx gulp compile
- name: Collect static assets
working-directory: ./src
run: docker compose run app python manage.py collectstatic
run: docker compose run app python manage.py collectstatic --no-input
- name: Deploy to cloud.gov sandbox
uses: 18f/cg-deploy-action@main
env:
@ -69,4 +69,4 @@ jobs:
owner: context.repo.owner,
repo: context.repo.repo,
body: '🥳 Successfully deployed to developer sandbox **[${{ env.ENVIRONMENT }}](https://getgov-${{ env.ENVIRONMENT }}.app.cloud.gov/)**.'
})
})

View file

@ -49,7 +49,7 @@ class ViewsTest(TestCase):
# assert
self.assertEqual(response.status_code, 500)
self.assertTemplateUsed(response, "500.html")
self.assertIn("Server Error", response.content.decode("utf-8"))
self.assertIn("server error", response.content.decode("utf-8"))
def test_login_callback_reads_next(self, mock_client):
# setup

View file

@ -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)

View file

@ -417,6 +417,10 @@ footer {
color: color('primary');
}
.usa-identifier__logo {
height: units(7);
}
abbr[title] {
// workaround for underlining abbr element
border-bottom: none;

View file

@ -44,6 +44,21 @@ class UserFixture:
"first_name": "Neil",
"last_name": "Martinsen-Burrell",
},
{
"username": "7185e6cd-d3c8-4adc-90a3-ceddba71d24f",
"first_name": "Jon",
"last_name": "Roberts",
},
{
"username": "5f283494-31bd-49b5-b024-a7e7cae00848",
"first_name": "Rachid",
"last_name": "Mrad",
},
{
"username": "eb2214cd-fc0c-48c0-9dbd-bc4cd6820c74",
"first_name": "Alysia",
"last_name": "Broddrick",
},
]
@classmethod

View 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",
},
),
]

View file

@ -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,
),
),
]

View file

@ -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",

View file

@ -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

View 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"

View file

@ -0,0 +1,20 @@
<svg width="404" height="409" viewBox="0 0 404 409" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M291.707 328.743C240.024 358.583 133.444 374.87 78.8899 280.379C14.3648 168.618 78.2559 99.3488 140.956 63.1491C203.655 26.9495 296.801 80.4848 337.226 150.503C377.652 220.522 343.391 298.903 291.707 328.743Z" fill="#F5F8FA"/>
<circle cx="276.88" cy="130.594" r="8" transform="rotate(135 276.88 130.594)" fill="#7AA5C1"/>
<circle cx="288.196" cy="119.279" r="8" transform="rotate(135 288.196 119.279)" fill="#7AA5C1"/>
<circle cx="231.626" cy="175.849" r="8" transform="rotate(135 231.626 175.849)" fill="#7AA5C1"/>
<circle cx="186.371" cy="221.104" r="8" transform="rotate(135 186.371 221.104)" fill="#7AA5C1"/>
<circle cx="242.939" cy="164.535" r="8" transform="rotate(135 242.939 164.535)" fill="#7AA5C1"/>
<circle cx="197.686" cy="209.788" r="8" transform="rotate(135 197.686 209.788)" fill="#7AA5C1"/>
<circle cx="220.312" cy="187.163" r="8" transform="rotate(135 220.312 187.163)" fill="#7AA5C1"/>
<circle cx="175.057" cy="232.417" r="8" transform="rotate(135 175.057 232.417)" fill="#7AA5C1"/>
<circle cx="163.743" cy="243.731" r="8" transform="rotate(135 163.743 243.731)" fill="#7AA5C1"/>
<circle cx="152.43" cy="255.045" r="8" transform="rotate(135 152.43 255.045)" fill="#7AA5C1"/>
<circle cx="141.116" cy="266.358" r="8" transform="rotate(135 141.116 266.358)" fill="#7AA5C1"/>
<circle cx="129.802" cy="277.672" r="8" transform="rotate(135 129.802 277.672)" fill="#7AA5C1"/>
<circle cx="118.489" cy="288.986" r="8" transform="rotate(135 118.489 288.986)" fill="#7AA5C1"/>
<circle cx="254.253" cy="153.221" r="8" transform="rotate(135 254.253 153.221)" fill="#7AA5C1"/>
<circle cx="265.566" cy="141.908" r="8" transform="rotate(135 265.566 141.908)" fill="#7AA5C1"/>
<circle cx="208.998" cy="198.476" r="8" transform="rotate(135 208.998 198.476)" fill="#7AA5C1"/>
<circle cx="203.342" cy="203.999" r="120.001" stroke="#7AA5C1" stroke-width="16"/>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 53 KiB

View file

@ -0,0 +1,59 @@
<svg width="409" height="214" viewBox="0 0 409 214" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M366.004 90.4612C372.017 135.603 322.608 199.102 196.168 205.902C-32.9139 218.22 19.0655 18.8457 205.511 8.81994C299.204 3.78172 359.99 45.3195 366.004 90.4612Z" fill="#F5F8FA"/>
<circle cx="213.873" cy="37.4943" r="6.56803" fill="#7AA5C1"/>
<circle cx="212.214" cy="58.4272" r="6.56803" fill="#7AA5C1"/>
<circle cx="231.089" cy="66.2297" r="6.56803" fill="#7AA5C1"/>
<circle cx="235.451" cy="85.568" r="6.56803" fill="#7AA5C1"/>
<circle cx="252.535" cy="99.7787" r="6.56803" fill="#7AA5C1"/>
<circle cx="273.981" cy="115.121" r="6.56803" fill="#7AA5C1"/>
<circle cx="372.072" cy="182.568" r="6.56803" fill="#7AA5C1"/>
<circle cx="402.423" cy="189.642" r="6.56803" fill="#7AA5C1"/>
<circle cx="220.441" cy="99.6379" r="6.56803" fill="#7AA5C1"/>
<circle cx="231.089" cy="118.336" r="6.56803" fill="#7AA5C1"/>
<circle cx="262.722" cy="143.568" r="6.56803" fill="#7AA5C1"/>
<circle cx="248.722" cy="125.568" r="6.56803" fill="#7AA5C1"/>
<circle cx="288.537" cy="138.04" r="6.56803" fill="#7AA5C1"/>
<circle cx="300.051" cy="160.304" r="6.56803" fill="#7AA5C1"/>
<circle cx="338.722" cy="170.568" r="6.56803" fill="#7AA5C1"/>
<circle cx="349.914" cy="189.575" r="6.56803" fill="#7AA5C1"/>
<circle cx="330.21" cy="189.575" r="6.56803" fill="#7AA5C1"/>
<circle cx="316.722" cy="173.568" r="6.56803" fill="#7AA5C1"/>
<circle cx="305.722" cy="190.568" r="6.56803" fill="#7AA5C1"/>
<circle cx="287.722" cy="184.568" r="6.56803" fill="#7AA5C1"/>
<circle cx="269.894" cy="183.007" r="6.56803" fill="#7AA5C1"/>
<circle cx="250.19" cy="189.575" r="6.56803" fill="#7AA5C1"/>
<circle cx="220.722" cy="192.568" r="6.56803" fill="#7AA5C1"/>
<circle cx="233.722" cy="173.568" r="6.56803" fill="#7AA5C1"/>
<circle cx="252.722" cy="165.568" r="6.56803" fill="#7AA5C1"/>
<circle cx="180.203" cy="189.575" r="6.56803" fill="#7AA5C1"/>
<circle cx="161.329" cy="196.143" r="6.56803" fill="#7AA5C1"/>
<circle cx="140.795" cy="189.575" r="6.56803" fill="#7AA5C1"/>
<path d="M122.733 189.575C122.733 193.203 119.793 196.143 116.165 196.143C112.538 196.143 109.597 193.203 109.597 189.575C109.597 185.948 112.538 183.007 116.165 183.007C119.793 183.007 122.733 185.948 122.733 189.575Z" fill="#7AA5C1"/>
<circle cx="91.5343" cy="189.642" r="6.56803" fill="#7AA5C1"/>
<circle cx="57.8852" cy="185.432" r="6.56803" fill="#7AA5C1"/>
<circle cx="198.722" cy="195.568" r="6.56803" fill="#7AA5C1"/>
<circle cx="193.34" cy="70.2917" r="6.56803" fill="#7AA5C1"/>
<circle cx="98.9323" cy="156.735" r="6.56803" fill="#7AA5C1"/>
<circle cx="25.247" cy="189.642" r="6.56803" fill="#7AA5C1"/>
<circle cx="7.26164" cy="190.584" r="6.56803" fill="#7AA5C1"/>
<circle cx="76.7592" cy="173.44" r="6.56803" fill="#7AA5C1"/>
<circle cx="129.722" cy="172.568" r="6.56803" fill="#7AA5C1"/>
<circle cx="147.722" cy="164.568" r="6.56803" fill="#7AA5C1"/>
<circle cx="164.722" cy="173.568" r="6.56803" fill="#7AA5C1"/>
<circle cx="174.465" cy="150.167" r="6.56803" fill="#7AA5C1"/>
<circle cx="186.771" cy="169.871" r="6.56803" fill="#7AA5C1"/>
<circle cx="198.266" cy="150.167" r="6.56803" fill="#7AA5C1"/>
<circle cx="232.747" cy="151.176" r="6.56803" fill="#7AA5C1"/>
<circle cx="273.722" cy="162.568" r="6.56803" fill="#7AA5C1"/>
<circle cx="210.589" cy="163.303" r="6.56803" fill="#7AA5C1"/>
<circle cx="204.834" cy="124.904" r="6.56803" fill="#7AA5C1"/>
<circle cx="193.34" cy="108.553" r="6.56803" fill="#7AA5C1"/>
<circle cx="171.181" cy="119.345" r="6.56803" fill="#7AA5C1"/>
<circle cx="160.499" cy="138.04" r="6.56803" fill="#7AA5C1"/>
<circle cx="139.153" cy="144.608" r="6.56803" fill="#7AA5C1"/>
<circle cx="170.351" cy="95.4167" r="6.56803" fill="#7AA5C1"/>
<circle cx="151.477" cy="115.121" r="6.56803" fill="#7AA5C1"/>
<circle cx="125.204" cy="137.031" r="6.56803" fill="#7AA5C1"/>
<circle cx="112.881" cy="163.303" r="6.56803" fill="#7AA5C1"/>
<circle cx="204.834" cy="86.6427" r="6.56803" fill="#7AA5C1"/>
</svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

View file

@ -1,28 +1,45 @@
{% extends "base.html" %}
{% load i18n %}
{% load i18n static %}
{% block title %}{% translate "Unauthorized" %}{% endblock %}
{% block title %}{% translate "Unauthorized | " %}{% endblock %}
{% block content %}
<main id="main-content" class="grid-container">
<h1>{% translate "Unauthorized" %}</h1>
<div class="grid-row grow-gap">
<div class="tablet:grid-col-6 usa-prose margin-bottom-3">
<h1>
{% translate "You are not authorized to view this page" %}
</h1>
<h2>
{% translate "Status 401" %}
</h2>
{% if friendly_message %}
<p>{{ friendly_message }}</p>
{% else %}
<p>{% translate "Authorization failed." %}</p>
{% endif %}
<p><a href="{% url 'login' %}">
{% translate "Would you like to try logging in again?" %}
</a></p>
{% if friendly_message %}
<p>{{ friendly_message }}</p>
{% else %}
<p>{% translate "Authorization failed." %}</p>
{% endif %}
<p>
You must be an authorized user and need to be signed in to view this page.
Would you like to <a href="{% url 'login' %}"> try logging in again?</a>
</p>
<p>
If you would like help with this error <a href="https://federalist-877ab29f-16f6-4f12-961c-96cf064cf070.sites.pages.cloud.gov/site/cisagov/getgov-home/contact/"> contact us </a>
</p>
{% if log_identifier %}
<p>Here's a unique identifier for this error.</p>
<blockquote>{{ log_identifier }}</blockquote>
<p>{% translate "Please include it if you contact us." %}</p>
{% endif %}
TODO: Content team to create a "how to contact us" footer for the error pages
{% if log_identifier %}
<p>Here's a unique identifier for this error.</p>
<p class="text-semibold">{{ log_identifier }}</p>
<p>{% translate "Please include it if you contact us." %}</p>
{% endif %}
</div>
<div class="tablet:grid-col-4">
<img
src="{% static 'img/registrar/dotgov_401_illo.svg' %}"
alt=""
/>
</div>
</div>
</main>
{% endblock %}

View file

@ -0,0 +1,45 @@
{% extends "base.html" %}
{% load i18n static %}
{% block title %}{% translate "Forbidden | " %}{% endblock %}
{% block content %}
<main id="main-content" class="grid-container">
<div class="grid-row grow-gap">
<div class="tablet:grid-col-6 usa-prose margin-bottom-3">
<h1>
{% translate "You do not have the right permissions to view this page." %}
</h1>
<h2>
{% translate "Status 403" %}
</h2>
{% if friendly_message %}
<p>{{ friendly_message }}</p>
{% else %}
<p>{% translate "Forbidden." %}</p>
{% endif %}
<p>
You must be an authorized user and need to be signed in to view this page.
Would you like to <a href="{% url 'login' %}"> try logging in again?</a>
</p>
<p>
If you would like help with this error <a href="https://federalist-877ab29f-16f6-4f12-961c-96cf064cf070.sites.pages.cloud.gov/site/cisagov/getgov-home/contact/"> contact us </a>
</p>
{% if log_identifier %}
<p>Here's a unique identifier for this error.</p>
<p class="text-semibold">{{ log_identifier }}</p>
<p>{% translate "Please include it if you contact us." %}</p>
{% endif %}
</div>
<div class="tablet:grid-col-4">
<img
src="{% static 'img/registrar/dotgov_401_illo.svg' %}"
alt=""
/>
</div>
</div>
</main>
{% endblock %}

View file

@ -1,15 +1,31 @@
{% extends "base.html" %}
{% load i18n %}
{% load i18n static %}
{% block title %}{% translate "Page not found" %}{% endblock %}
{% block title %}{% translate "Page not found | " %}{% endblock %}
{% block content %}
<main id="main-content" class="grid-container">
<div class="grid-row grid-gap">
<div class="tablet:grid-col-6 usa-prose margin-bottom-3">
<h1>
{% translate "We couldnt find that page" %}
</h1>
<h2>
{% translate "Status 404" %}
</h2>
<h1>{% translate "Page not found" %}</h1>
<p> Try going to the <a href="/">homepage</a>. If you cant find what youre looking for, <a href= "https://federalist-877ab29f-16f6-4f12-961c-96cf064cf070.sites.pages.cloud.gov/site/cisagov/getgov-home/contact/">contact us.</a>
</p>
</div>
<div class="tablet:grid-col-4">
<img
src="{% static 'img/registrar/dotgov_404_illo.svg' %}"
alt=""
/>
</div>
</div>
<p>{% translate "The requested page could not be found." %}</p>
</main>
{% endblock %}

View file

@ -1,24 +1,39 @@
{% extends "base.html" %}
{% load i18n %}
{% load i18n static %}
{% block title %}{% translate "Server error" %}{% endblock %}
{% block title %}{% translate "Server error | " %}{% endblock %}
{% block content %}
<main id="main-content" class="grid-container">
<h1>{% translate "Server Error" %}</h1>
<div class="grid-row grid-gap">
<div class="tablet:grid-col-6 usa-prose margin-bottom-3">
<h1>
{% translate "We're having some trouble" %}
</h1>
<h2>
{% translate "Status 500 server error" %}
</h2>
{% if friendly_message %}
<p>{{ friendly_message }}</p>
{% else %}
<p>
Sorry! Try waiting a few minutes and then reloading the page.
<a href="https://federalist-877ab29f-16f6-4f12-961c-96cf064cf070.sites.pages.cloud.gov/site/cisagov/getgov-home/contact/"> Contact us </a> if you need help.
</p>
{% endif %}
{% if friendly_message %}
<p>{{ friendly_message }}</p>
{% else %}
<p>{% translate "An internal server error occurred." %}</p>
{% endif %}
{% if log_identifier %}
<p>Here's a unique identifier for this error.</p>
<blockquote>{{ log_identifier }}</blockquote>
<p>{% translate "Please include it if you contact us." %}</p>
{% endif %}
TODO: Content team to create a "how to contact us" footer for the error pages
{% if log_identifier %}
<p>Here's a unique identifier for this error.</p>
<p class="text-semibold">{{ log_identifier }}</p>
<p>{% translate "Please include it if you contact us." %}</p>
{% endif %}
</div>
<div class="tablet:grid-col-4 flex-align-self-end">
<img
src="{%static 'img/registrar/dotgov_500_illo.svg' %}"
alt=""
/>
</div>
</div>
</main>
{% endblock %}

View file

@ -52,7 +52,7 @@
src="{% static 'img/CISA_logo.png' %}"
alt="CISA logo"
role="img"
width="48px"
width="56px"
/></a>
</div>
<section

View file

@ -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."""