mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-08-03 00:12:16 +02:00
Merge branch 'main' into bl/test-commit-signing
This commit is contained in:
commit
99eb0cd484
54 changed files with 1771 additions and 442 deletions
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"defaults": {
|
||||
"concurrency": 1,
|
||||
"timeout": 10000,
|
||||
"timeout": 30000,
|
||||
"hideElements": "a[href='/whoami/']"
|
||||
|
||||
},
|
||||
|
|
742
src/Pipfile.lock
generated
742
src/Pipfile.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -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
|
||||
|
|
|
@ -62,6 +62,9 @@ services:
|
|||
- POSTGRES_PASSWORD=feedabee
|
||||
|
||||
node:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: node.Dockerfile
|
||||
image: node
|
||||
volumes:
|
||||
- .:/app
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -230,4 +230,34 @@ function handleValidationClick(e) {
|
|||
})();
|
||||
|
||||
|
||||
/**
|
||||
* An IIFE that attaches a click handler for our dynamic nameservers form
|
||||
*
|
||||
* Only does something on a single page, but it should be fast enough to run
|
||||
* it everywhere.
|
||||
*/
|
||||
(function prepareForms() {
|
||||
let serverForm = document.querySelectorAll(".server-form")
|
||||
let container = document.querySelector("#form-container")
|
||||
let addButton = document.querySelector("#add-form")
|
||||
let totalForms = document.querySelector("#id_form-TOTAL_FORMS")
|
||||
|
||||
let formNum = serverForm.length-1
|
||||
addButton.addEventListener('click', addForm)
|
||||
|
||||
function addForm(e){
|
||||
let newForm = serverForm[2].cloneNode(true)
|
||||
let formNumberRegex = RegExp(`form-(\\d){1}-`,'g')
|
||||
let formLabelRegex = RegExp(`Name server (\\d){1}`, 'g')
|
||||
let formExampleRegex = RegExp(`ns(\\d){1}`, 'g')
|
||||
|
||||
formNum++
|
||||
newForm.innerHTML = newForm.innerHTML.replace(formNumberRegex, `form-${formNum}-`)
|
||||
newForm.innerHTML = newForm.innerHTML.replace(formLabelRegex, `Name server ${formNum+1}`)
|
||||
newForm.innerHTML = newForm.innerHTML.replace(formExampleRegex, `ns${formNum+1}`)
|
||||
container.insertBefore(newForm, addButton)
|
||||
newForm.querySelector("input").value = ""
|
||||
|
||||
totalForms.setAttribute('value', `${formNum+1}`)
|
||||
}
|
||||
})();
|
||||
|
|
|
@ -417,8 +417,18 @@ footer {
|
|||
color: color('primary');
|
||||
}
|
||||
|
||||
.usa-identifier__logo {
|
||||
height: units(7);
|
||||
}
|
||||
|
||||
abbr[title] {
|
||||
// workaround for underlining abbr element
|
||||
border-bottom: none;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.usa-textarea {
|
||||
@include at-media('tablet') {
|
||||
height: units('mobile');
|
||||
}
|
||||
}
|
|
@ -78,6 +78,11 @@ urlpatterns = [
|
|||
),
|
||||
path("domain/<int:pk>", views.DomainView.as_view(), name="domain"),
|
||||
path("domain/<int:pk>/users", views.DomainUsersView.as_view(), name="domain-users"),
|
||||
path(
|
||||
"domain/<int:pk>/nameservers",
|
||||
views.DomainNameserversView.as_view(),
|
||||
name="domain-nameservers",
|
||||
),
|
||||
path(
|
||||
"domain/<int:pk>/users/add",
|
||||
views.DomainAddUserView.as_view(),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
from .application_wizard import *
|
||||
from .domain import DomainAddUserForm
|
||||
from .domain import DomainAddUserForm, NameserverFormset
|
||||
|
|
|
@ -5,7 +5,7 @@ from typing import Callable
|
|||
from phonenumber_field.formfields import PhoneNumberField # type: ignore
|
||||
|
||||
from django import forms
|
||||
from django.core.validators import RegexValidator
|
||||
from django.core.validators import RegexValidator, MaxLengthValidator
|
||||
from django.urls import reverse
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
|
@ -315,6 +315,12 @@ class TypeOfWorkForm(RegistrarForm):
|
|||
# label has to end in a space to get the label_suffix to show
|
||||
label="What type of work does your organization do? ",
|
||||
widget=forms.Textarea(),
|
||||
validators=[
|
||||
MaxLengthValidator(
|
||||
1000,
|
||||
message="Response must be less than 1000 characters.",
|
||||
)
|
||||
],
|
||||
error_messages={"required": "Enter the type of work your organization does."},
|
||||
)
|
||||
|
||||
|
@ -327,6 +333,12 @@ class TypeOfWorkForm(RegistrarForm):
|
|||
" support your claims. "
|
||||
),
|
||||
widget=forms.Textarea(),
|
||||
validators=[
|
||||
MaxLengthValidator(
|
||||
1000,
|
||||
message="Response must be less than 1000 characters.",
|
||||
)
|
||||
],
|
||||
error_messages={
|
||||
"required": (
|
||||
"Describe how your organization is independent of a state government."
|
||||
|
@ -554,6 +566,12 @@ class PurposeForm(RegistrarForm):
|
|||
purpose = forms.CharField(
|
||||
label="Purpose",
|
||||
widget=forms.Textarea(),
|
||||
validators=[
|
||||
MaxLengthValidator(
|
||||
1000,
|
||||
message="Response must be less than 1000 characters.",
|
||||
)
|
||||
],
|
||||
error_messages={
|
||||
"required": "Describe how you'll use the .gov domain you’re requesting."
|
||||
},
|
||||
|
@ -696,6 +714,12 @@ class AnythingElseForm(RegistrarForm):
|
|||
required=False,
|
||||
label="Anything else we should know?",
|
||||
widget=forms.Textarea(),
|
||||
validators=[
|
||||
MaxLengthValidator(
|
||||
1000,
|
||||
message="Response must be less than 1000 characters.",
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
"""Forms for domain management."""
|
||||
|
||||
from django import forms
|
||||
from django.forms import formset_factory
|
||||
|
||||
|
||||
class DomainAddUserForm(forms.Form):
|
||||
|
@ -8,3 +9,16 @@ class DomainAddUserForm(forms.Form):
|
|||
"""Form for adding a user to a domain."""
|
||||
|
||||
email = forms.EmailField(label="Email")
|
||||
|
||||
|
||||
class DomainNameserverForm(forms.Form):
|
||||
|
||||
"""Form for changing nameservers."""
|
||||
|
||||
server = forms.CharField(label="Name server")
|
||||
|
||||
|
||||
NameserverFormset = formset_factory(
|
||||
DomainNameserverForm,
|
||||
extra=1,
|
||||
)
|
||||
|
|
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",
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import logging
|
||||
import re
|
||||
|
||||
from typing import List
|
||||
|
||||
from django.apps import apps
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import models
|
||||
|
@ -215,6 +217,24 @@ class Domain(TimeStampedModel):
|
|||
def __str__(self) -> str:
|
||||
return self.name
|
||||
|
||||
def nameservers(self) -> List[str]:
|
||||
"""A list of the nameservers for this domain.
|
||||
|
||||
TODO: call EPP to get this info instead of returning fake data.
|
||||
"""
|
||||
return [
|
||||
# reserved example domain
|
||||
"ns1.example.com",
|
||||
"ns2.example.com",
|
||||
"ns3.example.com",
|
||||
]
|
||||
|
||||
def set_nameservers(self, new_nameservers: List[str]):
|
||||
"""Set the nameservers for this domain."""
|
||||
# TODO: call EPP to set these values in the registry instead of doing
|
||||
# nothing.
|
||||
logger.warn("TODO: Fake setting nameservers to %s", new_nameservers)
|
||||
|
||||
@property
|
||||
def roid(self):
|
||||
return self._get_property("roid")
|
||||
|
|
|
@ -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"
|
20
src/registrar/public/img/registrar/dotgov_401_illo.svg
Normal file
20
src/registrar/public/img/registrar/dotgov_401_illo.svg
Normal 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 |
18
src/registrar/public/img/registrar/dotgov_404_illo.svg
Normal file
18
src/registrar/public/img/registrar/dotgov_404_illo.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 53 KiB |
59
src/registrar/public/img/registrar/dotgov_500_illo.svg
Normal file
59
src/registrar/public/img/registrar/dotgov_500_illo.svg
Normal 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 |
|
@ -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 %}
|
||||
|
|
45
src/registrar/templates/403.html
Normal file
45
src/registrar/templates/403.html
Normal 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 %}
|
|
@ -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 couldn’t 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 can’t find what you’re 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 %}
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
|
||||
{% block form_fields %}
|
||||
{% with add_label_class="usa-sr-only" attr_maxlength=500 %}
|
||||
{% with add_label_class="usa-sr-only" attr_maxlength=1000 %}
|
||||
{% input_with_errors forms.0.anything_else %}
|
||||
{% endwith %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -19,7 +19,7 @@ Read about <a href="{% url 'todo' %}">activities that are prohibited on .gov dom
|
|||
|
||||
|
||||
{% block form_fields %}
|
||||
{% with attr_maxlength=500 add_label_class="usa-sr-only" %}
|
||||
{% with attr_maxlength=1000 add_label_class="usa-sr-only" %}
|
||||
{% input_with_errors forms.0.purpose %}
|
||||
{% endwith %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
|
||||
{% block form_fields %}
|
||||
{% with attr_maxlength=500 %}
|
||||
{% with attr_maxlength=1000 %}
|
||||
{% input_with_errors forms.0.type_of_work %}
|
||||
{% input_with_errors forms.0.more_organization_information %}
|
||||
{% endwith %}
|
||||
|
|
|
@ -4,5 +4,6 @@
|
|||
{# hint: spacing in the class string matters #}
|
||||
class="{{ uswds_input_class }}{% if classes %} {{ classes }}{% endif %}"
|
||||
{% if widget.value != None %}value="{{ widget.value|stringformat:'s' }}"{% endif %}
|
||||
{% if sublabel_text %}aria-describedby="{{ widget.attrs.id }}__sublabel"{% endif %}
|
||||
{% include "django/forms/widgets/attrs.html" %}
|
||||
/>
|
||||
/>
|
||||
|
|
52
src/registrar/templates/domain_nameservers.html
Normal file
52
src/registrar/templates/domain_nameservers.html
Normal file
|
@ -0,0 +1,52 @@
|
|||
{% extends "domain_base.html" %}
|
||||
{% load static field_helpers%}
|
||||
|
||||
{% block title %}Domain name servers | {{ domain.name }} | {% endblock %}
|
||||
|
||||
{% block domain_content %}
|
||||
{# this is right after the messages block in the parent template #}
|
||||
{% for form in formset %}
|
||||
{% include "includes/form_errors.html" with form=form %}
|
||||
{% endfor %}
|
||||
|
||||
<h1>Domain name servers</h1>
|
||||
|
||||
<p>Before your domain can be used we'll need information about your domain
|
||||
name servers.</p>
|
||||
|
||||
<p><a class="usa-link" href="{% url "todo" %}">Get help with domain servers.</a></p>
|
||||
|
||||
{% include "includes/required_fields.html" %}
|
||||
|
||||
<form class="usa-form usa-form--large" method="post" novalidate id="form-container">
|
||||
{% csrf_token %}
|
||||
{{ formset.management_form }}
|
||||
|
||||
{% for form in formset %}
|
||||
<div class="server-form">
|
||||
{% with sublabel_text="Example: ns"|concat:forloop.counter|concat:".example.com" %}
|
||||
{% if forloop.counter <= 2 %}
|
||||
{% with attr_required=True %}
|
||||
{% input_with_errors form.server %}
|
||||
{% endwith %}
|
||||
{% else %}
|
||||
{% input_with_errors form.server %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<button type="button" class="usa-button usa-button--unstyled display-block" id="add-form">
|
||||
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24" height="24">
|
||||
<use xlink:href="{%static 'img/sprite.svg'%}#add_circle"></use>
|
||||
</svg><span class="margin-left-05">Add another name server</span>
|
||||
</button>
|
||||
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
class="usa-button"
|
||||
>Save</button>
|
||||
</form>
|
||||
|
||||
{% endblock %} {# domain_content #}
|
|
@ -13,7 +13,7 @@
|
|||
</li>
|
||||
|
||||
<li class="usa-sidenav__item">
|
||||
{% url 'todo' as url %}
|
||||
{% url 'domain-nameservers' pk=domain.id as url %}
|
||||
<a href="{{ url }}"
|
||||
{% if request.path == url %}class="usa-current"{% endif %}
|
||||
>
|
||||
|
|
|
@ -52,7 +52,7 @@
|
|||
src="{% static 'img/CISA_logo.png' %}"
|
||||
alt="CISA logo"
|
||||
role="img"
|
||||
width="48px"
|
||||
width="56px"
|
||||
/></a>
|
||||
</div>
|
||||
<section
|
||||
|
|
|
@ -28,6 +28,10 @@ error messages, if necessary.
|
|||
{% include "django/forms/label.html" %}
|
||||
{% endif %}
|
||||
|
||||
{% if sublabel_text %}
|
||||
<p id="{{ widget.attrs.id }}__sublabel" class="text-base margin-top-2px margin-bottom-1">{{ sublabel_text }}</p>
|
||||
{% endif %}
|
||||
|
||||
{% if field.errors %}
|
||||
<div id="{{ widget.attrs.id }}__error-message">
|
||||
{% for error in field.errors %}
|
||||
|
@ -71,4 +75,4 @@ error messages, if necessary.
|
|||
</span>
|
||||
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
|
|
@ -6,3 +6,9 @@ from django.template.defaulttags import register
|
|||
@register.filter
|
||||
def get_item(dictionary, key):
|
||||
return dictionary.get(key)
|
||||
|
||||
|
||||
@register.filter
|
||||
def concat(arg1, arg2):
|
||||
"""concatenate arg1 & arg2"""
|
||||
return str(arg1) + str(arg2)
|
||||
|
|
|
@ -11,6 +11,9 @@ from registrar.forms.application_wizard import (
|
|||
OtherContactsForm,
|
||||
RequirementsForm,
|
||||
TribalGovernmentForm,
|
||||
PurposeForm,
|
||||
AnythingElseForm,
|
||||
TypeOfWorkForm,
|
||||
)
|
||||
|
||||
|
||||
|
@ -85,6 +88,125 @@ class TestFormValidation(TestCase):
|
|||
["Enter an email address in the required format, like name@example.com."],
|
||||
)
|
||||
|
||||
def test_purpose_form_character_count_invalid(self):
|
||||
"""Response must be less than 1000 characters."""
|
||||
form = PurposeForm(
|
||||
data={
|
||||
"purpose": "Bacon ipsum dolor amet fatback strip steak pastrami"
|
||||
"shankle, drumstick doner chicken landjaeger turkey andouille."
|
||||
"Buffalo biltong chuck pork chop tongue bresaola turkey. Doner"
|
||||
"ground round strip steak, jowl tail chuck ribeye bacon"
|
||||
"beef ribs swine filet ball tip pancetta strip steak sirloin"
|
||||
"mignon ham spare ribs rump. Tail shank biltong beef ribs doner"
|
||||
"buffalo swine bacon. Tongue cow picanha brisket bacon chuck"
|
||||
"leberkas pork loin pork, drumstick capicola. Doner short loin"
|
||||
"ground round fatback turducken chislic shoulder turducken"
|
||||
"spare ribs, burgdoggen kielbasa kevin frankfurter ball tip"
|
||||
"pancetta cupim. Turkey meatball andouille porchetta hamburger"
|
||||
"pork chop corned beef. Brisket short ribs turducken, pork chop"
|
||||
"chislic turkey ball pork chop leberkas rump, rump bacon, jowl"
|
||||
"tip ham. Shankle salami tongue venison short ribs kielbasa"
|
||||
"tri-tip ham hock swine hamburger. Flank meatball corned beef"
|
||||
"cow sausage ball tip kielbasa ham hock. Ball tip cupim meatloaf"
|
||||
"beef ribs rump jowl tenderloin swine sausage biltong"
|
||||
"bacon rump tail boudin meatball boudin meatball boudin."
|
||||
}
|
||||
)
|
||||
self.assertEqual(
|
||||
form.errors["purpose"],
|
||||
["Response must be less than 1000 characters."],
|
||||
)
|
||||
|
||||
def test_anything_else_form_type_of_work_character_count_invalid(self):
|
||||
"""Response must be less than 1000 characters."""
|
||||
form = AnythingElseForm(
|
||||
data={
|
||||
"anything_else": "Bacon ipsum dolor amet fatback strip steak pastrami"
|
||||
"shankle, drumstick doner chicken landjaeger turkey andouille."
|
||||
"Buffalo biltong chuck pork chop tongue bresaola turkey. Doner"
|
||||
"ground round strip steak, jowl tail chuck ribeye bacon"
|
||||
"beef ribs swine filet ball tip pancetta strip steak sirloin"
|
||||
"mignon ham spare ribs rump. Tail shank biltong beef ribs doner"
|
||||
"buffalo swine bacon. Tongue cow picanha brisket bacon chuck"
|
||||
"leberkas pork loin pork, drumstick capicola. Doner short loin"
|
||||
"ground round fatback turducken chislic shoulder turducken"
|
||||
"spare ribs, burgdoggen kielbasa kevin frankfurter ball tip"
|
||||
"pancetta cupim. Turkey meatball andouille porchetta hamburger"
|
||||
"pork chop corned beef. Brisket short ribs turducken, pork chop"
|
||||
"chislic turkey ball pork chop leberkas rump, rump bacon, jowl"
|
||||
"tip ham. Shankle salami tongue venison short ribs kielbasa"
|
||||
"tri-tip ham hock swine hamburger. Flank meatball corned beef"
|
||||
"cow sausage ball tip kielbasa ham hock. Ball tip cupim meatloaf"
|
||||
"beef ribs rump jowl tenderloin swine sausage biltong"
|
||||
"bacon rump tail boudin meatball boudin meatball boudin."
|
||||
}
|
||||
)
|
||||
self.assertEqual(
|
||||
form.errors["anything_else"],
|
||||
["Response must be less than 1000 characters."],
|
||||
)
|
||||
|
||||
def test_anything_else_form_more_organization_information_character_count_invalid(
|
||||
self,
|
||||
):
|
||||
"""Response must be less than 1000 characters."""
|
||||
form = TypeOfWorkForm(
|
||||
data={
|
||||
"more_organization_information": "Bacon ipsum dolor amet fatback"
|
||||
"shankle, drumstick doner chicken landjaeger turkey andouille."
|
||||
"Buffalo biltong chuck pork chop tongue bresaola turkey. Doner"
|
||||
"ground round strip steak, jowl tail chuck ribeye bacon"
|
||||
"beef ribs swine filet ball tip pancetta strip steak sirloin"
|
||||
"mignon ham spare ribs rump. Tail shank biltong beef ribs doner"
|
||||
"buffalo swine bacon. Tongue cow picanha brisket bacon chuck"
|
||||
"leberkas pork loin pork, drumstick capicola. Doner short loin"
|
||||
"ground round fatback turducken chislic shoulder turducken"
|
||||
"spare ribs, burgdoggen kielbasa kevin frankfurter ball tip"
|
||||
"pancetta cupim. Turkey meatball andouille porchetta hamburger"
|
||||
"pork chop corned beef. Brisket short ribs turducken, pork chop"
|
||||
"chislic turkey ball pork chop leberkas rump, rump bacon, jowl"
|
||||
"tip ham. Shankle salami tongue venison short ribs kielbasa"
|
||||
"tri-tip ham hock swine hamburger. Flank meatball corned beef"
|
||||
"cow sausage ball tip kielbasa ham hock. Ball tip cupim meatloaf"
|
||||
"beef ribs rump jowl tenderloin swine sausage biltong"
|
||||
"bacon rump tail boudin meatball boudin meatball boudin"
|
||||
"strip steak pastrami."
|
||||
}
|
||||
)
|
||||
self.assertEqual(
|
||||
form.errors["more_organization_information"],
|
||||
["Response must be less than 1000 characters."],
|
||||
)
|
||||
|
||||
def test_anything_else_form_character_count_invalid(self):
|
||||
"""Response must be less than 1000 characters."""
|
||||
form = TypeOfWorkForm(
|
||||
data={
|
||||
"type_of_work": "Bacon ipsum dolor amet fatback strip steak pastrami"
|
||||
"shankle, drumstick doner chicken landjaeger turkey andouille."
|
||||
"Buffalo biltong chuck pork chop tongue bresaola turkey. Doner"
|
||||
"ground round strip steak, jowl tail chuck ribeye bacon"
|
||||
"beef ribs swine filet ball tip pancetta strip steak sirloin"
|
||||
"mignon ham spare ribs rump. Tail shank biltong beef ribs doner"
|
||||
"buffalo swine bacon. Tongue cow picanha brisket bacon chuck"
|
||||
"leberkas pork loin pork, drumstick capicola. Doner short loin"
|
||||
"ground round fatback turducken chislic shoulder turducken"
|
||||
"spare ribs, burgdoggen kielbasa kevin frankfurter ball tip"
|
||||
"pancetta cupim. Turkey meatball andouille porchetta hamburger"
|
||||
"pork chop corned beef. Brisket short ribs turducken, pork chop"
|
||||
"chislic turkey ball pork chop leberkas rump, rump bacon, jowl"
|
||||
"tip ham. Shankle salami tongue venison short ribs kielbasa"
|
||||
"tri-tip ham hock swine hamburger. Flank meatball corned beef"
|
||||
"cow sausage ball tip kielbasa ham hock. Ball tip cupim meatloaf"
|
||||
"beef ribs rump jowl tenderloin swine sausage biltong"
|
||||
"bacon rump tail boudin meatball boudin meatball boudin."
|
||||
}
|
||||
)
|
||||
self.assertEqual(
|
||||
form.errors["type_of_work"],
|
||||
["Response must be less than 1000 characters."],
|
||||
)
|
||||
|
||||
def test_authorizing_official_phone_invalid(self):
|
||||
"""Must be a valid phone number."""
|
||||
form = AuthorizingOfficialForm(data={"phone": "boss@boss"})
|
||||
|
|
|
@ -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."""
|
||||
|
|
|
@ -1058,6 +1058,11 @@ class TestDomainPermissions(TestWithDomainPermissions):
|
|||
)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
response = self.client.get(
|
||||
reverse("domain-nameservers", kwargs={"pk": self.domain.id})
|
||||
)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
def test_no_domain_role(self):
|
||||
"""Logged in but no role gets 403 Forbidden."""
|
||||
self.client.force_login(self.user)
|
||||
|
@ -1079,6 +1084,12 @@ class TestDomainPermissions(TestWithDomainPermissions):
|
|||
)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
with less_console_noise():
|
||||
response = self.client.get(
|
||||
reverse("domain-nameservers", kwargs={"pk": self.domain.id})
|
||||
)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
|
||||
class TestDomainDetail(TestWithDomainPermissions, WebTest):
|
||||
def setUp(self):
|
||||
|
@ -1222,6 +1233,55 @@ class TestDomainDetail(TestWithDomainPermissions, WebTest):
|
|||
home_page = self.app.get(reverse("home"))
|
||||
self.assertContains(home_page, self.domain.name)
|
||||
|
||||
def test_domain_nameservers(self):
|
||||
"""Can load domain's nameservers page."""
|
||||
page = self.client.get(
|
||||
reverse("domain-nameservers", kwargs={"pk": self.domain.id})
|
||||
)
|
||||
self.assertContains(page, "Domain name servers")
|
||||
|
||||
def test_domain_nameservers_form(self):
|
||||
"""Can change domain's nameservers.
|
||||
|
||||
Uses self.app WebTest because we need to interact with forms.
|
||||
"""
|
||||
nameservers_page = self.app.get(
|
||||
reverse("domain-nameservers", kwargs={"pk": self.domain.id})
|
||||
)
|
||||
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
||||
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||
with less_console_noise(): # swallow log warning message
|
||||
result = nameservers_page.form.submit()
|
||||
# form submission was a post, response should be a redirect
|
||||
self.assertEqual(result.status_code, 302)
|
||||
self.assertEqual(
|
||||
result["Location"],
|
||||
reverse("domain-nameservers", kwargs={"pk": self.domain.id}),
|
||||
)
|
||||
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||
page = result.follow()
|
||||
self.assertContains(page, "The name servers for this domain have been updated")
|
||||
|
||||
def test_domain_nameservers_form_invalid(self):
|
||||
"""Can change domain's nameservers.
|
||||
|
||||
Uses self.app WebTest because we need to interact with forms.
|
||||
"""
|
||||
nameservers_page = self.app.get(
|
||||
reverse("domain-nameservers", kwargs={"pk": self.domain.id})
|
||||
)
|
||||
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
||||
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||
# first two nameservers are required, so if we empty one out we should
|
||||
# get a form error
|
||||
nameservers_page.form["form-0-server"] = ""
|
||||
with less_console_noise(): # swallow logged warning message
|
||||
result = nameservers_page.form.submit()
|
||||
# form submission was a post with an error, response should be a 200
|
||||
# error text appears twice, once at the top of the page, once around
|
||||
# the field.
|
||||
self.assertContains(result, "This field is required", count=2, status_code=200)
|
||||
|
||||
|
||||
class TestApplicationStatus(TestWithUser, WebTest):
|
||||
def setUp(self):
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
from .application import *
|
||||
from .domain import (
|
||||
DomainView,
|
||||
DomainNameserversView,
|
||||
DomainUsersView,
|
||||
DomainAddUserView,
|
||||
DomainInvitationDeleteView,
|
||||
|
|
|
@ -12,7 +12,7 @@ from django.views.generic.edit import DeleteView, FormMixin
|
|||
|
||||
from registrar.models import Domain, DomainInvitation, User, UserDomainRole
|
||||
|
||||
from ..forms import DomainAddUserForm
|
||||
from ..forms import DomainAddUserForm, NameserverFormset
|
||||
from ..utility.email import send_templated_email, EmailSendingError
|
||||
from .utility import DomainPermission
|
||||
|
||||
|
@ -29,6 +29,73 @@ class DomainView(DomainPermission, DetailView):
|
|||
context_object_name = "domain"
|
||||
|
||||
|
||||
class DomainNameserversView(DomainPermission, FormMixin, DetailView):
|
||||
|
||||
"""Domain nameserver editing view."""
|
||||
|
||||
model = Domain
|
||||
template_name = "domain_nameservers.html"
|
||||
context_object_name = "domain"
|
||||
form_class = NameserverFormset
|
||||
|
||||
def get_initial(self):
|
||||
"""The initial value for the form (which is a formset here)."""
|
||||
domain = self.get_object()
|
||||
return [{"server": server} for server in domain.nameservers()]
|
||||
|
||||
def get_success_url(self):
|
||||
"""Redirect to the overview page for the domain."""
|
||||
return reverse("domain-nameservers", kwargs={"pk": self.object.pk})
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
"""Adjust context from FormMixin for formsets."""
|
||||
context = super().get_context_data(**kwargs)
|
||||
# use "formset" instead of "form" for the key
|
||||
context["formset"] = context.pop("form")
|
||||
return context
|
||||
|
||||
def get_form(self, **kwargs):
|
||||
"""Override the labels and required fields every time we get a formset."""
|
||||
formset = super().get_form(**kwargs)
|
||||
for i, form in enumerate(formset):
|
||||
form.fields["server"].label += f" {i+1}"
|
||||
if i < 2:
|
||||
form.fields["server"].required = True
|
||||
else:
|
||||
form.fields["server"].required = False
|
||||
return formset
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
"""Formset submission posts to this view."""
|
||||
self.object = self.get_object()
|
||||
formset = self.get_form()
|
||||
|
||||
if formset.is_valid():
|
||||
return self.form_valid(formset)
|
||||
else:
|
||||
return self.form_invalid(formset)
|
||||
|
||||
def form_valid(self, formset):
|
||||
"""The formset is valid, perform something with it."""
|
||||
|
||||
# Set the nameservers from the formset
|
||||
nameservers = []
|
||||
for form in formset:
|
||||
try:
|
||||
nameservers.append(form.cleaned_data["server"])
|
||||
except KeyError:
|
||||
# no server information in this field, skip it
|
||||
pass
|
||||
domain = self.get_object()
|
||||
domain.set_nameservers(nameservers)
|
||||
|
||||
messages.success(
|
||||
self.request, "The name servers for this domain have been updated"
|
||||
)
|
||||
# superclass has the redirect
|
||||
return super().form_valid(formset)
|
||||
|
||||
|
||||
class DomainUsersView(DomainPermission, DetailView):
|
||||
|
||||
"""User management page in the domain details."""
|
||||
|
|
|
@ -51,6 +51,7 @@
|
|||
10038 OUTOFSCOPE http://app:8080/(robots.txt|sitemap.xml|TODO|edit/)
|
||||
10038 OUTOFSCOPE http://app:8080/users
|
||||
10038 OUTOFSCOPE http://app:8080/users/add
|
||||
10038 OUTOFSCOPE http://app:8080/nameservers
|
||||
10038 OUTOFSCOPE http://app:8080/delete
|
||||
10038 OUTOFSCOPE http://app:8080/withdraw
|
||||
10038 OUTOFSCOPE http://app:8080/withdrawconfirmed
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue