mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-08-05 17:28:31 +02:00
Merge branch 'main' into nmb/footer
This commit is contained in:
commit
73247a6916
52 changed files with 2098 additions and 282 deletions
|
@ -78,6 +78,21 @@ To test behind logged in pages with external tools, like `pa11y-ci` or `OWASP Za
|
|||
|
||||
to MIDDLEWARE in settings.py. **Remove it when you are finished testing.**
|
||||
|
||||
### Reducing console noise in tests
|
||||
|
||||
Some tests, particularly when using Django's test client, will print errors.
|
||||
|
||||
These errors do not indicate test failure, but can make the output hard to read.
|
||||
|
||||
To silence them, we have a helper function `less_console_noise`:
|
||||
|
||||
```python
|
||||
from .common import less_console_noise
|
||||
...
|
||||
with less_console_noise():
|
||||
# <test code goes here>
|
||||
```
|
||||
|
||||
### Accessibility Scanning
|
||||
|
||||
The tool `pa11y-ci` is used to scan pages for compliance with a set of
|
||||
|
@ -123,3 +138,17 @@ In an effort to keep our domain logic centralized, we are representing the state
|
|||
objects in the application using the [django-fsm](https://github.com/viewflow/django-fsm)
|
||||
library. See the [ADR number 15](../architecture/decisions/0015-use-django-fs.md) for
|
||||
more information on the topic.
|
||||
|
||||
## Login Time Bug
|
||||
|
||||
If you are seeing errors related to openid complaining about issuing a token from the future like this:
|
||||
|
||||
```
|
||||
ERROR [djangooidc.oidc:243] Issued in the future
|
||||
```
|
||||
|
||||
it may help to resync your laptop with time.nist.gov:
|
||||
|
||||
```
|
||||
sudo sntp -sS time.nist.gov
|
||||
```
|
||||
|
|
17
src/.pa11yci
17
src/.pa11yci
|
@ -3,6 +3,21 @@
|
|||
"http://app:8080/",
|
||||
"http://app:8080/health/",
|
||||
"http://app:8080/whoami/",
|
||||
"http://app:8080/register/"
|
||||
"http://app:8080/register/",
|
||||
"http://app:8080/register/organization/",
|
||||
"http://app:8080/register/org_federal/",
|
||||
"http://app:8080/register/org_election/",
|
||||
"http://app:8080/register/org_contact/",
|
||||
"http://app:8080/register/authorizing_official/",
|
||||
"http://app:8080/register/current_sites/",
|
||||
"http://app:8080/register/dotgov_domain/",
|
||||
"http://app:8080/register/purpose/",
|
||||
"http://app:8080/register/your_contact/",
|
||||
"http://app:8080/register/other_contacts/",
|
||||
"http://app:8080/register/security_email/",
|
||||
"http://app:8080/register/anything_else/",
|
||||
"http://app:8080/register/requirements/",
|
||||
"http://app:8080/register/review/",
|
||||
"http://app:8080/register/finished/"
|
||||
]
|
||||
}
|
||||
|
|
49
src/api/tests/common.py
Normal file
49
src/api/tests/common.py
Normal file
|
@ -0,0 +1,49 @@
|
|||
import os
|
||||
import logging
|
||||
|
||||
from contextlib import contextmanager
|
||||
|
||||
|
||||
def get_handlers():
|
||||
"""Obtain pointers to all StreamHandlers."""
|
||||
handlers = {}
|
||||
|
||||
rootlogger = logging.getLogger()
|
||||
for h in rootlogger.handlers:
|
||||
if isinstance(h, logging.StreamHandler):
|
||||
handlers[h.name] = h
|
||||
|
||||
for logger in logging.Logger.manager.loggerDict.values():
|
||||
if not isinstance(logger, logging.PlaceHolder):
|
||||
for h in logger.handlers:
|
||||
if isinstance(h, logging.StreamHandler):
|
||||
handlers[h.name] = h
|
||||
|
||||
return handlers
|
||||
|
||||
|
||||
@contextmanager
|
||||
def less_console_noise():
|
||||
"""
|
||||
Context manager to use in tests to silence console logging.
|
||||
|
||||
This is helpful on tests which trigger console messages
|
||||
(such as errors) which are normal and expected.
|
||||
|
||||
It can easily be removed to debug a failing test.
|
||||
"""
|
||||
restore = {}
|
||||
handlers = get_handlers()
|
||||
devnull = open(os.devnull, "w")
|
||||
|
||||
# redirect all the streams
|
||||
for handler in handlers.values():
|
||||
prior = handler.setStream(devnull)
|
||||
restore[handler.name] = prior
|
||||
try:
|
||||
# run the test
|
||||
yield
|
||||
finally:
|
||||
# restore the streams
|
||||
for handler in handlers.values():
|
||||
handler.setStream(restore[handler.name])
|
|
@ -7,6 +7,7 @@ from django.contrib.auth import get_user_model
|
|||
from django.test import TestCase, RequestFactory
|
||||
|
||||
from ..views import available, _domains, in_domains
|
||||
from .common import less_console_noise
|
||||
|
||||
API_BASE_PATH = "/api/v1/available/"
|
||||
|
||||
|
@ -104,10 +105,12 @@ class AvailableAPITest(TestCase):
|
|||
|
||||
def test_available_post(self):
|
||||
"""Cannot post to the /available/ API endpoint."""
|
||||
response = self.client.post(API_BASE_PATH + "nonsense")
|
||||
with less_console_noise():
|
||||
response = self.client.post(API_BASE_PATH + "nonsense")
|
||||
self.assertEqual(response.status_code, 405)
|
||||
|
||||
def test_available_bad_input(self):
|
||||
self.client.force_login(self.user)
|
||||
response = self.client.get(API_BASE_PATH + "blah!;")
|
||||
with less_console_noise():
|
||||
response = self.client.get(API_BASE_PATH + "blah!;")
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
|
|
@ -11,7 +11,7 @@ import requests
|
|||
|
||||
from cachetools.func import ttl_cache
|
||||
|
||||
from registrar.models import Website
|
||||
from registrar.models import Domain
|
||||
|
||||
DOMAIN_FILE_URL = (
|
||||
"https://raw.githubusercontent.com/cisagov/dotgov-data/main/current-full.csv"
|
||||
|
@ -35,7 +35,7 @@ def _domains():
|
|||
# get the domain before the first comma
|
||||
domain = line.split(",", 1)[0]
|
||||
# sanity-check the string we got from the file here
|
||||
if Website.string_could_be_domain(domain):
|
||||
if Domain.string_could_be_domain(domain):
|
||||
# lowercase everything when we put it in domains
|
||||
domains.add(domain.lower())
|
||||
return domains
|
||||
|
@ -68,8 +68,8 @@ def available(request, domain=""):
|
|||
# validate that the given domain could be a domain name and fail early if
|
||||
# not.
|
||||
if not (
|
||||
Website.string_could_be_domain(domain)
|
||||
or Website.string_could_be_domain(domain + ".gov")
|
||||
Domain.string_could_be_domain(domain)
|
||||
or Domain.string_could_be_domain(domain + ".gov")
|
||||
):
|
||||
raise BadRequest("Invalid request.")
|
||||
# a domain is available if it is NOT in the list of current domains
|
||||
|
|
0
src/epp/__init__.py
Normal file
0
src/epp/__init__.py
Normal file
40
src/epp/mock_epp.py
Normal file
40
src/epp/mock_epp.py
Normal file
|
@ -0,0 +1,40 @@
|
|||
"""
|
||||
This file defines a number of mock functions which can be used to simulate
|
||||
communication with the registry until that integration is implemented.
|
||||
"""
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
def domain_check(_):
|
||||
"""Is domain available for registration?"""
|
||||
return True
|
||||
|
||||
|
||||
def domain_info(domain):
|
||||
"""What does the registry know about this domain?"""
|
||||
return {
|
||||
"name": domain,
|
||||
"roid": "EXAMPLE1-REP",
|
||||
"status": ["ok"],
|
||||
"registrant": "jd1234",
|
||||
"contact": {
|
||||
"admin": "sh8013",
|
||||
"tech": None,
|
||||
},
|
||||
"ns": {
|
||||
f"ns1.{domain}",
|
||||
f"ns2.{domain}",
|
||||
},
|
||||
"host": [
|
||||
f"ns1.{domain}",
|
||||
f"ns2.{domain}",
|
||||
],
|
||||
"sponsor": "ClientX",
|
||||
"creator": "ClientY",
|
||||
# TODO: think about timezones
|
||||
"creation_date": datetime.today(),
|
||||
"updator": "ClientX",
|
||||
"last_update_date": datetime.today(),
|
||||
"expiration_date": datetime.today(),
|
||||
"last_transfer_date": datetime.today(),
|
||||
}
|
|
@ -4,7 +4,7 @@ from django.contrib.contenttypes.models import ContentType
|
|||
from django.http.response import HttpResponseRedirect
|
||||
from django.urls import reverse
|
||||
|
||||
from .models import User, UserProfile, DomainApplication, Website
|
||||
from . import models
|
||||
|
||||
|
||||
class AuditedAdmin(admin.ModelAdmin):
|
||||
|
@ -26,7 +26,7 @@ class UserProfileInline(admin.StackedInline):
|
|||
|
||||
"""Edit a user's profile on the user page."""
|
||||
|
||||
model = UserProfile
|
||||
model = models.UserProfile
|
||||
|
||||
|
||||
class MyUserAdmin(UserAdmin):
|
||||
|
@ -36,6 +36,24 @@ class MyUserAdmin(UserAdmin):
|
|||
inlines = [UserProfileInline]
|
||||
|
||||
|
||||
admin.site.register(User, MyUserAdmin)
|
||||
admin.site.register(DomainApplication, AuditedAdmin)
|
||||
admin.site.register(Website, AuditedAdmin)
|
||||
class HostIPInline(admin.StackedInline):
|
||||
|
||||
"""Edit an ip address on the host page."""
|
||||
|
||||
model = models.HostIP
|
||||
|
||||
|
||||
class MyHostAdmin(AuditedAdmin):
|
||||
|
||||
"""Custom host admin class to use our inlines."""
|
||||
|
||||
inlines = [HostIPInline]
|
||||
|
||||
|
||||
admin.site.register(models.User, MyUserAdmin)
|
||||
admin.site.register(models.Contact, AuditedAdmin)
|
||||
admin.site.register(models.DomainApplication, AuditedAdmin)
|
||||
admin.site.register(models.Domain, AuditedAdmin)
|
||||
admin.site.register(models.Host, MyHostAdmin)
|
||||
admin.site.register(models.Nameserver, MyHostAdmin)
|
||||
admin.site.register(models.Website, AuditedAdmin)
|
||||
|
|
|
@ -10,12 +10,14 @@ from django.urls import include, path
|
|||
from django.views.generic import RedirectView
|
||||
|
||||
from registrar.views import health, index, profile, whoami
|
||||
from registrar.forms import ApplicationWizard
|
||||
from registrar.forms import ApplicationWizard, WIZARD_CONDITIONS
|
||||
from api.views import available
|
||||
|
||||
APPLICATION_URL_NAME = "application_step"
|
||||
application_wizard = ApplicationWizard.as_view(
|
||||
url_name=APPLICATION_URL_NAME, done_step_name="finished"
|
||||
url_name=APPLICATION_URL_NAME,
|
||||
done_step_name="finished",
|
||||
condition_dict=WIZARD_CONDITIONS,
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from .edit_profile import EditProfileForm
|
||||
from .application_wizard import ApplicationWizard
|
||||
from .application_wizard import ApplicationWizard, WIZARD_CONDITIONS
|
||||
|
||||
__all__ = ["EditProfileForm", "ApplicationWizard"]
|
||||
__all__ = ["EditProfileForm", "ApplicationWizard", "WIZARD_CONDITIONS"]
|
||||
|
|
|
@ -1,21 +1,30 @@
|
|||
"""Forms Wizard for creating a new domain application."""
|
||||
|
||||
from __future__ import annotations # allows forward references in annotations
|
||||
|
||||
import logging
|
||||
|
||||
from django import forms
|
||||
from django.shortcuts import redirect
|
||||
from django.shortcuts import render
|
||||
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
|
||||
from formtools.wizard.views import NamedUrlSessionWizardView # type: ignore
|
||||
|
||||
from registrar.models import DomainApplication, Website
|
||||
from registrar.models import DomainApplication, Domain
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class OrganizationForm(forms.Form):
|
||||
# Subclass used to remove the default colon suffix from all fields
|
||||
class RegistrarForm(forms.Form):
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs.setdefault("label_suffix", "")
|
||||
super(RegistrarForm, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class OrganizationTypeForm(RegistrarForm):
|
||||
organization_type = forms.ChoiceField(
|
||||
required=True,
|
||||
choices=[
|
||||
|
@ -45,49 +54,244 @@ class OrganizationForm(forms.Form):
|
|||
],
|
||||
widget=forms.RadioSelect,
|
||||
)
|
||||
|
||||
|
||||
class OrganizationFederalForm(RegistrarForm):
|
||||
federal_type = forms.ChoiceField(
|
||||
required=False,
|
||||
choices=[
|
||||
("Executive", "Executive"),
|
||||
("Judicial", "Judicial"),
|
||||
("Legislative", "Legislative"),
|
||||
],
|
||||
choices=DomainApplication.BRANCH_CHOICES,
|
||||
widget=forms.RadioSelect,
|
||||
)
|
||||
is_election_board = forms.ChoiceField(
|
||||
|
||||
|
||||
class OrganizationElectionForm(RegistrarForm):
|
||||
is_election_board = forms.BooleanField(
|
||||
widget=forms.RadioSelect(
|
||||
choices=[
|
||||
(True, "Yes"),
|
||||
(False, "No"),
|
||||
],
|
||||
),
|
||||
required=False,
|
||||
choices=[
|
||||
("Yes", "Yes"),
|
||||
("No", "No"),
|
||||
],
|
||||
widget=forms.RadioSelect,
|
||||
)
|
||||
|
||||
|
||||
class ContactForm(forms.Form):
|
||||
class OrganizationContactForm(RegistrarForm):
|
||||
organization_name = forms.CharField(label="Organization Name")
|
||||
street_address = forms.CharField(label="Street address")
|
||||
address_line1 = forms.CharField(label="Address line 1")
|
||||
address_line2 = forms.CharField(
|
||||
required=False,
|
||||
label="Address line 2",
|
||||
)
|
||||
us_state = forms.ChoiceField(
|
||||
label="State",
|
||||
choices=[
|
||||
("AL", "Alabama"),
|
||||
("AK", "Alaska"),
|
||||
("AZ", "Arizona"),
|
||||
("AR", "Arkansas"),
|
||||
("CA", "California"),
|
||||
("CO", "Colorado"),
|
||||
("CT", "Connecticut"),
|
||||
("DE", "Delaware"),
|
||||
("DC", "District of Columbia"),
|
||||
("FL", "Florida"),
|
||||
("GA", "Georgia"),
|
||||
("HI", "Hawaii"),
|
||||
("ID", "Idaho"),
|
||||
("IL", "Illinois"),
|
||||
("IN", "Indiana"),
|
||||
("IA", "Iowa"),
|
||||
("KS", "Kansas"),
|
||||
("KY", "Kentucky"),
|
||||
("LA", "Louisiana"),
|
||||
("ME", "Maine"),
|
||||
("MD", "Maryland"),
|
||||
("MA", "Massachusetts"),
|
||||
("MI", "Michigan"),
|
||||
("MN", "Minnesota"),
|
||||
("MS", "Mississippi"),
|
||||
("MO", "Missouri"),
|
||||
("MT", "Montana"),
|
||||
("NE", "Nebraska"),
|
||||
("NV", "Nevada"),
|
||||
("NH", "New Hampshire"),
|
||||
("NJ", "New Jersey"),
|
||||
("NM", "New Mexico"),
|
||||
("NY", "New York"),
|
||||
("NC", "North Carolina"),
|
||||
("ND", "North Dakota"),
|
||||
("OH", "Ohio"),
|
||||
("OK", "Oklahoma"),
|
||||
("OR", "Oregon"),
|
||||
("PA", "Pennsylvania"),
|
||||
("RI", "Rhode Island"),
|
||||
("SC", "South Carolina"),
|
||||
("SD", "South Dakota"),
|
||||
("TN", "Tennessee"),
|
||||
("TX", "Texas"),
|
||||
("UT", "Utah"),
|
||||
("VT", "Vermont"),
|
||||
("VA", "Virginia"),
|
||||
("WA", "Washington"),
|
||||
("WV", "West Virginia"),
|
||||
("WI", "Wisconsin"),
|
||||
("WY", "Wyoming"),
|
||||
("AS", "American Samoa"),
|
||||
("GU", "Guam"),
|
||||
("MP", "Northern Mariana Islands"),
|
||||
("PR", "Puerto Rico"),
|
||||
("VI", "Virgin Islands"),
|
||||
],
|
||||
)
|
||||
zipcode = forms.CharField(label="ZIP code")
|
||||
|
||||
|
||||
class AuthorizingOfficialForm(RegistrarForm):
|
||||
first_name = forms.CharField(label="First name/given name")
|
||||
middle_name = forms.CharField(
|
||||
required=False,
|
||||
label="Middle name (optional)",
|
||||
)
|
||||
last_name = forms.CharField(label="Last name/family name")
|
||||
title = forms.CharField(label="Title or role in your organization")
|
||||
email = forms.EmailField(label="Email")
|
||||
phone = forms.CharField(label="Phone")
|
||||
|
||||
|
||||
class CurrentSitesForm(RegistrarForm):
|
||||
current_site = forms.CharField(
|
||||
required=False,
|
||||
label="Enter your organization’s public website, if you have one. For example, "
|
||||
"www.city.com.",
|
||||
)
|
||||
|
||||
|
||||
class DotGovDomainForm(RegistrarForm):
|
||||
dotgov_domain = forms.CharField(label="What .gov domain do you want?")
|
||||
alternative_domain = forms.CharField(
|
||||
required=False,
|
||||
label="Are there other domains you’d like if we can’t give you your first "
|
||||
"choice? Entering alternative domains is optional.",
|
||||
)
|
||||
|
||||
|
||||
class PurposeForm(RegistrarForm):
|
||||
purpose_field = forms.CharField(label="Purpose", widget=forms.Textarea())
|
||||
|
||||
|
||||
class YourContactForm(RegistrarForm):
|
||||
first_name = forms.CharField(label="First name/given name")
|
||||
middle_name = forms.CharField(
|
||||
required=False,
|
||||
label="Middle name (optional)",
|
||||
)
|
||||
last_name = forms.CharField(label="Last name/family name")
|
||||
title = forms.CharField(label="Title or role in your organization")
|
||||
email = forms.EmailField(label="Email")
|
||||
phone = forms.CharField(label="Phone")
|
||||
|
||||
|
||||
class OtherContactsForm(RegistrarForm):
|
||||
first_name = forms.CharField(label="First name/given name")
|
||||
middle_name = forms.CharField(
|
||||
required=False,
|
||||
label="Middle name (optional)",
|
||||
)
|
||||
last_name = forms.CharField(label="Last name/family name")
|
||||
title = forms.CharField(label="Title or role in your organization")
|
||||
email = forms.EmailField(label="Email")
|
||||
phone = forms.CharField(label="Phone")
|
||||
|
||||
|
||||
class SecurityEmailForm(RegistrarForm):
|
||||
email = forms.EmailField(
|
||||
required=False,
|
||||
label="Security email",
|
||||
)
|
||||
|
||||
|
||||
class AnythingElseForm(RegistrarForm):
|
||||
anything_else = forms.CharField(
|
||||
required=False, label="Anything else we should know", widget=forms.Textarea()
|
||||
)
|
||||
|
||||
|
||||
class RequirementsForm(RegistrarForm):
|
||||
agree_check = forms.BooleanField(
|
||||
label="I read and agree to the .gov domain requirements."
|
||||
)
|
||||
|
||||
|
||||
# Empty class for the review page which gets included as part of the form, but does not
|
||||
# have any form fields itself
|
||||
class ReviewForm(RegistrarForm):
|
||||
pass
|
||||
|
||||
|
||||
# List of forms in our wizard. Each entry is a tuple of a name and a form
|
||||
# subclass
|
||||
FORMS = [
|
||||
("organization", OrganizationForm),
|
||||
("contact", ContactForm),
|
||||
("organization_type", OrganizationTypeForm),
|
||||
("organization_federal", OrganizationFederalForm),
|
||||
("organization_election", OrganizationElectionForm),
|
||||
("organization_contact", OrganizationContactForm),
|
||||
("authorizing_official", AuthorizingOfficialForm),
|
||||
("current_sites", CurrentSitesForm),
|
||||
("dotgov_domain", DotGovDomainForm),
|
||||
("purpose", PurposeForm),
|
||||
("your_contact", YourContactForm),
|
||||
("other_contacts", OtherContactsForm),
|
||||
("security_email", SecurityEmailForm),
|
||||
("anything_else", AnythingElseForm),
|
||||
("requirements", RequirementsForm),
|
||||
("review", ReviewForm),
|
||||
]
|
||||
|
||||
# Dict to match up the right template with the right step. Keys here must
|
||||
# match the first elements of the tuples in FORMS
|
||||
TEMPLATES = {
|
||||
"organization": "application_organization.html",
|
||||
"contact": "application_contact.html",
|
||||
"organization_type": "application_org_type.html",
|
||||
"organization_federal": "application_org_federal.html",
|
||||
"organization_election": "application_org_election.html",
|
||||
"organization_contact": "application_org_contact.html",
|
||||
"authorizing_official": "application_authorizing_official.html",
|
||||
"current_sites": "application_current_sites.html",
|
||||
"dotgov_domain": "application_dotgov_domain.html",
|
||||
"purpose": "application_purpose.html",
|
||||
"your_contact": "application_your_contact.html",
|
||||
"other_contacts": "application_other_contacts.html",
|
||||
"security_email": "application_security_email.html",
|
||||
"anything_else": "application_anything_else.html",
|
||||
"requirements": "application_requirements.html",
|
||||
"review": "application_review.html",
|
||||
}
|
||||
|
||||
# We need to pass our page titles as context to the templates, indexed
|
||||
# by the step names
|
||||
TITLES = {
|
||||
"organization": "About your organization",
|
||||
"contact": "Your organization's contact information",
|
||||
"organization_type": "Type of organization",
|
||||
"organization_federal": "Type of organization — Federal",
|
||||
"organization_election": "Type of organization — Election board",
|
||||
"organization_contact": "Organization name and mailing address",
|
||||
"authorizing_official": "Authorizing official",
|
||||
"current_sites": "Organization website",
|
||||
"dotgov_domain": ".gov domain",
|
||||
"purpose": "Purpose of your domain",
|
||||
"your_contact": "Your contact information",
|
||||
"other_contacts": "Other contacts for your domain",
|
||||
"security_email": "Security email for public use",
|
||||
"anything_else": "Anything else we should know?",
|
||||
"requirements": "Requirements for registration and operation of .gov domains",
|
||||
"review": "Review and submit your domain request",
|
||||
}
|
||||
|
||||
|
||||
# We can use a dictionary with step names and callables that return booleans
|
||||
# to show or hide particular steps based on the state of the process.
|
||||
WIZARD_CONDITIONS = {
|
||||
"organization_federal": DomainApplication.show_organization_federal,
|
||||
"organization_election": DomainApplication.show_organization_election,
|
||||
}
|
||||
|
||||
|
||||
|
@ -120,22 +324,32 @@ class ApplicationWizard(LoginRequiredMixin, NamedUrlSessionWizardView):
|
|||
"""Unpack the form responses onto the model object properties."""
|
||||
application = DomainApplication.objects.create(creator=self.request.user)
|
||||
|
||||
# organization information
|
||||
organization_data = form_dict["organization"].cleaned_data
|
||||
application.organization_type = organization_data["organization_type"]
|
||||
application.federal_branch = organization_data["federal_type"]
|
||||
application.is_election_office = organization_data["is_election_board"]
|
||||
# organization type information
|
||||
organization_type_data = form_dict["organization_type"].cleaned_data
|
||||
application.organization_type = organization_type_data["organization_type"]
|
||||
|
||||
# federal branch information may not exist
|
||||
federal_branch_data = form_dict.get("organization_federal")
|
||||
if federal_branch_data is not None:
|
||||
federal_branch_data = federal_branch_data.cleaned_data
|
||||
application.federal_branch = federal_branch_data["federal_type"]
|
||||
|
||||
# election board information may not exist.
|
||||
election_board_data = form_dict.get("organization_election")
|
||||
if election_board_data is not None:
|
||||
election_board_data = election_board_data.cleaned_data
|
||||
application.is_election_office = election_board_data["is_election_board"]
|
||||
|
||||
# contact information
|
||||
contact_data = form_dict["contact"].cleaned_data
|
||||
contact_data = form_dict["organization_contact"].cleaned_data
|
||||
application.organization_name = contact_data["organization_name"]
|
||||
application.street_address = contact_data["street_address"]
|
||||
application.street_address = contact_data["address_line1"]
|
||||
# TODO: add the rest of these fields when they are created in the forms
|
||||
|
||||
# This isn't really the requested_domain field
|
||||
# but we need something in this field to make the form submittable
|
||||
requested_site, _ = Website.objects.get_or_create(
|
||||
website=contact_data["organization_name"] + ".gov"
|
||||
requested_site, _ = Domain.objects.get_or_create(
|
||||
name=contact_data["organization_name"] + ".gov"
|
||||
)
|
||||
application.requested_domain = requested_site
|
||||
return application
|
||||
|
@ -145,4 +359,6 @@ class ApplicationWizard(LoginRequiredMixin, NamedUrlSessionWizardView):
|
|||
application.submit() # change the status to submitted
|
||||
application.save()
|
||||
logger.debug("Application object saved: %s", application.id)
|
||||
return redirect("home")
|
||||
return render(
|
||||
self.request, "application_done.html", {"application_id": application.id}
|
||||
)
|
||||
|
|
|
@ -0,0 +1,165 @@
|
|||
# Generated by Django 4.1.3 on 2022-11-28 19:07
|
||||
|
||||
from django.conf import settings
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import django_fsm # type: ignore
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("registrar", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="Domain",
|
||||
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)),
|
||||
(
|
||||
"name",
|
||||
models.CharField(
|
||||
default=None,
|
||||
help_text="Fully qualified domain name",
|
||||
max_length=253,
|
||||
),
|
||||
),
|
||||
(
|
||||
"is_active",
|
||||
django_fsm.FSMField(
|
||||
choices=[(True, "Yes"), (False, "No")],
|
||||
default=False,
|
||||
help_text="Domain is live in the registry",
|
||||
max_length=50,
|
||||
),
|
||||
),
|
||||
("owners", models.ManyToManyField(to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Host",
|
||||
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)),
|
||||
(
|
||||
"name",
|
||||
models.CharField(
|
||||
default=None,
|
||||
help_text="Fully qualified domain name",
|
||||
max_length=253,
|
||||
unique=True,
|
||||
),
|
||||
),
|
||||
(
|
||||
"domain",
|
||||
models.ForeignKey(
|
||||
help_text="Domain to which this host belongs",
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
related_name="host",
|
||||
to="registrar.domain",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"abstract": False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Nameserver",
|
||||
fields=[
|
||||
(
|
||||
"host_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="registrar.host",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"abstract": False,
|
||||
},
|
||||
bases=("registrar.host",),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="HostIP",
|
||||
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)),
|
||||
(
|
||||
"address",
|
||||
models.CharField(
|
||||
default=None,
|
||||
help_text="IP address",
|
||||
max_length=46,
|
||||
validators=[django.core.validators.validate_ipv46_address],
|
||||
),
|
||||
),
|
||||
(
|
||||
"host",
|
||||
models.ForeignKey(
|
||||
help_text="Host to which this IP address belongs",
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
related_name="ip",
|
||||
to="registrar.host",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"abstract": False,
|
||||
},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="domainapplication",
|
||||
name="requested_domain",
|
||||
field=models.OneToOneField(
|
||||
blank=True,
|
||||
help_text="The requested domain",
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
related_name="domain_application",
|
||||
to="registrar.domain",
|
||||
),
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name="domain",
|
||||
constraint=models.UniqueConstraint(
|
||||
condition=models.Q(("is_active", True)),
|
||||
fields=("name",),
|
||||
name="unique_domain_name_in_registry",
|
||||
),
|
||||
),
|
||||
]
|
|
@ -2,6 +2,10 @@ from auditlog.registry import auditlog # type: ignore
|
|||
|
||||
from .contact import Contact
|
||||
from .domain_application import DomainApplication
|
||||
from .domain import Domain
|
||||
from .host_ip import HostIP
|
||||
from .host import Host
|
||||
from .nameserver import Nameserver
|
||||
from .user_profile import UserProfile
|
||||
from .user import User
|
||||
from .website import Website
|
||||
|
@ -9,6 +13,10 @@ from .website import Website
|
|||
__all__ = [
|
||||
"Contact",
|
||||
"DomainApplication",
|
||||
"Domain",
|
||||
"HostIP",
|
||||
"Host",
|
||||
"Nameserver",
|
||||
"UserProfile",
|
||||
"User",
|
||||
"Website",
|
||||
|
@ -16,6 +24,10 @@ __all__ = [
|
|||
|
||||
auditlog.register(Contact)
|
||||
auditlog.register(DomainApplication)
|
||||
auditlog.register(Domain)
|
||||
auditlog.register(HostIP)
|
||||
auditlog.register(Host)
|
||||
auditlog.register(Nameserver)
|
||||
auditlog.register(UserProfile)
|
||||
auditlog.register(User)
|
||||
auditlog.register(Website)
|
||||
|
|
237
src/registrar/models/domain.py
Normal file
237
src/registrar/models/domain.py
Normal file
|
@ -0,0 +1,237 @@
|
|||
import logging
|
||||
import re
|
||||
|
||||
from django.db import models
|
||||
from django_fsm import FSMField, transition # type: ignore
|
||||
|
||||
from epp.mock_epp import domain_info, domain_check
|
||||
|
||||
from .utility.time_stamped_model import TimeStampedModel
|
||||
from .domain_application import DomainApplication
|
||||
from .user import User
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Domain(TimeStampedModel):
|
||||
"""
|
||||
Manage the lifecycle of domain names.
|
||||
|
||||
The registry is the source of truth for this data and this model exists:
|
||||
1. To tie ownership information in the registrar to
|
||||
DNS entries in the registry; and
|
||||
2. To allow a new registrant to draft DNS entries before their
|
||||
application is approved
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
constraints = [
|
||||
# draft domains may share the same name, but
|
||||
# once approved, they must be globally unique
|
||||
models.UniqueConstraint(
|
||||
fields=["name"],
|
||||
condition=models.Q(is_active=True),
|
||||
name="unique_domain_name_in_registry",
|
||||
),
|
||||
]
|
||||
|
||||
class Status(models.TextChoices):
|
||||
"""
|
||||
The status codes we can receive from the registry.
|
||||
|
||||
These are detailed in RFC 5731 in section 2.3.
|
||||
https://www.rfc-editor.org/std/std69.txt
|
||||
"""
|
||||
|
||||
# Requests to delete the object MUST be rejected.
|
||||
CLIENT_DELETE_PROHIBITED = "clientDeleteProhibited"
|
||||
SERVER_DELETE_PROHIBITED = "serverDeleteProhibited"
|
||||
|
||||
# DNS delegation information MUST NOT be published for the object.
|
||||
CLIENT_HOLD = "clientHold"
|
||||
SERVER_HOLD = "serverHold"
|
||||
|
||||
# Requests to renew the object MUST be rejected.
|
||||
CLIENT_RENEW_PROHIBITED = "clientRenewProhibited"
|
||||
SERVER_RENEW_PROHIBITED = "serverRenewProhibited"
|
||||
|
||||
# Requests to transfer the object MUST be rejected.
|
||||
CLIENT_TRANSFER_PROHIBITED = "clientTransferProhibited"
|
||||
SERVER_TRANSFER_PROHIBITED = "serverTransferProhibited"
|
||||
|
||||
# Requests to update the object (other than to remove this status)
|
||||
# MUST be rejected.
|
||||
CLIENT_UPDATE_PROHIBITED = "clientUpdateProhibited"
|
||||
SERVER_UPDATE_PROHIBITED = "serverUpdateProhibited"
|
||||
|
||||
# Delegation information has not been associated with the object.
|
||||
# This is the default status when a domain object is first created
|
||||
# and there are no associated host objects for the DNS delegation.
|
||||
# This status can also be set by the server when all host-object
|
||||
# associations are removed.
|
||||
INACTIVE = "inactive"
|
||||
|
||||
# This is the normal status value for an object that has no pending
|
||||
# operations or prohibitions. This value is set and removed by the
|
||||
# server as other status values are added or removed.
|
||||
OK = "ok"
|
||||
|
||||
# A transform command has been processed for the object, but the
|
||||
# action has not been completed by the server. Server operators can
|
||||
# delay action completion for a variety of reasons, such as to allow
|
||||
# for human review or third-party action. A transform command that
|
||||
# is processed, but whose requested action is pending, is noted with
|
||||
# response code 1001.
|
||||
PENDING_CREATE = "pendingCreate"
|
||||
PENDING_DELETE = "pendingDelete"
|
||||
PENDING_RENEW = "pendingRenew"
|
||||
PENDING_TRANSFER = "pendingTransfer"
|
||||
PENDING_UPDATE = "pendingUpdate"
|
||||
|
||||
# a domain name is alphanumeric or hyphen, up to 63 characters, doesn't
|
||||
# begin or end with a hyphen, followed by a TLD of 2-6 alphabetic characters
|
||||
DOMAIN_REGEX = re.compile(r"^(?!-)[A-Za-z0-9-]{1,63}(?<!-)\.[A-Za-z]{2,6}")
|
||||
|
||||
@classmethod
|
||||
def string_could_be_domain(cls, domain: str) -> bool:
|
||||
"""Return True if the string could be a domain name, otherwise False."""
|
||||
if cls.DOMAIN_REGEX.match(domain):
|
||||
return True
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def available(cls, domain: str) -> bool:
|
||||
"""Check if a domain is available.
|
||||
|
||||
Not implemented. Returns a dummy value for testing."""
|
||||
return domain_check(domain)
|
||||
|
||||
def transfer(self):
|
||||
"""Going somewhere. Not implemented."""
|
||||
pass
|
||||
|
||||
def renew(self):
|
||||
"""Time to renew. Not implemented."""
|
||||
pass
|
||||
|
||||
def _get_property(self, property):
|
||||
"""Get some info about a domain."""
|
||||
if not self.is_active:
|
||||
return None
|
||||
if not hasattr(self, "info"):
|
||||
try:
|
||||
# get info from registry
|
||||
self.info = domain_info(self.name)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
# TODO: back off error handling
|
||||
return None
|
||||
if hasattr(self, "info"):
|
||||
if property in self.info:
|
||||
return self.info[property]
|
||||
else:
|
||||
raise KeyError(
|
||||
"Requested key %s was not found in registry data." % str(property)
|
||||
)
|
||||
else:
|
||||
# TODO: return an error if registry cannot be contacted
|
||||
return None
|
||||
|
||||
def could_be_domain(self) -> bool:
|
||||
"""Could this instance be a domain?"""
|
||||
# short-circuit if self.website is null/None
|
||||
if not self.name:
|
||||
return False
|
||||
return self.string_could_be_domain(str(self.name))
|
||||
|
||||
@transition(field="is_active", source="*", target=True)
|
||||
def activate(self):
|
||||
"""This domain should be made live."""
|
||||
if hasattr(self, "domain_application"):
|
||||
if self.domain_application.status != DomainApplication.APPROVED:
|
||||
raise ValueError("Cannot activate. Application must be approved.")
|
||||
if Domain.objects.filter(name=self.name, is_active=True).exists():
|
||||
raise ValueError("Cannot activate. Domain name is already in use.")
|
||||
# TODO: depending on the details of our registry integration
|
||||
# we will either contact the registry and deploy the domain
|
||||
# in this function OR we will verify that it has already been
|
||||
# activated and reject this state transition if it has not
|
||||
pass
|
||||
|
||||
@transition(field="is_active", source="*", target=False)
|
||||
def deactivate(self):
|
||||
"""This domain should not be live."""
|
||||
# there are security concerns to having this function exist
|
||||
# within the codebase; discuss these with the project lead
|
||||
# if there is a feature request to implement this
|
||||
raise Exception("Cannot revoke, contact registry.")
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.name
|
||||
|
||||
@property
|
||||
def roid(self):
|
||||
return self._get_property("roid")
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
return self._get_property("status")
|
||||
|
||||
@property
|
||||
def registrant(self):
|
||||
return self._get_property("registrant")
|
||||
|
||||
@property
|
||||
def sponsor(self):
|
||||
return self._get_property("sponsor")
|
||||
|
||||
@property
|
||||
def creator(self):
|
||||
return self._get_property("creator")
|
||||
|
||||
@property
|
||||
def creation_date(self):
|
||||
return self._get_property("creation_date")
|
||||
|
||||
@property
|
||||
def updator(self):
|
||||
return self._get_property("updator")
|
||||
|
||||
@property
|
||||
def last_update_date(self):
|
||||
return self._get_property("last_update_date")
|
||||
|
||||
@property
|
||||
def expiration_date(self):
|
||||
return self._get_property("expiration_date")
|
||||
|
||||
@property
|
||||
def last_transfer_date(self):
|
||||
return self._get_property("last_transfer_date")
|
||||
|
||||
name = models.CharField(
|
||||
max_length=253,
|
||||
blank=False,
|
||||
default=None, # prevent saving without a value
|
||||
help_text="Fully qualified domain name",
|
||||
)
|
||||
|
||||
# we use `is_active` rather than `domain_application.status`
|
||||
# because domains may exist without associated applications
|
||||
is_active = FSMField(
|
||||
choices=[
|
||||
(True, "Yes"),
|
||||
(False, "No"),
|
||||
],
|
||||
default=False,
|
||||
# TODO: how to edit models in Django admin if protected = True
|
||||
protected=False,
|
||||
help_text="Domain is live in the registry",
|
||||
)
|
||||
|
||||
# TODO: determine the relationship between this field
|
||||
# and the domain application's `creator` and `submitter`
|
||||
owners = models.ManyToManyField(
|
||||
User,
|
||||
help_text="",
|
||||
)
|
|
@ -1,3 +1,5 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from django.db import models
|
||||
from django_fsm import FSMField, transition # type: ignore
|
||||
|
||||
|
@ -6,6 +8,11 @@ from .contact import Contact
|
|||
from .user import User
|
||||
from .website import Website
|
||||
|
||||
from typing import TYPE_CHECKING, Union
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..forms.application_wizard import ApplicationWizard
|
||||
|
||||
|
||||
class DomainApplication(TimeStampedModel):
|
||||
|
||||
|
@ -147,12 +154,12 @@ class DomainApplication(TimeStampedModel):
|
|||
related_name="current+",
|
||||
)
|
||||
|
||||
requested_domain = models.ForeignKey(
|
||||
Website,
|
||||
requested_domain = models.OneToOneField(
|
||||
"Domain",
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="The requested domain",
|
||||
related_name="requested+",
|
||||
related_name="domain_application",
|
||||
on_delete=models.PROTECT,
|
||||
)
|
||||
alternative_domains = models.ManyToManyField(
|
||||
|
@ -204,8 +211,8 @@ class DomainApplication(TimeStampedModel):
|
|||
|
||||
def __str__(self):
|
||||
try:
|
||||
if self.requested_domain and self.requested_domain.website:
|
||||
return self.requested_domain.website
|
||||
if self.requested_domain and self.requested_domain.name:
|
||||
return self.requested_domain.name
|
||||
else:
|
||||
return f"{self.status} application created by {self.creator}"
|
||||
except Exception:
|
||||
|
@ -225,3 +232,36 @@ class DomainApplication(TimeStampedModel):
|
|||
# if no exception was raised, then we don't need to do anything
|
||||
# inside this method, keep the `pass` here to remind us of that
|
||||
pass
|
||||
|
||||
# ## Form policies ###
|
||||
#
|
||||
# These methods control what questions need to be answered by applicants
|
||||
# during the application flow. They are policies about the application so
|
||||
# they appear here.
|
||||
|
||||
@staticmethod
|
||||
def _get_organization_type(wizard: ApplicationWizard) -> Union[str, None]:
|
||||
"""Extract the answer to the organization type question from the wizard."""
|
||||
# using the step data from the storage is a workaround for this
|
||||
# bug in django-formtools version 2.4
|
||||
# https://github.com/jazzband/django-formtools/issues/220
|
||||
type_data = wizard.storage.get_step_data("organization_type")
|
||||
if type_data:
|
||||
return type_data.get("organization_type-organization_type")
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def show_organization_federal(wizard: ApplicationWizard) -> bool:
|
||||
"""Show this step if the answer to the first question was "federal"."""
|
||||
return DomainApplication._get_organization_type(wizard) == "Federal"
|
||||
|
||||
@staticmethod
|
||||
def show_organization_election(wizard: ApplicationWizard) -> bool:
|
||||
"""Show this step if the answer to the first question implies it.
|
||||
|
||||
This shows for answers that aren't "Federal" or "Interstate".
|
||||
"""
|
||||
type_answer = DomainApplication._get_organization_type(wizard)
|
||||
if type_answer and type_answer not in ("Federal", "Interstate"):
|
||||
return True
|
||||
return False
|
||||
|
|
33
src/registrar/models/host.py
Normal file
33
src/registrar/models/host.py
Normal file
|
@ -0,0 +1,33 @@
|
|||
from django.db import models
|
||||
|
||||
from .utility.time_stamped_model import TimeStampedModel
|
||||
from .domain import Domain
|
||||
|
||||
|
||||
class Host(TimeStampedModel):
|
||||
"""
|
||||
Hosts are internet-connected computers.
|
||||
|
||||
They may handle email, serve websites, or perform other tasks.
|
||||
|
||||
The registry is the source of truth for this data.
|
||||
|
||||
This model exists ONLY to allow a new registrant to draft DNS entries
|
||||
before their application is approved.
|
||||
"""
|
||||
|
||||
name = models.CharField(
|
||||
max_length=253,
|
||||
null=False,
|
||||
blank=False,
|
||||
default=None, # prevent saving without a value
|
||||
unique=True,
|
||||
help_text="Fully qualified domain name",
|
||||
)
|
||||
|
||||
domain = models.ForeignKey(
|
||||
Domain,
|
||||
on_delete=models.PROTECT,
|
||||
related_name="host", # access this Host via the Domain as `domain.host`
|
||||
help_text="Domain to which this host belongs",
|
||||
)
|
32
src/registrar/models/host_ip.py
Normal file
32
src/registrar/models/host_ip.py
Normal file
|
@ -0,0 +1,32 @@
|
|||
from django.db import models
|
||||
from django.core.validators import validate_ipv46_address
|
||||
|
||||
from .utility.time_stamped_model import TimeStampedModel
|
||||
from .host import Host
|
||||
|
||||
|
||||
class HostIP(TimeStampedModel):
|
||||
"""
|
||||
Hosts may have one or more IP addresses.
|
||||
|
||||
The registry is the source of truth for this data.
|
||||
|
||||
This model exists ONLY to allow a new registrant to draft DNS entries
|
||||
before their application is approved.
|
||||
"""
|
||||
|
||||
address = models.CharField(
|
||||
max_length=46,
|
||||
null=False,
|
||||
blank=False,
|
||||
default=None, # prevent saving without a value
|
||||
validators=[validate_ipv46_address],
|
||||
help_text="IP address",
|
||||
)
|
||||
|
||||
host = models.ForeignKey(
|
||||
Host,
|
||||
on_delete=models.PROTECT,
|
||||
related_name="ip", # access this HostIP via the Host as `host.ip`
|
||||
help_text="Host to which this IP address belongs",
|
||||
)
|
16
src/registrar/models/nameserver.py
Normal file
16
src/registrar/models/nameserver.py
Normal file
|
@ -0,0 +1,16 @@
|
|||
from .host import Host
|
||||
|
||||
|
||||
class Nameserver(Host):
|
||||
"""
|
||||
A nameserver is a host which has been delegated to respond to DNS queries.
|
||||
|
||||
The registry is the source of truth for this data.
|
||||
|
||||
This model exists ONLY to allow a new registrant to draft DNS entries
|
||||
before their application is approved.
|
||||
"""
|
||||
|
||||
# there is nothing here because all of the fields are
|
||||
# defined over there on the Host class
|
||||
pass
|
|
@ -1,5 +1,3 @@
|
|||
import re
|
||||
|
||||
from django.db import models
|
||||
|
||||
|
||||
|
@ -16,26 +14,5 @@ class Website(models.Model):
|
|||
help_text="",
|
||||
)
|
||||
|
||||
# a domain name is alphanumeric or hyphen, up to 63 characters, doesn't
|
||||
# begin or end with a hyphen, followed by a TLD of 2-6 alphabetic characters
|
||||
DOMAIN_REGEX = re.compile(r"^(?!-)[A-Za-z0-9-]{1,63}(?<!-)\.[A-Za-z]{2,6}")
|
||||
|
||||
@classmethod
|
||||
def string_could_be_domain(cls, domain: str) -> bool:
|
||||
"""Return True if the string could be a domain name, otherwise False.
|
||||
|
||||
TODO: when we have a Domain class, this could be a classmethod there.
|
||||
"""
|
||||
if cls.DOMAIN_REGEX.match(domain):
|
||||
return True
|
||||
return False
|
||||
|
||||
def could_be_domain(self) -> bool:
|
||||
"""Could this instance be a domain?"""
|
||||
# short-circuit if self.website is null/None
|
||||
if not self.website:
|
||||
return False
|
||||
return self.string_could_be_domain(str(self.website))
|
||||
|
||||
def __str__(self) -> str:
|
||||
return str(self.website)
|
||||
|
|
|
@ -4,24 +4,25 @@
|
|||
{% block title %}{% translate "Unauthorized" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{% translate "Unauthorized" %}</h1>
|
||||
<main id="main-content" class="grid-container">
|
||||
<h1>{% translate "Unauthorized" %}</h1>
|
||||
|
||||
{% if friendly_message %}
|
||||
<p>{{ friendly_message }}</p>
|
||||
{% else %}
|
||||
<p>{% translate "Authorization failed." %}</p>
|
||||
{% endif %}
|
||||
{% if friendly_message %}
|
||||
<p>{{ friendly_message }}</p>
|
||||
{% else %}
|
||||
<p>{% translate "Authorization failed." %}</p>
|
||||
{% endif %}
|
||||
|
||||
<p><a href="{% url 'login' %}">
|
||||
<p><a href="{% url 'login' %}">
|
||||
{% translate "Would you like to try logging in again?" %}
|
||||
</a></p>
|
||||
</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 %}
|
||||
{% 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
|
||||
|
||||
{% endblock %}
|
||||
TODO: Content team to create a "how to contact us" footer for the error pages
|
||||
</main>
|
||||
{% endblock %}
|
||||
|
|
|
@ -5,9 +5,11 @@
|
|||
{% block title %}{% translate "Page not found" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<main id="main-content" class="grid-container">
|
||||
|
||||
<h2>{% translate "Page not found" %}</h2>
|
||||
<h1>{% translate "Page not found" %}</h1>
|
||||
|
||||
<p>{% translate "The requested page could not be found." %}</p>
|
||||
<p>{% translate "The requested page could not be found." %}</p>
|
||||
|
||||
{% endblock %}
|
||||
</main>
|
||||
{% endblock %}
|
||||
|
|
|
@ -4,20 +4,21 @@
|
|||
{% block title %}{% translate "Server error" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{% translate "Server Error" %}</h1>
|
||||
<main id="main-content" class="grid-container">
|
||||
<h1>{% translate "Server Error" %}</h1>
|
||||
|
||||
{% if friendly_message %}
|
||||
<p>{{ friendly_message }}</p>
|
||||
{% else %}
|
||||
<p>{% translate "An internal server error occurred." %}</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 %}
|
||||
{% 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
|
||||
|
||||
{% endblock %}
|
||||
TODO: Content team to create a "how to contact us" footer for the error pages
|
||||
</main>
|
||||
{% endblock %}
|
||||
|
|
25
src/registrar/templates/application_anything_else.html
Normal file
25
src/registrar/templates/application_anything_else.html
Normal file
|
@ -0,0 +1,25 @@
|
|||
<!-- Test page -->
|
||||
{% extends 'application_form.html' %}
|
||||
{% load widget_tweaks %}
|
||||
|
||||
{% block form_content %}
|
||||
|
||||
<p id="instructions">Is there anything else we should know about your domain request?</p>
|
||||
|
||||
<form id="step__{{wizard.steps.current}}" class="usa-form usa-form--large" method="post">
|
||||
<div class="usa-form-group">
|
||||
{{ wizard.management_form }}
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="usa-character-count">
|
||||
{{ wizard.form.anything_else|add_label_class:"usa-label usa-sr-only" }}
|
||||
{{ wizard.form.anything_else|add_class:"usa-textarea usa-character-count__field"|attr:"aria-describedby:instructions"|attr:"maxlength=500" }}
|
||||
<span class="usa-character-count__message" id="with-hint-textarea-info with-hint-textarea-hint"> You can enter up to 500 characters </span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ block.super }}
|
||||
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
|
@ -0,0 +1,55 @@
|
|||
<!-- Test page -->
|
||||
{% extends 'application_form.html' %}
|
||||
{% load widget_tweaks %}
|
||||
{% load static %}
|
||||
|
||||
{% block form_content %}
|
||||
|
||||
<h2>Who is the authorizing official for your organization</h2>
|
||||
|
||||
<div id="instructions">
|
||||
<p>Your authorizing official is the person within your organization who can authorize your domain request. This is generally the highest ranking or highest elected official in your organization. Read more about <a href="#">who can serve as an authorizing official</a>.
|
||||
</p>
|
||||
|
||||
<div class="ao-example">
|
||||
{% include "includes/ao_example__city.html" %}
|
||||
</div>
|
||||
|
||||
<p>We’ll contact your authorizing official to let them know that you made this request and to double check that they approve it.</p>
|
||||
</div>
|
||||
|
||||
<p>All fields are required unless they are marked optional.</p>
|
||||
|
||||
<form class="usa-form usa-form--large" id="step__{{wizard.steps.current}}" method="post">
|
||||
{{ wizard.management_form }}
|
||||
{% csrf_token %}
|
||||
|
||||
<fieldset class="usa-fieldset">
|
||||
<legend class="usa-sr-only">
|
||||
Who is the authorizing official for your organization
|
||||
</legend>
|
||||
{{ wizard.form.first_name|add_label_class:"usa-label" }}
|
||||
{{ wizard.form.first_name|add_class:"usa-input"}}
|
||||
|
||||
{{ wizard.form.middle_name|add_label_class:"usa-label" }}
|
||||
{{ wizard.form.middle_name|add_class:"usa-input"}}
|
||||
|
||||
{{ wizard.form.last_name|add_label_class:"usa-label" }}
|
||||
{{ wizard.form.last_name|add_class:"usa-input"}}
|
||||
|
||||
{{ wizard.form.title|add_label_class:"usa-label" }}
|
||||
{{ wizard.form.title|add_class:"usa-input"}}
|
||||
|
||||
{{ wizard.form.email|add_label_class:"usa-label" }}
|
||||
{{ wizard.form.email|add_class:"usa-input"}}
|
||||
|
||||
{{ wizard.form.phone|add_label_class:"usa-label" }}
|
||||
{{ wizard.form.phone|add_class:"usa-input usa-input--medium" }}
|
||||
</fieldset>
|
||||
|
||||
|
||||
{{ block.super }}
|
||||
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
|
@ -1,35 +0,0 @@
|
|||
<!-- Test page -->
|
||||
{% extends 'application_form.html' %}
|
||||
{% load widget_tweaks %}
|
||||
|
||||
{% block title %}Apply for a .gov domain - Your organization's contact information{% endblock %}
|
||||
|
||||
{% block form_content %}
|
||||
<h1>Your organization's contact information</h1>
|
||||
|
||||
<h2>What is the name and mailing address of your organization?</h2>
|
||||
|
||||
<p id="instructions">Enter the name of the organization your represent. Your organization might be part
|
||||
of a larger entity. If so, enter information about your part of the larger entity.</p>
|
||||
|
||||
<p>All fields are required unless they are marked optional.</p>
|
||||
|
||||
<form class="usa-form usa-form--large" method="post">
|
||||
{{ wizard.management_form }}
|
||||
{% csrf_token %}
|
||||
|
||||
{{ wizard.form.organization_name|add_label_class:"usa-label" }}
|
||||
{{ wizard.form.organization_name|add_class:"usa-input"|attr:"aria-describedby:instructions" }}
|
||||
|
||||
<fieldset class="usa-fieldset">
|
||||
{{ wizard.form.street_address|add_label_class:"usa-label" }}
|
||||
{{ wizard.form.street_address|add_class:"usa-input"|attr:"validate:domain" }}
|
||||
</fieldset>
|
||||
|
||||
{% if wizard.steps.prev %}
|
||||
<button name="wizard_goto_step" type="submit" class="usa-button usa-button--base" value="{{ wizard.steps.prev }}">Previous</button>
|
||||
{% endif %}
|
||||
<button type="submit" class="usa-button">Submit</button>
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
19
src/registrar/templates/application_current_sites.html
Normal file
19
src/registrar/templates/application_current_sites.html
Normal file
|
@ -0,0 +1,19 @@
|
|||
<!-- Test page -->
|
||||
{% extends 'application_form.html' %}
|
||||
{% load widget_tweaks %}
|
||||
{% load static %}
|
||||
|
||||
{% block form_content %}
|
||||
|
||||
<form class="usa-form usa-form--large" id="step__{{wizard.steps.current}}" method="post">
|
||||
{{ wizard.management_form }}
|
||||
{% csrf_token %}
|
||||
|
||||
{{ wizard.form.current_site|add_label_class:"usa-label" }}
|
||||
{{ wizard.form.current_site|add_class:"usa-input" }}
|
||||
|
||||
{{ block.super }}
|
||||
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
40
src/registrar/templates/application_done.html
Normal file
40
src/registrar/templates/application_done.html
Normal file
|
@ -0,0 +1,40 @@
|
|||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}Thank you for your domain request{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Thank you!</h1>
|
||||
|
||||
<p>
|
||||
Thank you for your domain request. We'll email a copy of your request to you,
|
||||
your authorizing official, and any contacts you added.</p>
|
||||
|
||||
<h2>Next steps in this process</h2>
|
||||
|
||||
<ul>
|
||||
|
||||
<li> We'll review your request. This could take up to two weeks. During
|
||||
this review we'll verify that you're eligible for a .gov domain, that
|
||||
your authorizing official approves your request, and that your domain
|
||||
meets our naming requirements.
|
||||
</li>
|
||||
|
||||
<li> You can <a href="/application/{{ application_id }}"><del>check the
|
||||
status</del></a> of your request at any time.
|
||||
</li>
|
||||
|
||||
<li> We'll email you with any questions or when we complete our
|
||||
review.</li>
|
||||
</ul>
|
||||
|
||||
<h2>Option to enter domain name server information</h2>
|
||||
|
||||
<p>Before your domain can be used we'll need information about your
|
||||
domain name servers. If you have this information you can enter it now.
|
||||
If you don't have it, that's okay. You can enter it later on the
|
||||
<a href="/domains"><del>manage your domains page</del></a>.
|
||||
</p>
|
||||
|
||||
<p><a href="/application/{{ application_id }}#nameservers" class="usa-button">Enter DNS name servers</a></p>
|
||||
|
||||
{% endblock %}
|
61
src/registrar/templates/application_dotgov_domain.html
Normal file
61
src/registrar/templates/application_dotgov_domain.html
Normal file
|
@ -0,0 +1,61 @@
|
|||
<!-- Test page -->
|
||||
{% extends 'application_form.html' %}
|
||||
{% load widget_tweaks static%}
|
||||
|
||||
{% block form_content %}
|
||||
<p> Before requesting a .gov domain, <a href="#">please make sure it meets our naming requirements.</a> Your domain name must:
|
||||
<ul class="usa-list">
|
||||
<li>Be available </li>
|
||||
<li>Be unique </li>
|
||||
<li>Relate to your organization’s name, location, and/or services </li>
|
||||
<li>Be clear to the general public. Your domain name must not be easily confused with other organizations.</li>
|
||||
</ul>
|
||||
</p>
|
||||
|
||||
<p>Note that <strong> only federal agencies can request generic terms </strong>like vote.gov.</p>
|
||||
|
||||
<p>We’ll try to give you the domain you want. We first need to make sure your request meets our requirements. We’ll work with you to find the best domain for your organization.</p>
|
||||
|
||||
<p>Here are a few domain examples for your type of organization.</p>
|
||||
<div class="domain-example">
|
||||
{% include "includes/domain_example__city.html" %}
|
||||
</div>
|
||||
|
||||
<form id="step__{{wizard.steps.current}}" class="usa-form usa-form--large" method="post">
|
||||
<h2> What .gov domain do you want? </h2>
|
||||
<p class="domain_instructions"> After you enter your domain, we’ll make sure it’s available and that it meets some of our naming requirements. If your domain passes these initial checks, we’ll verify that it meets all of our requirements once you complete and submit the rest of the domain request form. </p>
|
||||
|
||||
{{ wizard.management_form }}
|
||||
{% csrf_token %}
|
||||
{{ wizard.form.dotgov_domain|add_label_class:"usa-label" }}
|
||||
<div class="display-flex flex-align-center">
|
||||
<span class="padding-top-05 padding-right-2px">www.</span>
|
||||
{{ wizard.form.dotgov_domain|add_class:"usa-input"|attr:"aria-describedby:domain_instructions" }}
|
||||
<span class="padding-top-05 padding-left-2px">.gov </span>
|
||||
</div>
|
||||
<button type="button" class="usa-button">Check availability </button>
|
||||
|
||||
<h2>Alternative domains</h2>
|
||||
|
||||
<div>
|
||||
{{ wizard.form.alternative_domain|add_label_class:"usa-label" }}
|
||||
<div class="display-flex flex-align-center">
|
||||
<span class="padding-top-05 padding-right-2px">www.</span>
|
||||
{{ wizard.form.alternative_domain|add_class:"usa-input" }}
|
||||
<span class="padding-top-05 padding-left-2px">.gov </span>
|
||||
</div>
|
||||
|
||||
<button type="button" class="usa-button usa-button--unstyled">
|
||||
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img">
|
||||
<use xlink:href="{%static 'img/sprite.svg'%}#add_circle"></use>
|
||||
</svg><span class="margin-left-05">Add another alternative</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p>If you’re not sure this is the domain you want, that’s okay. You can change it later.</p>
|
||||
|
||||
{{ block.super }}
|
||||
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
|
@ -1,13 +1,33 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load static widget_tweaks %}
|
||||
|
||||
{% block title %}Apply for a .gov domain – {{form_titles|get_item:wizard.steps.current}}{% endblock %}
|
||||
{% block content %}
|
||||
<div class="grid-row">
|
||||
<div class="grid-col-3">
|
||||
{% include 'application_sidebar.html' %}
|
||||
</div>
|
||||
|
||||
<div class="grid-col-9">
|
||||
{% block form_content %}{% endblock %}
|
||||
<div class="grid-container">
|
||||
<div class="grid-row grid-gap">
|
||||
<div class="grid-col-3">
|
||||
{% include 'application_sidebar.html' %}
|
||||
</div>
|
||||
<div class="grid-col-9">
|
||||
<main id="main-content" class="grid-container">
|
||||
{% if wizard.steps.prev %}
|
||||
<a href="{% url wizard.url_name step=wizard.steps.prev %}">
|
||||
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img">
|
||||
<use xlink:href="{%static 'img/sprite.svg'%}#arrow_back"></use>
|
||||
</svg><span class="margin-left-05">Previous step </span>
|
||||
</a>
|
||||
{% endif %}
|
||||
<h1> {{form_titles|get_item:wizard.steps.current}} </h1>
|
||||
{% block form_content %}
|
||||
{% if wizard.steps.next %}
|
||||
<button type="submit" class="usa-button">Next</button>
|
||||
{% else %}
|
||||
<button type="submit" class="usa-button">Submit your domain request</button>
|
||||
{% endif %}
|
||||
<button type="button" class="usa-button usa-button--outline">Save</button>
|
||||
</main>
|
||||
</div>
|
||||
{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
44
src/registrar/templates/application_org_contact.html
Normal file
44
src/registrar/templates/application_org_contact.html
Normal file
|
@ -0,0 +1,44 @@
|
|||
<!-- Test page -->
|
||||
{% extends 'application_form.html' %}
|
||||
{% load widget_tweaks %}
|
||||
|
||||
{% block form_content %}
|
||||
|
||||
|
||||
<h2>What is the name and mailing address of your organization?</h2>
|
||||
|
||||
<div id="instructions">
|
||||
<p>Enter the name of the organization your represent. Your organization might be part
|
||||
of a larger entity. If so, enter information about your part of the larger entity.</p>
|
||||
|
||||
<p>Once your domain is approved, the name of your organization will be publicly listed as the domain registrant. </p>
|
||||
|
||||
<p>All fields are required unless they are marked optional.</p>
|
||||
</div>
|
||||
|
||||
<form id="step__{{wizard.steps.current}}" class="usa-form usa-form--large" method="post">
|
||||
{{ wizard.management_form }}
|
||||
{% csrf_token %}
|
||||
|
||||
<fieldset class="usa-fieldset">
|
||||
<legend class="usa-sr-only">What is the name and mailing address of your organization?</legend>
|
||||
{{ wizard.form.organization_name|add_label_class:"usa-label" }}
|
||||
{{ wizard.form.organization_name|add_class:"usa-input" }}
|
||||
{{ wizard.form.address_line1|add_label_class:"usa-label" }}
|
||||
{{ wizard.form.address_line1|add_class:"usa-input" }}
|
||||
{{ wizard.form.address_line2|add_label_class:"usa-label" }}
|
||||
{{ wizard.form.address_line2|add_class:"usa-input" }}
|
||||
{{ wizard.form.us_state|add_label_class:"usa-label" }}
|
||||
<div class="usa-combo-box">
|
||||
{{ wizard.form.us_state|add_class:"usa-select" }}
|
||||
</div>
|
||||
{{ wizard.form.zipcode|add_label_class:"usa-label" }}
|
||||
{{ wizard.form.zipcode|add_class:"usa-input usa-input--small" }}
|
||||
|
||||
</fieldset>
|
||||
|
||||
{{ block.super }}
|
||||
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
23
src/registrar/templates/application_org_election.html
Normal file
23
src/registrar/templates/application_org_election.html
Normal file
|
@ -0,0 +1,23 @@
|
|||
<!-- Test page -->
|
||||
{% extends 'application_form.html' %}
|
||||
{% load widget_tweaks %}
|
||||
{% load dynamic_question_tags %}
|
||||
|
||||
{% block form_content %}
|
||||
|
||||
<form id="step__{{wizard.steps.current}}" class="usa-form usa-form--large" method="post">
|
||||
{{ wizard.management_form }}
|
||||
{% csrf_token %}
|
||||
<fieldset id="election_board__fieldset" class="usa-fieldset">
|
||||
<legend>
|
||||
<h2>Is your organization an election office?</h2>
|
||||
</legend>
|
||||
{% radio_buttons_by_value wizard.form.is_election_board as choices %}
|
||||
{% include "includes/radio_button.html" with choice=choices|get_item:True %}
|
||||
{% include "includes/radio_button.html" with choice=choices|get_item:False %}
|
||||
</fieldset>
|
||||
|
||||
{{ block.super }}
|
||||
|
||||
</form>
|
||||
{% endblock %}
|
24
src/registrar/templates/application_org_federal.html
Normal file
24
src/registrar/templates/application_org_federal.html
Normal file
|
@ -0,0 +1,24 @@
|
|||
<!-- Test page -->
|
||||
{% extends 'application_form.html' %}
|
||||
{% load widget_tweaks %}
|
||||
{% load dynamic_question_tags %}
|
||||
|
||||
{% block form_content %}
|
||||
|
||||
<form id="step__{{wizard.steps.current}}" class="usa-form usa-form--large" method="post">
|
||||
{{ wizard.management_form }}
|
||||
{% csrf_token %}
|
||||
<fieldset id="federal_type__fieldset" class="usa-fieldset">
|
||||
<legend>
|
||||
<h2>Which federal branch is your organization in?</h2>
|
||||
</legend>
|
||||
{% radio_buttons_by_value wizard.form.federal_type as federal_choices %}
|
||||
{% include "includes/radio_button.html" with choice=federal_choices.Executive%}
|
||||
{% include "includes/radio_button.html" with choice=federal_choices.Judicial%}
|
||||
{% include "includes/radio_button.html" with choice=federal_choices.Legislative%}
|
||||
</fieldset>
|
||||
|
||||
{{ block.super }}
|
||||
|
||||
</form>
|
||||
{% endblock %}
|
31
src/registrar/templates/application_org_type.html
Normal file
31
src/registrar/templates/application_org_type.html
Normal file
|
@ -0,0 +1,31 @@
|
|||
<!-- Test page -->
|
||||
{% extends 'application_form.html' %}
|
||||
{% load widget_tweaks %}
|
||||
{% load dynamic_question_tags %}
|
||||
|
||||
{% block form_content %}
|
||||
|
||||
<form id="step__{{wizard.steps.current}}" class="usa-form usa-form--large" method="post">
|
||||
{{ wizard.management_form }}
|
||||
{% csrf_token %}
|
||||
|
||||
{% radio_buttons_by_value wizard.form.organization_type as choices %}
|
||||
|
||||
<fieldset id="organization_type__fieldset" class="usa-fieldset">
|
||||
<legend class="usa-legend">
|
||||
<h2> What kind of government organization do you represent?</h2>
|
||||
</legend>
|
||||
{{ wizard.form.organization_type.errors }}
|
||||
{% include "includes/radio_button.html" with choice=choices.Federal tile="true" %}
|
||||
{% include "includes/radio_button.html" with choice=choices.Interstate tile="true" %}
|
||||
{% include "includes/radio_button.html" with choice=choices.State_or_Territory tile="true" %}
|
||||
{% include "includes/radio_button.html" with choice=choices.Tribal tile="true" %}
|
||||
{% include "includes/radio_button.html" with choice=choices.County tile="true" %}
|
||||
{% include "includes/radio_button.html" with choice=choices.City tile="true" %}
|
||||
{% include "includes/radio_button.html" with choice=choices.Special_District tile="true" %}
|
||||
</fieldset>
|
||||
|
||||
{{ block.super }}
|
||||
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -1,54 +0,0 @@
|
|||
<!-- Test page -->
|
||||
{% extends 'application_form.html' %}
|
||||
{% load widget_tweaks %}
|
||||
{% load dynamic_question_tags %}
|
||||
|
||||
{% block title %}Apply for a .gov domain - About your organization{% endblock %}
|
||||
|
||||
{% block form_content %}
|
||||
<h1>About your organization</h1>
|
||||
|
||||
<form class="usa-form usa-form--large" method="post">
|
||||
{{ wizard.management_form }}
|
||||
{% csrf_token %}
|
||||
|
||||
{% radio_buttons_by_value wizard.form.organization_type as choices %}
|
||||
|
||||
<fieldset id="organization_type__fieldset" class="usa-fieldset">
|
||||
<legend>
|
||||
<h2> What kind of government organization do you represent?</h2>
|
||||
</legend>
|
||||
{{ wizard.form.organization_type.errors }}
|
||||
{% include "includes/radio_button.html" with choice=choices.Federal %}
|
||||
{% include "includes/radio_button.html" with choice=choices.Interstate %}
|
||||
{% include "includes/radio_button.html" with choice=choices.State_or_Territory %}
|
||||
{% include "includes/radio_button.html" with choice=choices.Tribal %}
|
||||
{% include "includes/radio_button.html" with choice=choices.County %}
|
||||
{% include "includes/radio_button.html" with choice=choices.City %}
|
||||
{% include "includes/radio_button.html" with choice=choices.Special_District %}
|
||||
</fieldset>
|
||||
|
||||
<fieldset id="federal_type__fieldset" {% trigger wizard.form.federal_type choices.Federal %} class="usa-fieldset">
|
||||
<legend>
|
||||
<h2> Which federal branch does your organization belong to?</h2>
|
||||
</legend>
|
||||
<noscript>
|
||||
Only required if you selected "Federal".
|
||||
</noscript>
|
||||
{{ wizard.form.federal_type|add_class:"usa-radio" }}
|
||||
</fieldset>
|
||||
|
||||
<fieldset id="is_election_board__fieldset" {% trigger wizard.form.is_election_board choices.Tribal choices.County choices.City choices.Special_District%} class="usa-fieldset">
|
||||
<legend>
|
||||
<h2> Is your organization an election office?</h2>
|
||||
</legend>
|
||||
<noscript>
|
||||
Only required if you selected "Tribal", "County", "City" or "Special District".
|
||||
</noscript>
|
||||
{{ wizard.form.is_election_board|add_class:"usa-radio" }}
|
||||
</fieldset>
|
||||
|
||||
<button type="submit" class="usa-button">Next</button>
|
||||
|
||||
</form>
|
||||
{% endblock %}
|
50
src/registrar/templates/application_other_contacts.html
Normal file
50
src/registrar/templates/application_other_contacts.html
Normal file
|
@ -0,0 +1,50 @@
|
|||
<!-- Test page -->
|
||||
{% extends 'application_form.html' %}
|
||||
{% load widget_tweaks %}
|
||||
{% load static %}
|
||||
|
||||
{% block form_content %}
|
||||
|
||||
<p id="instructions">We strongly encourage you to have at least two points of contact for your domain. Many organizations have an administrative point of contact and a technical point of contact. We recommend that you add at least one more contact.</p>
|
||||
<p>All fields are required unless they are marked optional.</p>
|
||||
|
||||
<form class="usa-form usa-form--large" id="step__{{wizard.steps.current}}" method="post">
|
||||
{{ wizard.management_form }}
|
||||
{% csrf_token %}
|
||||
|
||||
<fieldset class="usa-fieldset">
|
||||
<legend>
|
||||
<h2> Contact 2 </h2>
|
||||
</legend>
|
||||
{{ wizard.form.first_name|add_label_class:"usa-label" }}
|
||||
{{ wizard.form.first_name|add_class:"usa-input"|attr:"aria-describedby:instructions" }}
|
||||
|
||||
{{ wizard.form.middle_name|add_label_class:"usa-label" }}
|
||||
{{ wizard.form.middle_name|add_class:"usa-input"|attr:"aria-describedby:instructions" }}
|
||||
|
||||
{{ wizard.form.last_name|add_label_class:"usa-label" }}
|
||||
{{ wizard.form.last_name|add_class:"usa-input"|attr:"aria-describedby:instructions" }}
|
||||
|
||||
{{ wizard.form.title|add_label_class:"usa-label" }}
|
||||
{{ wizard.form.title|add_class:"usa-input"|attr:"aria-describedby:instructions" }}
|
||||
|
||||
{{ wizard.form.email|add_label_class:"usa-label" }}
|
||||
{{ wizard.form.email|add_class:"usa-input"|attr:"aria-describedby:instructions" }}
|
||||
|
||||
{{ wizard.form.phone|add_label_class:"usa-label" }}
|
||||
{{ wizard.form.phone|add_class:"usa-input usa-input--medium"|attr:"aria-describedby:instructions" }}
|
||||
</fieldset>
|
||||
|
||||
<div>
|
||||
<button type="button" class="usa-button usa-button--unstyled">
|
||||
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img">
|
||||
<use xlink:href="{%static '/img/sprite.svg'%}#add_circle"></use>
|
||||
</svg><span class="margin-left-05">Add another contact</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{{ block.super }}
|
||||
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
25
src/registrar/templates/application_purpose.html
Normal file
25
src/registrar/templates/application_purpose.html
Normal file
|
@ -0,0 +1,25 @@
|
|||
<!-- Test page -->
|
||||
{% extends 'application_form.html' %}
|
||||
{% load widget_tweaks %}
|
||||
|
||||
{% block form_content %}
|
||||
|
||||
<p id="instructions">Describe your organization’s mission or the reason for your domain request. Explain how you plan to use this domain. Will you use it for a website and/or email? Are you moving your website from another top-level domain (like .com or .org)? Read about <a href="#">activities that are prohibited on .gov domains.</a></p>
|
||||
|
||||
<form id="step__{{wizard.steps.current}}" class="usa-form usa-form--large" method="post">
|
||||
<div class="usa-form-group">
|
||||
{{ wizard.management_form }}
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="usa-character-count">
|
||||
{{ wizard.form.purpose_field|add_label_class:"usa-label usa-sr-only" }}
|
||||
{{ wizard.form.purpose_field|add_class:"usa-textarea usa-character-count__field"|attr:"aria-describedby:instructions"|attr:"maxlength=500" }}
|
||||
<span class="usa-character-count__message" id="with-hint-textarea-info with-hint-textarea-hint"> You can enter up to 500 characters </span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ block.super }}
|
||||
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
72
src/registrar/templates/application_requirements.html
Normal file
72
src/registrar/templates/application_requirements.html
Normal file
|
@ -0,0 +1,72 @@
|
|||
<!-- Test page -->
|
||||
{% extends 'application_form.html' %}
|
||||
{% load widget_tweaks %}
|
||||
|
||||
{% block form_content %}
|
||||
|
||||
<p>The .gov domain exists to support a broad diversity of government missions and public initiatives. Generally, the .gov registry does not review or audit how government organizations use their domains.</p>
|
||||
|
||||
<p>However, misuse of an individual .gov domain can reflect upon the integrity of the entire .gov space. There are categories of misuse that are statutorily prohibited or abusive in nature.</p>
|
||||
|
||||
<h2>Prohibited activities for .gov domains</h2>
|
||||
|
||||
<h3>Commercial purposes </h3>
|
||||
<p>A .gov domain must not be used for commercial purposes, such as advertising benefitting private individuals or entities.</p>
|
||||
|
||||
<h3>Political campaigns</h3>
|
||||
<p>A .gov domain must not be used for political campaigns.</p>
|
||||
|
||||
<h3>Illegal content</h3>
|
||||
<p>A .gov domain must not be used to distribute or promote material whose distribution violates applicable law.</p>
|
||||
|
||||
<h3>Malicious cyber activity </h3>
|
||||
<p>.gov is a trusted and safe space. .gov domains must not distribute malware, host open redirects, or otherwise engage in malicious cyber activity.</p>
|
||||
|
||||
|
||||
<h2>Required activities for .gov domain registrants </h2>
|
||||
|
||||
<h3>Keep your contact information update</h3>
|
||||
<p>As a .gov domain registrant, maintain current and accurate contact information in the .gov registrar. We strongly recommend that you create and use a security contact.</p>
|
||||
|
||||
<h3>Be responsive if we contact you</h3>
|
||||
<p>Registrants should respond in a timely manner to communications about required and prohibited activities.</p>
|
||||
|
||||
|
||||
<h2>Domains can be suspended or terminated for violations</h2>
|
||||
<p>The .gov program may need to suspend or terminate a domain registration for violations. Registrants should respond in a timely manner to communications about prohibited activities.</p>
|
||||
<p>When we discover a violation, we will make reasonable efforts to contact a registrant, including:
|
||||
<ul class="usa-list">
|
||||
<li>Emails to domain contacts </li>
|
||||
<li>Phone calls to domain contacts</li>
|
||||
<li>Email or phone call to the authorizing official</li>
|
||||
<li>Email or phone call to the government organization, a parent organization, or affiliated entities</li>
|
||||
</ul>
|
||||
</p>
|
||||
|
||||
<p>We understand the critical importance of the availability of .gov domains. Suspending or terminating a .gov domain is reserved only for prolonged, unresolved serious violations where the registrant is non-responsive. We will make extensive efforts to contact registrants and to identify potential solutions, and will make reasonable accommodations for remediation timelines proportional to the severity of the issue.</p>
|
||||
|
||||
|
||||
<h2>HSTS preloading</h2>
|
||||
<p>The .gov program will preload all newly registered .gov domains for HTTP Strict Transport Security (HSTS).</p>
|
||||
<p>HSTS is a simple and widely-supported standard that protects visitors by ensuring that their browsers always connect to a website over HTTPS. HSTS removes the need to redirect users from http:// to https:// URLs. (This redirection is a security risk that HSTS eliminates.)</p>
|
||||
<p>HSTS preloading impacts web traffic only. Once a domain is on the HSTS preload list, modern web browsers will enforce HTTPS connections for all websites hosted on the .gov domain. Users will not be able to click through warnings to reach a site. Non-web uses of .gov (email, VPN, APIs, etc.) are not affected.</p>
|
||||
|
||||
|
||||
<h2>Acknowledgement of .gov domain requirements</h2>
|
||||
|
||||
<form id="step__{{wizard.steps.current}}" class="usa-form usa-form--large" method="post">
|
||||
<div class="usa-form-group">
|
||||
{{ wizard.management_form }}
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="usa-checkbox">
|
||||
{{ wizard.form.agree_check|add_class:"usa-checkbox__input"}}
|
||||
{{ wizard.form.agree_check|add_label_class:"usa-checkbox__label" }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ block.super }}
|
||||
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
27
src/registrar/templates/application_review.html
Normal file
27
src/registrar/templates/application_review.html
Normal file
|
@ -0,0 +1,27 @@
|
|||
<!-- Test page -->
|
||||
{% extends 'application_form.html' %}
|
||||
{% load static widget_tweaks %}
|
||||
|
||||
{% block form_content %}
|
||||
|
||||
<form id="step__{{wizard.steps.current}}" class="usa-form usa-form--large" method="post">
|
||||
{{ wizard.management_form }}
|
||||
{% csrf_token %}
|
||||
|
||||
{% for this_step in wizard.steps.all|slice:":-1" %}
|
||||
<div class="review__step margin-top-2">
|
||||
<div class="review__step__title display-flex flex-justify">
|
||||
<span class="review__step__name">{{ form_titles|get_item:this_step }}</span>
|
||||
<a href="{% url wizard.url_name step=this_step %}">Edit </a>
|
||||
</div>
|
||||
<div class="review__step__value">
|
||||
<Answer value>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
{{ block.super }}
|
||||
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
21
src/registrar/templates/application_security_email.html
Normal file
21
src/registrar/templates/application_security_email.html
Normal file
|
@ -0,0 +1,21 @@
|
|||
<!-- Test page -->
|
||||
{% extends 'application_form.html' %}
|
||||
{% load widget_tweaks %}
|
||||
{% load static %}
|
||||
|
||||
{% block form_content %}
|
||||
|
||||
<p id="instructions"> We strongly recommend that you provide a security email. This email will allow the public to report observed or suspected security issues on your domain. <strong> Security emails are made public.</strong> We recommend using an alias, like security@<domain.gov>.</p>
|
||||
|
||||
<form class="usa-form usa-form--large" id="step__{{wizard.steps.current}}" method="post">
|
||||
{{ wizard.management_form }}
|
||||
{% csrf_token %}
|
||||
|
||||
{{ wizard.form.email|add_label_class:"usa-label" }}
|
||||
{{ wizard.form.email|add_class:"usa-input"|attr:"aria-describedby:instructions" }}
|
||||
|
||||
{{ block.super }}
|
||||
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
|
@ -1,16 +1,21 @@
|
|||
<div class="tablet:grid-col-4 margin-bottom-4 tablet:margin-bottom-0">
|
||||
{% load static %}
|
||||
<div class="margin-bottom-4 tablet:margin-bottom-0">
|
||||
<nav aria-label="Form steps,">
|
||||
<ul class="usa-sidenav">
|
||||
{% for this_step in wizard.steps.all %}
|
||||
{% if forloop.counter <= wizard.steps.step1 %}
|
||||
<li class="usa-sidenav__item">
|
||||
{% if forloop.counter <= wizard.steps.step1 %}
|
||||
<a href="{% url wizard.url_name step=this_step %}"
|
||||
{% if this_step == wizard.steps.current %}class="usa-current"{% endif%}>
|
||||
<a href="{% url wizard.url_name step=this_step %}"
|
||||
{% if this_step == wizard.steps.current %}class="usa-current"{% endif%}>
|
||||
{{ form_titles|get_item:this_step }}
|
||||
</a>
|
||||
{% else %}
|
||||
{{ form_titles|get_item:this_step }}
|
||||
{% endif %}
|
||||
</a>
|
||||
{% else %}
|
||||
<li class="usa-sidenav__item step--locked">
|
||||
{{ form_titles|get_item:this_step }}
|
||||
<svg class="usa-icon" aria-labelledby="locked-step" role="img">
|
||||
<title id="locked-step__{{forloop.counter}}">locked until previous steps have been completed </title>
|
||||
<use xlink:href="{%static 'img/sprite.svg'%}#lock"></use>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
|
50
src/registrar/templates/application_your_contact.html
Normal file
50
src/registrar/templates/application_your_contact.html
Normal file
|
@ -0,0 +1,50 @@
|
|||
<!-- Test page -->
|
||||
{% extends 'application_form.html' %}
|
||||
{% load widget_tweaks %}
|
||||
{% load static %}
|
||||
|
||||
{% block form_content %}
|
||||
|
||||
<div id="instructions">
|
||||
<p> We’ll use the following information to contact you about your domain request and, once your request is approved, about managing your domain.</p>
|
||||
|
||||
<p>If you’d like us to use a different name, email, or phone number you can make those changes below. Changing your contact information here won’t affect your login.gov account information.</p>
|
||||
|
||||
<p>The contact information you provide here won’t be public and will only be used for the .gov registry.</p>
|
||||
<div>
|
||||
|
||||
<p>All fields are required unless they are marked optional.</p>
|
||||
|
||||
<form class="usa-form usa-form--large" id="step__{{wizard.steps.current}}" method="post">
|
||||
{{ wizard.management_form }}
|
||||
{% csrf_token %}
|
||||
|
||||
<fieldset class="usa-fieldset">
|
||||
<legend class="usa-sr-only">
|
||||
Your contact information
|
||||
</legend>
|
||||
{{ wizard.form.first_name|add_label_class:"usa-label" }}
|
||||
{{ wizard.form.first_name|add_class:"usa-input"|attr:"aria-describedby:instructions" }}
|
||||
|
||||
{{ wizard.form.middle_name|add_label_class:"usa-label" }}
|
||||
{{ wizard.form.middle_name|add_class:"usa-input"|attr:"aria-describedby:instructions" }}
|
||||
|
||||
{{ wizard.form.last_name|add_label_class:"usa-label" }}
|
||||
{{ wizard.form.last_name|add_class:"usa-input"|attr:"aria-describedby:instructions" }}
|
||||
|
||||
{{ wizard.form.title|add_label_class:"usa-label" }}
|
||||
{{ wizard.form.title|add_class:"usa-input"|attr:"aria-describedby:instructions" }}
|
||||
|
||||
{{ wizard.form.email|add_label_class:"usa-label" }}
|
||||
{{ wizard.form.email|add_class:"usa-input"|attr:"aria-describedby:instructions" }}
|
||||
|
||||
{{ wizard.form.phone|add_label_class:"usa-label" }}
|
||||
{{ wizard.form.phone|add_class:"usa-input usa-input--medium"|attr:"aria-describedby:instructions" }}
|
||||
</fieldset>
|
||||
|
||||
|
||||
{{ block.super }}
|
||||
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
|
@ -171,10 +171,8 @@
|
|||
|
||||
{% block section_nav %}{% endblock %}
|
||||
|
||||
<main id="main-content" class="grid-container">
|
||||
{% block hero %}{% endblock %}
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
|
||||
<div role="complementary">{% block complementary %}{% endblock %}</div>
|
||||
|
||||
|
|
|
@ -6,20 +6,51 @@
|
|||
<section class="usa-hero">
|
||||
<div class="usa-grid">
|
||||
<div class="usa-hero-callout usa-section-dark">
|
||||
<h2>
|
||||
<h1>
|
||||
<span class="usa-hero-callout-alt">Welcome to the .gov registrar</span>
|
||||
</h2>
|
||||
</h1>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<main id="main-content" class="grid-container">
|
||||
<p>This is the .gov registrar.</p>
|
||||
|
||||
{% if user.is_authenticated %}
|
||||
<p><a href="/openid/logout/">Click here to log out.</a></p>
|
||||
{% else %}
|
||||
<p><a href="/openid/login/">Click here to log in.</a></p>
|
||||
|
||||
{% if domain_applications %}
|
||||
<h2>Your domain applications</h2>
|
||||
<table class="usa-table usa-table--borderless">
|
||||
<caption class="sr-only">Your domain applications</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for application in domain_applications %}
|
||||
<tr>
|
||||
<th>{{ application.requested_domain.name }}</th>
|
||||
<td>{{ application.status }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
|
||||
<p><a href="{% url 'application' %}" class="usa-button">Apply</a></p>
|
||||
|
||||
<p><a href="{% url 'edit-profile' %}">Edit profile</a></p>
|
||||
|
||||
{% if user.is_staff %}
|
||||
<p><a href="{% url 'admin:index' %}">CISA admin panel</a></p>
|
||||
{% endif %}
|
||||
|
||||
<p><a href="{% url 'logout' %}">Click here to log out.</a></p>
|
||||
{% else %}
|
||||
<p><a href="{% url 'login' %}">Click here to log in.</a></p>
|
||||
{% endif %}
|
||||
</main>
|
||||
{% endblock %}
|
||||
|
|
1
src/registrar/templates/includes/ao_example__city.html
Normal file
1
src/registrar/templates/includes/ao_example__city.html
Normal file
|
@ -0,0 +1 @@
|
|||
<p>Domain requests from <strong>cities</strong> must be authorized by the mayor or the equivalent highest elected official.</p>
|
15
src/registrar/templates/includes/domain_example__city.html
Normal file
15
src/registrar/templates/includes/domain_example__city.html
Normal file
|
@ -0,0 +1,15 @@
|
|||
<p> Most city domains must include the two-letter state abbreviation or clearly spell out the state name. Using phrases like “City of” or “Town of” is optional.</p>
|
||||
<p>Examples:
|
||||
<ul class="usa-list">
|
||||
<li>www.BlufftonIndiana.gov</li>
|
||||
<li>www.CityofEudoraKS.gov</li>
|
||||
<li>www.WallawallaWA.gov</li>
|
||||
</ul>
|
||||
</p>
|
||||
<p> Some cities don’t have to refer to their state.
|
||||
<ul class="usa-list">
|
||||
<li> City names that are not shared by any other U.S. city, town, or village can be requested without referring to the state. We use the Census Bureau’s National Places Gazetteer Files to determine if names are unique. </li>
|
||||
<li>Certain cities are so well-known that they may not require a state reference to communicate location. We use the list of U.S. “dateline cities” in the Associated Press Stylebook to make this determination.</li>
|
||||
<li>The 50 largest cities, as measured by population according to the Census Bureau, can have .gov domain names that don’t refer to their state.</li>
|
||||
</ul>
|
||||
</p>
|
|
@ -1,14 +1,14 @@
|
|||
<div>
|
||||
<label for="{{ choice.id_for_label }}"
|
||||
><input
|
||||
<div class="usa-radio">
|
||||
<input
|
||||
type="{{ choice.data.type }}"
|
||||
name="{{ choice.data.name }}"
|
||||
value="{{ choice.data.value }}"
|
||||
class="usa-radio"
|
||||
class="usa-radio__input {% if tile %}usa-radio__input--tile {%endif%}"
|
||||
id="{{ choice.id_for_label }}"
|
||||
{% if choice.data.attrs.required %}required{% endif %}
|
||||
{% if choice.data.selected %}checked{% endif %}
|
||||
/>
|
||||
{{ choice.data.label }}</label
|
||||
>
|
||||
</div>
|
||||
<label class="usa-radio__label" for="{{ choice.id_for_label }}" >
|
||||
{{ choice.data.label }}
|
||||
</label >
|
||||
</div>
|
||||
|
|
|
@ -5,22 +5,24 @@ Edit your User Profile
|
|||
{% endblock title %}
|
||||
|
||||
{% block content %}
|
||||
<form class="usa-form usa-form--large" method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
<fieldset class="usa-fieldset">
|
||||
<legend class="usa-legend usa-legend--large">Your profile</legend>
|
||||
<p>
|
||||
<main id="main-content" class="grid-container">
|
||||
<form class="usa-form usa-form--large" method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
<fieldset class="usa-fieldset">
|
||||
<legend class="usa-legend usa-legend--large">Your profile</legend>
|
||||
<p>
|
||||
Required fields are marked with an asterisk (<abbr
|
||||
title="required"
|
||||
class="usa-hint usa-hint--required"
|
||||
>*</abbr
|
||||
>).
|
||||
</p>
|
||||
{% for field in profile_form %}
|
||||
<label class="usa-label" for="id_{{ field.name }}">{{ field.label }}</label>
|
||||
{{ field }}
|
||||
{% endfor %}
|
||||
</fieldset>
|
||||
<button type="submit" class="usa-button usa-button--big">Save Changes</button>
|
||||
</form>
|
||||
>*</abbr>).
|
||||
</p>
|
||||
{% for field in profile_form %}
|
||||
<label class="usa-label" for="id_{{ field.name }}">{{ field.label }}</label>
|
||||
{{ field }}
|
||||
{% endfor %}
|
||||
</fieldset>
|
||||
<button type="submit" class="usa-button usa-button--big">Save Changes</button>
|
||||
</form>
|
||||
</main>
|
||||
{% endblock content %}
|
||||
|
||||
|
|
|
@ -3,7 +3,9 @@
|
|||
|
||||
{% block title %} Hello {% endblock %}
|
||||
{% block content %}
|
||||
<p> Hello {{ user.last_name|default:"No last name given" }}, {{ user.first_name|default:"No first name given" }} <{{ user.email }}>! </p>
|
||||
<main id="main-content" class="grid-container">
|
||||
<p> Hello {{ user.last_name|default:"No last name given" }}, {{ user.first_name|default:"No first name given" }} <{{ user.email }}>! </p>
|
||||
|
||||
<p><a href="/openid/logout">Click here to log out</a></p>
|
||||
<p><a href="/openid/logout">Click here to log out</a></p>
|
||||
</main>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,7 +1,57 @@
|
|||
import os
|
||||
import logging
|
||||
|
||||
from contextlib import contextmanager
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import get_user_model, login
|
||||
|
||||
|
||||
def get_handlers():
|
||||
"""Obtain pointers to all StreamHandlers."""
|
||||
handlers = {}
|
||||
|
||||
rootlogger = logging.getLogger()
|
||||
for h in rootlogger.handlers:
|
||||
if isinstance(h, logging.StreamHandler):
|
||||
handlers[h.name] = h
|
||||
|
||||
for logger in logging.Logger.manager.loggerDict.values():
|
||||
if not isinstance(logger, logging.PlaceHolder):
|
||||
for h in logger.handlers:
|
||||
if isinstance(h, logging.StreamHandler):
|
||||
handlers[h.name] = h
|
||||
|
||||
return handlers
|
||||
|
||||
|
||||
@contextmanager
|
||||
def less_console_noise():
|
||||
"""
|
||||
Context manager to use in tests to silence console logging.
|
||||
|
||||
This is helpful on tests which trigger console messages
|
||||
(such as errors) which are normal and expected.
|
||||
|
||||
It can easily be removed to debug a failing test.
|
||||
"""
|
||||
restore = {}
|
||||
handlers = get_handlers()
|
||||
devnull = open(os.devnull, "w")
|
||||
|
||||
# redirect all the streams
|
||||
for handler in handlers.values():
|
||||
prior = handler.setStream(devnull)
|
||||
restore[handler.name] = prior
|
||||
try:
|
||||
# run the test
|
||||
yield
|
||||
finally:
|
||||
# restore the streams
|
||||
for handler in handlers.values():
|
||||
handler.setStream(restore[handler.name])
|
||||
|
||||
|
||||
class MockUserLogin:
|
||||
def __init__(self, get_response):
|
||||
self.get_response = get_response
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
from django.test import TestCase
|
||||
from django.db.utils import IntegrityError
|
||||
|
||||
from registrar.models import Contact, DomainApplication, User, Website
|
||||
from registrar.models import Contact, DomainApplication, User, Website, Domain
|
||||
from unittest import skip
|
||||
|
||||
|
||||
class TestDomainApplication(TestCase):
|
||||
|
@ -22,6 +23,7 @@ class TestDomainApplication(TestCase):
|
|||
contact = Contact.objects.create()
|
||||
com_website, _ = Website.objects.get_or_create(website="igorville.com")
|
||||
gov_website, _ = Website.objects.get_or_create(website="igorville.gov")
|
||||
domain, _ = Domain.objects.get_or_create(name="igorville.gov")
|
||||
application = DomainApplication.objects.create(
|
||||
creator=user,
|
||||
investigator=user,
|
||||
|
@ -35,7 +37,7 @@ class TestDomainApplication(TestCase):
|
|||
state_territory="CA",
|
||||
zip_code="12345-6789",
|
||||
authorizing_official=contact,
|
||||
requested_domain=gov_website,
|
||||
requested_domain=domain,
|
||||
submitter=contact,
|
||||
purpose="Igorville rules!",
|
||||
security_email="security@igorville.gov",
|
||||
|
@ -56,9 +58,101 @@ class TestDomainApplication(TestCase):
|
|||
|
||||
def test_status_fsm_submit_succeed(self):
|
||||
user, _ = User.objects.get_or_create()
|
||||
site = Website.objects.create(website="igorville.gov")
|
||||
site = Domain.objects.create(name="igorville.gov")
|
||||
application = DomainApplication.objects.create(
|
||||
creator=user, requested_domain=site
|
||||
)
|
||||
application.submit()
|
||||
self.assertEqual(application.status, application.SUBMITTED)
|
||||
|
||||
|
||||
class TestDomain(TestCase):
|
||||
def test_empty_create_fails(self):
|
||||
"""Can't create a completely empty domain."""
|
||||
with self.assertRaisesRegex(IntegrityError, "name"):
|
||||
Domain.objects.create()
|
||||
|
||||
def test_minimal_create(self):
|
||||
"""Can create with just a name."""
|
||||
domain = Domain.objects.create(name="igorville.gov")
|
||||
self.assertEquals(domain.is_active, False)
|
||||
|
||||
def test_get_status(self):
|
||||
"""Returns proper status based on `is_active`."""
|
||||
domain = Domain.objects.create(name="igorville.gov")
|
||||
domain.save()
|
||||
self.assertEquals(None, domain.status)
|
||||
domain.activate()
|
||||
domain.save()
|
||||
self.assertIn("ok", domain.status)
|
||||
|
||||
def test_fsm_activate_fail_unique(self):
|
||||
"""Can't activate domain if name is not unique."""
|
||||
d1, _ = Domain.objects.get_or_create(name="igorville.gov")
|
||||
d2, _ = Domain.objects.get_or_create(name="igorville.gov")
|
||||
d1.activate()
|
||||
d1.save()
|
||||
with self.assertRaises(ValueError):
|
||||
d2.activate()
|
||||
|
||||
def test_fsm_activate_fail_unapproved(self):
|
||||
"""Can't activate domain if application isn't approved."""
|
||||
d1, _ = Domain.objects.get_or_create(name="igorville.gov")
|
||||
user, _ = User.objects.get_or_create()
|
||||
application = DomainApplication.objects.create(creator=user)
|
||||
d1.domain_application = application
|
||||
d1.save()
|
||||
with self.assertRaises(ValueError):
|
||||
d1.activate()
|
||||
|
||||
|
||||
@skip("Not implemented yet.")
|
||||
class TestDomainApplicationLifeCycle(TestCase):
|
||||
def test_application_approval(self):
|
||||
# DomainApplication is created
|
||||
# test: Domain is created and is inactive
|
||||
# analyst approves DomainApplication
|
||||
# test: Domain is activated
|
||||
pass
|
||||
|
||||
def test_application_rejection(self):
|
||||
# DomainApplication is created
|
||||
# test: Domain is created and is inactive
|
||||
# analyst rejects DomainApplication
|
||||
# test: Domain remains inactive
|
||||
pass
|
||||
|
||||
def test_application_deleted_before_approval(self):
|
||||
# DomainApplication is created
|
||||
# test: Domain is created and is inactive
|
||||
# admin deletes DomainApplication
|
||||
# test: Domain is deleted; Hosts, HostIps and Nameservers are deleted
|
||||
pass
|
||||
|
||||
def test_application_deleted_following_approval(self):
|
||||
# DomainApplication is created
|
||||
# test: Domain is created and is inactive
|
||||
# analyst approves DomainApplication
|
||||
# admin deletes DomainApplication
|
||||
# test: DomainApplication foreign key field on Domain is set to null
|
||||
pass
|
||||
|
||||
def test_application_approval_with_conflicting_name(self):
|
||||
# DomainApplication #1 is created
|
||||
# test: Domain #1 is created and is inactive
|
||||
# analyst approves DomainApplication #1
|
||||
# test: Domain #1 is activated
|
||||
# DomainApplication #2 is created, with the same domain name string
|
||||
# test: Domain #2 is created and is inactive
|
||||
# analyst approves DomainApplication #2
|
||||
# test: error is raised
|
||||
# test: DomainApplication #1 remains approved
|
||||
# test: Domain #1 remains active
|
||||
# test: DomainApplication #2 remains in investigating
|
||||
# test: Domain #2 remains inactive
|
||||
pass
|
||||
|
||||
def test_application_approval_with_network_errors(self):
|
||||
# TODO: scenario wherein application is approved,
|
||||
# but attempts to contact the registry to activate the domain fail
|
||||
pass
|
||||
|
|
|
@ -5,7 +5,10 @@ from django.contrib.auth import get_user_model
|
|||
|
||||
from django_webtest import WebTest # type: ignore
|
||||
|
||||
from registrar.models import DomainApplication
|
||||
from registrar.models import DomainApplication, Domain
|
||||
from registrar.forms.application_wizard import TITLES
|
||||
|
||||
from .common import less_console_noise
|
||||
|
||||
|
||||
class TestViews(TestCase):
|
||||
|
@ -54,6 +57,18 @@ class LoggedInTests(TestWithUser):
|
|||
super().setUp()
|
||||
self.client.force_login(self.user)
|
||||
|
||||
def test_home_lists_domain_applications(self):
|
||||
response = self.client.get("/")
|
||||
self.assertNotContains(response, "igorville.gov")
|
||||
site = Domain.objects.create(name="igorville.gov")
|
||||
application = DomainApplication.objects.create(
|
||||
creator=self.user, requested_domain=site
|
||||
)
|
||||
response = self.client.get("/")
|
||||
self.assertContains(response, "igorville.gov", count=1)
|
||||
# clean up
|
||||
application.delete()
|
||||
|
||||
def test_whoami_page(self):
|
||||
"""User information appears on the whoami page."""
|
||||
response = self.client.get("/whoami/")
|
||||
|
@ -67,7 +82,9 @@ class LoggedInTests(TestWithUser):
|
|||
|
||||
def test_application_form_view(self):
|
||||
response = self.client.get("/register/", follow=True)
|
||||
self.assertContains(response, "About your organization")
|
||||
self.assertContains(
|
||||
response, "What kind of government organization do you represent?"
|
||||
)
|
||||
|
||||
|
||||
class FormTests(TestWithUser, WebTest):
|
||||
|
@ -92,63 +109,272 @@ class FormTests(TestWithUser, WebTest):
|
|||
page = self.app.get(reverse("application")).follow()
|
||||
# submitting should get back the same page if the required field is empty
|
||||
result = page.form.submit()
|
||||
self.assertIn("About your organization", result)
|
||||
|
||||
def test_application_form_organization(self):
|
||||
# 302 redirect to the first form
|
||||
page = self.app.get(reverse("application")).follow()
|
||||
form = page.form
|
||||
form["organization-organization_type"] = "Federal"
|
||||
result = page.form.submit().follow()
|
||||
# Got the next form page
|
||||
self.assertContains(result, "contact information")
|
||||
self.assertIn("What kind of government organization do you represent?", result)
|
||||
|
||||
def test_application_form_submission(self):
|
||||
"""Can fill out the entire form and submit.
|
||||
|
||||
As we add additional form pages, we need to include them here to make
|
||||
this test work.
|
||||
"""
|
||||
page = self.app.get(reverse("application")).follow()
|
||||
type_page = self.app.get(reverse("application")).follow()
|
||||
# django-webtest does not handle cookie-based sessions well because it keeps
|
||||
# resetting the session key on each new request, thus destroying the concept
|
||||
# of a "session". We are going to do it manually, saving the session ID here
|
||||
# and then setting the cookie on each request.
|
||||
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
||||
|
||||
form = page.form
|
||||
form["organization-organization_type"] = "Federal"
|
||||
form["organization-federal_type"] = "Executive"
|
||||
# ---- TYPE PAGE ----
|
||||
type_form = type_page.form
|
||||
type_form["organization_type-organization_type"] = "Federal"
|
||||
|
||||
# set the session ID before .submit()
|
||||
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||
result = page.form.submit()
|
||||
type_result = type_page.form.submit()
|
||||
|
||||
# the post request should return a redirect to the next form in
|
||||
# the application
|
||||
self.assertEquals(result.status_code, 302)
|
||||
self.assertEquals(result["Location"], "/register/contact/")
|
||||
self.assertEquals(type_result.status_code, 302)
|
||||
self.assertEquals(type_result["Location"], "/register/organization_federal/")
|
||||
|
||||
# ---- FEDERAL BRANCH PAGE ----
|
||||
# Follow the redirect to the next form page
|
||||
next_page = result.follow()
|
||||
contact_form = next_page.form
|
||||
contact_form["contact-organization_name"] = "test"
|
||||
contact_form["contact-street_address"] = "100 Main Street"
|
||||
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||
federal_page = type_result.follow()
|
||||
federal_form = federal_page.form
|
||||
federal_form["organization_federal-federal_type"] = "Executive"
|
||||
|
||||
# set the session ID before .submit()
|
||||
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||
result = contact_form.submit()
|
||||
federal_result = federal_form.submit()
|
||||
|
||||
self.assertEquals(federal_result.status_code, 302)
|
||||
self.assertEquals(federal_result["Location"], "/register/organization_contact/")
|
||||
|
||||
# ---- ORG CONTACT PAGE ----
|
||||
# Follow the redirect to the next form page
|
||||
org_contact_page = federal_result.follow()
|
||||
org_contact_form = org_contact_page.form
|
||||
org_contact_form["organization_contact-organization_name"] = "Testorg"
|
||||
org_contact_form["organization_contact-address_line1"] = "address 1"
|
||||
org_contact_form["organization_contact-us_state"] = "NY"
|
||||
org_contact_form["organization_contact-zipcode"] = "10002"
|
||||
|
||||
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||
org_contact_result = org_contact_form.submit()
|
||||
|
||||
self.assertEquals(org_contact_result.status_code, 302)
|
||||
self.assertEquals(
|
||||
org_contact_result["Location"], "/register/authorizing_official/"
|
||||
)
|
||||
# ---- AUTHORIZING OFFICIAL PAGE ----
|
||||
# Follow the redirect to the next form page
|
||||
ao_page = org_contact_result.follow()
|
||||
ao_form = ao_page.form
|
||||
ao_form["authorizing_official-first_name"] = "Testy"
|
||||
ao_form["authorizing_official-last_name"] = "Tester"
|
||||
ao_form["authorizing_official-title"] = "Chief Tester"
|
||||
ao_form["authorizing_official-email"] = "testy@town.com"
|
||||
ao_form["authorizing_official-phone"] = "(555) 555 5555"
|
||||
|
||||
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||
ao_result = ao_form.submit()
|
||||
|
||||
self.assertEquals(ao_result.status_code, 302)
|
||||
self.assertEquals(ao_result["Location"], "/register/current_sites/")
|
||||
|
||||
# ---- CURRENT SITES PAGE ----
|
||||
# Follow the redirect to the next form page
|
||||
current_sites_page = ao_result.follow()
|
||||
current_sites_form = current_sites_page.form
|
||||
current_sites_form["current_sites-current_site"] = "www.city.com"
|
||||
|
||||
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||
current_sites_result = current_sites_form.submit()
|
||||
|
||||
self.assertEquals(current_sites_result.status_code, 302)
|
||||
self.assertEquals(current_sites_result["Location"], "/register/dotgov_domain/")
|
||||
|
||||
# ---- DOTGOV DOMAIN PAGE ----
|
||||
# Follow the redirect to the next form page
|
||||
dotgov_page = current_sites_result.follow()
|
||||
dotgov_form = dotgov_page.form
|
||||
dotgov_form["dotgov_domain-dotgov_domain"] = "city"
|
||||
|
||||
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||
dotgov_result = dotgov_form.submit()
|
||||
|
||||
self.assertEquals(dotgov_result.status_code, 302)
|
||||
self.assertEquals(dotgov_result["Location"], "/register/purpose/")
|
||||
|
||||
# ---- PURPOSE DOMAIN PAGE ----
|
||||
# Follow the redirect to the next form page
|
||||
purpose_page = dotgov_result.follow()
|
||||
purpose_form = purpose_page.form
|
||||
purpose_form["purpose-purpose_field"] = "Purpose of the site"
|
||||
|
||||
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||
purpose_result = purpose_form.submit()
|
||||
|
||||
self.assertEquals(purpose_result.status_code, 302)
|
||||
self.assertEquals(purpose_result["Location"], "/register/your_contact/")
|
||||
|
||||
# ---- YOUR CONTACT INFO PAGE ----
|
||||
# Follow the redirect to the next form page
|
||||
your_contact_page = purpose_result.follow()
|
||||
your_contact_form = your_contact_page.form
|
||||
|
||||
your_contact_form["your_contact-first_name"] = "Testy you"
|
||||
your_contact_form["your_contact-last_name"] = "Tester you"
|
||||
your_contact_form["your_contact-title"] = "Admin Tester"
|
||||
your_contact_form["your_contact-email"] = "testy-admin@town.com"
|
||||
your_contact_form["your_contact-phone"] = "(555) 555 5556"
|
||||
|
||||
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||
your_contact_result = your_contact_form.submit()
|
||||
|
||||
self.assertEquals(your_contact_result.status_code, 302)
|
||||
self.assertEquals(your_contact_result["Location"], "/register/other_contacts/")
|
||||
|
||||
# ---- OTHER CONTACTS PAGE ----
|
||||
# Follow the redirect to the next form page
|
||||
other_contacts_page = your_contact_result.follow()
|
||||
other_contacts_form = other_contacts_page.form
|
||||
|
||||
other_contacts_form["other_contacts-first_name"] = "Testy2"
|
||||
other_contacts_form["other_contacts-last_name"] = "Tester2"
|
||||
other_contacts_form["other_contacts-title"] = "Another Tester"
|
||||
other_contacts_form["other_contacts-email"] = "testy2@town.com"
|
||||
other_contacts_form["other_contacts-phone"] = "(555) 555 5557"
|
||||
|
||||
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||
other_contacts_result = other_contacts_form.submit()
|
||||
|
||||
self.assertEquals(other_contacts_result.status_code, 302)
|
||||
self.assertEquals(
|
||||
other_contacts_result["Location"], "/register/security_email/"
|
||||
)
|
||||
|
||||
# ---- SECURITY EMAIL PAGE ----
|
||||
# Follow the redirect to the next form page
|
||||
security_email_page = other_contacts_result.follow()
|
||||
security_email_form = security_email_page.form
|
||||
|
||||
security_email_form["security_email-email"] = "security@city.com"
|
||||
|
||||
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||
security_email_result = security_email_form.submit()
|
||||
|
||||
self.assertEquals(security_email_result.status_code, 302)
|
||||
self.assertEquals(security_email_result["Location"], "/register/anything_else/")
|
||||
|
||||
# ---- ANYTHING ELSE PAGE ----
|
||||
# Follow the redirect to the next form page
|
||||
anything_else_page = security_email_result.follow()
|
||||
anything_else_form = anything_else_page.form
|
||||
|
||||
anything_else_form["anything_else-anything_else"] = "No"
|
||||
|
||||
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||
anything_else_result = anything_else_form.submit()
|
||||
|
||||
self.assertEquals(anything_else_result.status_code, 302)
|
||||
self.assertEquals(anything_else_result["Location"], "/register/requirements/")
|
||||
|
||||
# ---- REQUIREMENTS PAGE ----
|
||||
# Follow the redirect to the next form page
|
||||
requirements_page = anything_else_result.follow()
|
||||
requirements_form = requirements_page.form
|
||||
|
||||
requirements_form["requirements-agree_check"] = True
|
||||
|
||||
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||
requirements_result = requirements_form.submit()
|
||||
|
||||
self.assertEquals(requirements_result.status_code, 302)
|
||||
self.assertEquals(requirements_result["Location"], "/register/review/")
|
||||
|
||||
# ---- REVIEW AND FINSIHED PAGES ----
|
||||
# Follow the redirect to the next form page
|
||||
review_page = requirements_result.follow()
|
||||
review_form = review_page.form
|
||||
|
||||
# final submission results in a redirect to the "finished" URL
|
||||
self.assertEquals(result.status_code, 302)
|
||||
self.assertEquals(result["Location"], "/register/finished/")
|
||||
|
||||
# the finished URL (for now) returns a redirect to /
|
||||
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||
review_result = review_form.submit()
|
||||
|
||||
self.assertEquals(review_result.status_code, 302)
|
||||
self.assertEquals(review_result["Location"], "/register/finished/")
|
||||
|
||||
# following this redirect is a GET request, so include the cookie
|
||||
# here too.
|
||||
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||
next_result = result.follow()
|
||||
self.assertEquals(next_result.status_code, 302)
|
||||
self.assertEquals(next_result["Location"], "/")
|
||||
with less_console_noise():
|
||||
final_result = review_result.follow()
|
||||
self.assertContains(final_result, "Thank you for your domain request")
|
||||
|
||||
self.assertContains(next_result.follow(), "Welcome to the .gov registrar")
|
||||
# TODO: when we have a page that lists applications, visit it and
|
||||
# make sure that the new one exists
|
||||
def test_application_form_conditional_federal(self):
|
||||
"""Federal branch question is shown for federal organizations."""
|
||||
type_page = self.app.get(reverse("application")).follow()
|
||||
# django-webtest does not handle cookie-based sessions well because it keeps
|
||||
# resetting the session key on each new request, thus destroying the concept
|
||||
# of a "session". We are going to do it manually, saving the session ID here
|
||||
# and then setting the cookie on each request.
|
||||
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
||||
|
||||
# ---- TYPE PAGE ----
|
||||
|
||||
# the conditional step titles shouldn't appear initially
|
||||
self.assertNotContains(type_page, TITLES["organization_federal"])
|
||||
self.assertNotContains(type_page, TITLES["organization_election"])
|
||||
type_form = type_page.form
|
||||
type_form["organization_type-organization_type"] = "Federal"
|
||||
|
||||
# set the session ID before .submit()
|
||||
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||
type_result = type_form.submit()
|
||||
|
||||
# the post request should return a redirect to the federal branch
|
||||
# question
|
||||
self.assertEquals(type_result.status_code, 302)
|
||||
self.assertEquals(type_result["Location"], "/register/organization_federal/")
|
||||
|
||||
# and the step label should appear in the sidebar of the resulting page
|
||||
# but the step label for the elections page should not appear
|
||||
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||
federal_page = type_result.follow()
|
||||
self.assertContains(federal_page, TITLES["organization_federal"])
|
||||
self.assertNotContains(federal_page, TITLES["organization_election"])
|
||||
|
||||
def test_application_form_conditional_elections(self):
|
||||
"""Election question is shown for other organizations."""
|
||||
type_page = self.app.get(reverse("application")).follow()
|
||||
# django-webtest does not handle cookie-based sessions well because it keeps
|
||||
# resetting the session key on each new request, thus destroying the concept
|
||||
# of a "session". We are going to do it manually, saving the session ID here
|
||||
# and then setting the cookie on each request.
|
||||
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
||||
|
||||
# ---- TYPE PAGE ----
|
||||
|
||||
# the conditional step titles shouldn't appear initially
|
||||
self.assertNotContains(type_page, TITLES["organization_federal"])
|
||||
self.assertNotContains(type_page, TITLES["organization_election"])
|
||||
type_form = type_page.form
|
||||
type_form["organization_type-organization_type"] = "County"
|
||||
|
||||
# set the session ID before .submit()
|
||||
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||
type_result = type_form.submit()
|
||||
|
||||
# the post request should return a redirect to the federal branch
|
||||
# question
|
||||
self.assertEquals(type_result.status_code, 302)
|
||||
self.assertEquals(type_result["Location"], "/register/organization_election/")
|
||||
|
||||
# and the step label should appear in the sidebar of the resulting page
|
||||
# but the step label for the elections page should not appear
|
||||
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||
election_page = type_result.follow()
|
||||
self.assertContains(election_page, TITLES["organization_election"])
|
||||
self.assertNotContains(election_page, TITLES["organization_federal"])
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
from django.shortcuts import render
|
||||
|
||||
from registrar.models import DomainApplication
|
||||
|
||||
|
||||
def index(request):
|
||||
"""This page is available to anyone without logging in."""
|
||||
return render(request, "home.html")
|
||||
context = {}
|
||||
if request.user.is_authenticated:
|
||||
applications = DomainApplication.objects.filter(creator=request.user)
|
||||
context["domain_applications"] = applications
|
||||
return render(request, "home.html", context)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue