mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-08-05 09:21:54 +02:00
Merge branch 'main' into nmb/available-domain
This commit is contained in:
commit
5d9a469ebd
18 changed files with 383 additions and 24 deletions
|
@ -2,6 +2,7 @@
|
|||
"urls": [
|
||||
"http://app:8080/",
|
||||
"http://app:8080/health/",
|
||||
"http://app:8080/whoami/"
|
||||
"http://app:8080/whoami/",
|
||||
"http://app:8080/register/"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
FROM python:3
|
||||
FROM python:3.10
|
||||
|
||||
# Python 3.11 introduces a bug in oic package, fyi - Oct 31, 2022
|
||||
|
||||
RUN apt-get update && apt-get install -y postgresql-client
|
||||
|
||||
|
|
|
@ -15,6 +15,8 @@ oic = "*"
|
|||
pyjwkest = "*"
|
||||
psycopg2-binary = "*"
|
||||
whitenoise = "*"
|
||||
django-formtools = "*"
|
||||
django-widget-tweaks = "*"
|
||||
cachetools = "*"
|
||||
requests = "*"
|
||||
|
||||
|
@ -27,4 +29,5 @@ flake8 = "*"
|
|||
mypy = "*"
|
||||
types-requests = "*"
|
||||
django-stubs = "*"
|
||||
django-webtest = "*"
|
||||
types-cachetools = "*"
|
||||
|
|
72
src/Pipfile.lock
generated
72
src/Pipfile.lock
generated
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "c26e60781d5da15edf636481c9a18506899f824283e8da44e0edf357d29a62cc"
|
||||
"sha256": "1b7689dae771eaeee047ef75ed1da344ebc9d40fbb9ade689e9dba885e20ec59"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {},
|
||||
|
@ -214,6 +214,22 @@
|
|||
"index": "pypi",
|
||||
"version": "==3.7"
|
||||
},
|
||||
"django-formtools": {
|
||||
"hashes": [
|
||||
"sha256:deb932be55b1d9419e37dc4d65dfbfeb8d307b71c8c11fd52f159aba5fc0deed",
|
||||
"sha256:f5f32f62ec8192cd1bc55bd929ca7dff5a5f2addf9027db95a5906ecfaa64836"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.4"
|
||||
},
|
||||
"django-widget-tweaks": {
|
||||
"hashes": [
|
||||
"sha256:9bfc5c705684754a83cc81da328b39ad1b80f32bd0f4340e2a810cbab4b0c00e",
|
||||
"sha256:fe6b17d5d595c63331f300917980db2afcf71f240ab9341b954aea8f45d25b9a"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.4.12"
|
||||
},
|
||||
"environs": {
|
||||
"extras": [
|
||||
"django"
|
||||
|
@ -236,7 +252,7 @@
|
|||
"hashes": [
|
||||
"sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"
|
||||
],
|
||||
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'",
|
||||
"version": "==0.18.2"
|
||||
},
|
||||
"gunicorn": {
|
||||
|
@ -502,7 +518,7 @@
|
|||
"sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
|
||||
"sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'",
|
||||
"version": "==1.16.0"
|
||||
},
|
||||
"sqlparse": {
|
||||
|
@ -555,6 +571,14 @@
|
|||
"index": "pypi",
|
||||
"version": "==1.7.4"
|
||||
},
|
||||
"beautifulsoup4": {
|
||||
"hashes": [
|
||||
"sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30",
|
||||
"sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==4.11.1"
|
||||
},
|
||||
"black": {
|
||||
"hashes": [
|
||||
"sha256:14ff67aec0a47c424bc99b71005202045dc09270da44a27848d534600ac64fc7",
|
||||
|
@ -630,6 +654,14 @@
|
|||
"markers": "python_version >= '3.6'",
|
||||
"version": "==0.5.0"
|
||||
},
|
||||
"django-webtest": {
|
||||
"hashes": [
|
||||
"sha256:c8c32041791cdae468e443097c432c67cf17cad339e1ab88b01a6c4841ee4c74",
|
||||
"sha256:ef075e98b38fe3836dc533c2924d3e37c6bb3483008c40567115518a0303b1af"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.9.10"
|
||||
},
|
||||
"flake8": {
|
||||
"hashes": [
|
||||
"sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db",
|
||||
|
@ -798,7 +830,7 @@
|
|||
"sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
|
||||
"sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'",
|
||||
"version": "==1.16.0"
|
||||
},
|
||||
"smmap": {
|
||||
|
@ -809,6 +841,14 @@
|
|||
"markers": "python_version >= '3.6'",
|
||||
"version": "==5.0.0"
|
||||
},
|
||||
"soupsieve": {
|
||||
"hashes": [
|
||||
"sha256:3b2503d3c7084a42b1ebd08116e5f81aadfaea95863628c80a3b774a11b7c759",
|
||||
"sha256:fc53893b3da2c33de295667a0e19f078c14bf86544af307354de5fcf12a3f30d"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==2.3.2.post1"
|
||||
},
|
||||
"sqlparse": {
|
||||
"hashes": [
|
||||
"sha256:0323c0ec29cd52bceabc1b4d9d579e311f3e4961b98d174201d5622a23b85e34",
|
||||
|
@ -877,6 +917,30 @@
|
|||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==4.4.0"
|
||||
},
|
||||
"waitress": {
|
||||
"hashes": [
|
||||
"sha256:7500c9625927c8ec60f54377d590f67b30c8e70ef4b8894214ac6e4cad233d2a",
|
||||
"sha256:780a4082c5fbc0fde6a2fcfe5e26e6efc1e8f425730863c04085769781f51eba"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==2.1.2"
|
||||
},
|
||||
"webob": {
|
||||
"hashes": [
|
||||
"sha256:73aae30359291c14fa3b956f8b5ca31960e420c28c1bec002547fb04928cf89b",
|
||||
"sha256:b64ef5141be559cfade448f044fa45c2260351edcb6a8ef6b7e00c7dcef0c323"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'",
|
||||
"version": "==1.8.7"
|
||||
},
|
||||
"webtest": {
|
||||
"hashes": [
|
||||
"sha256:2a001a9efa40d2a7e5d9cd8d1527c75f41814eb6afce2c3d207402547b1e5ead",
|
||||
"sha256:54bd969725838d9861a9fa27f8d971f79d275d94ae255f5c501f53bb6d9929eb"
|
||||
],
|
||||
"markers": "python_version >= '3.6' and python_version < '4'",
|
||||
"version": "==3.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -84,6 +84,8 @@ INSTALLED_APPS = [
|
|||
"django.contrib.staticfiles",
|
||||
# application used for integrating with Login.gov
|
||||
"djangooidc",
|
||||
# library to simplify form templating
|
||||
"widget_tweaks",
|
||||
# let's be sure to install our own application!
|
||||
"registrar",
|
||||
# Our internal API application
|
||||
|
@ -146,7 +148,6 @@ STATICFILES_DIRS = [
|
|||
BASE_DIR / "assets",
|
||||
]
|
||||
|
||||
# TODO: decide on template engine and document in ADR
|
||||
TEMPLATES = [
|
||||
{
|
||||
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||
|
@ -168,11 +169,16 @@ TEMPLATES = [
|
|||
"django.contrib.messages.context_processors.messages",
|
||||
"registrar.context_processors.language_code",
|
||||
"registrar.context_processors.canonical_path",
|
||||
"registrar.context_processors.is_demo_site",
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
# IS_DEMO_SITE controls whether or not we show our big red "TEST SITE" banner
|
||||
# underneath the "this is a real government website" banner.
|
||||
IS_DEMO_SITE = True
|
||||
|
||||
# endregion
|
||||
# region: Database----------------------------------------------------------###
|
||||
|
||||
|
|
|
@ -10,8 +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 api.views import available
|
||||
|
||||
APPLICATION_URL_NAME = "application_step"
|
||||
application_wizard = ApplicationWizard.as_view(
|
||||
url_name=APPLICATION_URL_NAME, done_step_name="finished"
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
path("", index.index, name="home"),
|
||||
path("whoami/", whoami.whoami, name="whoami"),
|
||||
|
@ -19,6 +25,8 @@ urlpatterns = [
|
|||
path("health/", health.health),
|
||||
path("edit_profile/", profile.edit_profile, name="edit-profile"),
|
||||
path("openid/", include("djangooidc.urls")),
|
||||
path("register/", application_wizard, name="application"),
|
||||
path("register/<step>/", application_wizard, name=APPLICATION_URL_NAME),
|
||||
path("available/<domain>", available, name="available"),
|
||||
]
|
||||
|
||||
|
|
|
@ -21,3 +21,13 @@ def canonical_path(request):
|
|||
template itself, so we do it here and pass the information on.
|
||||
"""
|
||||
return {"CANONICAL_PATH": request.build_absolute_uri(request.path)}
|
||||
|
||||
|
||||
def is_demo_site(request):
|
||||
"""Add a boolean if this is a demo site.
|
||||
|
||||
To be able to render or not our "demo site" banner, we need a context
|
||||
variable for the template that indicates if this banner should or
|
||||
should not appear.
|
||||
"""
|
||||
return {"IS_DEMO_SITE": settings.IS_DEMO_SITE}
|
||||
|
|
4
src/registrar/forms/__init__.py
Normal file
4
src/registrar/forms/__init__.py
Normal file
|
@ -0,0 +1,4 @@
|
|||
from .edit_profile import EditProfileForm
|
||||
from .application_wizard import ApplicationWizard
|
||||
|
||||
__all__ = ["EditProfileForm", "ApplicationWizard"]
|
100
src/registrar/forms/application_wizard.py
Normal file
100
src/registrar/forms/application_wizard.py
Normal file
|
@ -0,0 +1,100 @@
|
|||
"""Forms Wizard for creating a new domain application."""
|
||||
|
||||
import logging
|
||||
|
||||
from django import forms
|
||||
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
|
||||
from formtools.wizard.views import NamedUrlSessionWizardView # type: ignore
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class OrganizationForm(forms.Form):
|
||||
organization_type = forms.ChoiceField(
|
||||
required=True,
|
||||
choices=[
|
||||
("Federal", "Federal: a federal agency"),
|
||||
("Interstate", "Interstate: an organization of two or more states"),
|
||||
(
|
||||
"State_or_Territory",
|
||||
(
|
||||
"State or Territory: One of the 50 U.S. states, the District of "
|
||||
"Columbia, American Samoa, Guam, Northern Mariana Islands, "
|
||||
"Puerto Rico, or the U.S. Virgin Islands"
|
||||
),
|
||||
),
|
||||
(
|
||||
"Tribal",
|
||||
(
|
||||
"Tribal: a tribal government recognized by the federal or "
|
||||
"state government"
|
||||
),
|
||||
),
|
||||
("County", "County: a county, parish, or borough"),
|
||||
("City", "City: a city, town, township, village, etc."),
|
||||
(
|
||||
"Special_District",
|
||||
"Special District: an independent organization within a single state",
|
||||
),
|
||||
],
|
||||
widget=forms.RadioSelect,
|
||||
)
|
||||
|
||||
|
||||
class ContactForm(forms.Form):
|
||||
organization_name = forms.CharField(label="Organization Name")
|
||||
street_address = forms.CharField(label="Street address")
|
||||
|
||||
|
||||
# List of forms in our wizard. Each entry is a tuple of a name and a form
|
||||
# subclass
|
||||
FORMS = [
|
||||
("organization", OrganizationForm),
|
||||
("contact", ContactForm),
|
||||
]
|
||||
|
||||
# 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",
|
||||
}
|
||||
|
||||
# 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",
|
||||
}
|
||||
|
||||
|
||||
class ApplicationWizard(LoginRequiredMixin, NamedUrlSessionWizardView):
|
||||
|
||||
"""Multi-page form ("wizard") for new domain applications.
|
||||
|
||||
This sets up a sequence of forms that gather information for new
|
||||
domain applications. Each form in the sequence has its own URL and
|
||||
the progress through the form is stored in the Django session (thus
|
||||
"NamedUrlSessionWizardView").
|
||||
"""
|
||||
|
||||
form_list = FORMS
|
||||
|
||||
def get_template_names(self):
|
||||
"""Template for the current step.
|
||||
|
||||
The return is a singleton list.
|
||||
"""
|
||||
return [TEMPLATES[self.steps.current]]
|
||||
|
||||
def get_context_data(self, form, **kwargs):
|
||||
"""Add title information to the context for all steps."""
|
||||
context = super().get_context_data(form=form, **kwargs)
|
||||
context["form_titles"] = TITLES
|
||||
return context
|
||||
|
||||
def done(self, form_list, **kwargs):
|
||||
logger.info("Application form submitted.")
|
|
@ -1,6 +1,6 @@
|
|||
from django import forms
|
||||
|
||||
from .models import UserProfile
|
||||
from ..models import UserProfile
|
||||
|
||||
|
||||
class EditProfileForm(forms.ModelForm):
|
35
src/registrar/templates/application_contact.html
Normal file
35
src/registrar/templates/application_contact.html
Normal file
|
@ -0,0 +1,35 @@
|
|||
<!-- 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" }}
|
||||
</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 %}
|
13
src/registrar/templates/application_form.html
Normal file
13
src/registrar/templates/application_form.html
Normal file
|
@ -0,0 +1,13 @@
|
|||
{% extends 'base.html' %}
|
||||
|
||||
{% 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>
|
||||
</div>
|
||||
{% endblock %}
|
25
src/registrar/templates/application_organization.html
Normal file
25
src/registrar/templates/application_organization.html
Normal file
|
@ -0,0 +1,25 @@
|
|||
<!-- Test page -->
|
||||
{% extends 'application_form.html' %}
|
||||
{% load widget_tweaks %}
|
||||
|
||||
{% 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 %}
|
||||
|
||||
<fieldset class="usa-fieldset">
|
||||
<legend>
|
||||
<h2> What kind of government organization do you represent?</h2>
|
||||
</legend>
|
||||
|
||||
{{ wizard.form.organization_type|add_class:"usa-radio" }}
|
||||
|
||||
</fieldset>
|
||||
<button type="submit" class="usa-button">Next</button>
|
||||
|
||||
</form>
|
||||
{% endblock %}
|
18
src/registrar/templates/application_sidebar.html
Normal file
18
src/registrar/templates/application_sidebar.html
Normal file
|
@ -0,0 +1,18 @@
|
|||
<div class="tablet:grid-col-4 margin-bottom-4 tablet:margin-bottom-0">
|
||||
<nav aria-label="Form steps,">
|
||||
<ul class="usa-sidenav">
|
||||
{% for this_step in wizard.steps.all %}
|
||||
<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%}>
|
||||
{{ form_titles|get_item:this_step }}
|
||||
</a>
|
||||
{% else %}
|
||||
{{ form_titles|get_item:this_step }}
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
|
@ -47,7 +47,7 @@
|
|||
<script src="{% static 'js/uswds.min.js' %}" defer></script>
|
||||
<a class="usa-skipnav" href="#main-content">Skip to main content</a>
|
||||
|
||||
<section class="usa-banner" aria-label="Official government website">
|
||||
<section class="usa-banner" aria-label="Official website of the United States government">
|
||||
<div class="usa-accordion">
|
||||
<header class="usa-banner__header">
|
||||
<div class="usa-banner__inner">
|
||||
|
@ -58,17 +58,15 @@
|
|||
<p class="usa-banner__header-text">
|
||||
An official website of the United States government
|
||||
</p>
|
||||
<p class="usa-banner__header-action" aria-hidden="true">
|
||||
Here’s how you know
|
||||
</p>
|
||||
<p class="usa-banner__header-action">Here’s how you know</p>
|
||||
</div>
|
||||
<button class="usa-accordion__button usa-banner__button" aria-expanded="false"
|
||||
aria-controls="gov-banner-default-default">
|
||||
aria-controls="gov-banner-default">
|
||||
<span class="usa-banner__button-text">Here’s how you know</span>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
<div class="usa-banner__content usa-accordion__content" id="gov-banner-default-default">
|
||||
<div class="usa-banner__content usa-accordion__content" id="gov-banner-default">
|
||||
<div class="grid-row grid-gap-lg">
|
||||
<div class="usa-banner__guidance tablet:grid-col-6">
|
||||
<img class="usa-banner__icon usa-media-block__img" src="{% static 'img/icon-dot-gov.svg' %}" role="img"
|
||||
|
@ -90,9 +88,9 @@
|
|||
<strong>lock</strong> (
|
||||
<span class="icon-lock"><svg xmlns="http://www.w3.org/2000/svg" width="52" height="64"
|
||||
viewBox="0 0 52 64" class="usa-banner__lock-image" role="img"
|
||||
aria-labelledby="banner-lock-title-default banner-lock-description-default" focusable="false">
|
||||
<title id="banner-lock-title-default">Lock</title>
|
||||
<desc id="banner-lock-description-default">A locked padlock</desc>
|
||||
aria-labelledby="banner-lock-description" focusable="false">
|
||||
<title id="banner-lock-title">Lock</title>
|
||||
<desc id="banner-lock-description">Locked padlock icon</desc>
|
||||
<path fill="#000000" fill-rule="evenodd"
|
||||
d="M26 0c10.493 0 19 8.507 19 19v9h3a4 4 0 0 1 4 4v28a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V32a4 4 0 0 1 4-4h3v-9C7 8.507 15.507 0 26 0zm0 8c-5.979 0-10.843 4.77-10.996 10.712L15 19v9h22v-9c0-6.075-4.925-11-11-11z" />
|
||||
</svg> </span>) or <strong>https://</strong> means you’ve safely connected to
|
||||
|
@ -105,10 +103,25 @@
|
|||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% if IS_DEMO_SITE %}
|
||||
<section
|
||||
class="usa-site-alert usa-site-alert--emergency usa-site-alert--no-icon"
|
||||
aria-label="Site alert"
|
||||
>
|
||||
<div class="usa-alert">
|
||||
<div class="usa-alert__body">
|
||||
<p class="usa-alert__text">
|
||||
<strong>TEST SITE</strong> - Do not use real personal information. Demo purposes only.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% block usa_overlay %}<div class="usa-overlay"></div>{% endblock %}
|
||||
{% block banner %}
|
||||
<header class="usa-header usa-header-basic" role="navigation">
|
||||
<header class="usa-header usa-header-basic">
|
||||
<div class="usa-nav-container">
|
||||
<div class="usa-navbar">
|
||||
{% block logo %}
|
||||
|
@ -120,10 +133,10 @@
|
|||
</em>
|
||||
</div>
|
||||
{% endblock %}
|
||||
<button class="usa-menu-btn">Menu</button>
|
||||
<button type="button" class="usa-menu-btn">Menu</button>
|
||||
</div>
|
||||
{% block usa_nav %}
|
||||
<nav>
|
||||
<nav class="usa-nav" aria-label="Primary navigation,">
|
||||
<button type="button" class="usa-nav__close">
|
||||
<img src="/public/img/usa-icons/close.svg" role="img" alt="Close" />
|
||||
</button>
|
||||
|
@ -142,7 +155,6 @@
|
|||
</div>
|
||||
</header>
|
||||
{% endblock banner %}
|
||||
{% block usa_overlay %}<div class="usa-overlay"></div>{% endblock %}
|
||||
<div id="wrapper">
|
||||
{% block messages %}
|
||||
{% if messages %}
|
||||
|
@ -158,7 +170,7 @@
|
|||
|
||||
{% block section_nav %}{% endblock %}
|
||||
|
||||
<main id="main-content">
|
||||
<main id="main-content" class="grid-container">
|
||||
{% block hero %}{% endblock %}
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
|
|
8
src/registrar/templatetags/__init__.py
Normal file
8
src/registrar/templatetags/__init__.py
Normal file
|
@ -0,0 +1,8 @@
|
|||
"""Custom template tags to make our lives easier."""
|
||||
|
||||
from django.template.defaulttags import register
|
||||
|
||||
|
||||
@register.filter
|
||||
def get_item(dictionary, key):
|
||||
return dictionary.get(key)
|
|
@ -14,7 +14,7 @@ class MockUserLogin:
|
|||
args = {
|
||||
UserModel.USERNAME_FIELD: username,
|
||||
}
|
||||
user = UserModel.objects.get_or_create(**args)
|
||||
user, _ = UserModel.objects.get_or_create(**args)
|
||||
user.is_staff = True
|
||||
user.is_superuser = True
|
||||
user.save()
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
from django.test import Client, TestCase
|
||||
from django.urls import reverse
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
from django_webtest import WebTest # type: ignore
|
||||
|
||||
|
||||
class TestViews(TestCase):
|
||||
def setUp(self):
|
||||
|
@ -22,8 +25,14 @@ class TestViews(TestCase):
|
|||
self.assertEqual(response.status_code, 302)
|
||||
self.assertIn("?next=/whoami/", response.headers["Location"])
|
||||
|
||||
def test_application_form_not_logged_in(self):
|
||||
"""Application form not accessible without a logged-in user."""
|
||||
response = self.client.get("/register/")
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertIn("/login?next=/register/", response.headers["Location"])
|
||||
|
||||
class LoggedInTests(TestCase):
|
||||
|
||||
class TestWithUser(TestCase):
|
||||
def setUp(self):
|
||||
username = "test_user"
|
||||
first_name = "First"
|
||||
|
@ -32,6 +41,14 @@ class LoggedInTests(TestCase):
|
|||
self.user = get_user_model().objects.create(
|
||||
username=username, first_name=first_name, last_name=last_name, email=email
|
||||
)
|
||||
|
||||
def tearDown(self):
|
||||
self.user.delete()
|
||||
|
||||
|
||||
class LoggedInTests(TestWithUser):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.client.force_login(self.user)
|
||||
|
||||
def test_whoami_page(self):
|
||||
|
@ -44,3 +61,36 @@ class LoggedInTests(TestCase):
|
|||
def test_edit_profile(self):
|
||||
response = self.client.get("/edit_profile/")
|
||||
self.assertContains(response, "Display Name")
|
||||
|
||||
def test_application_form_view(self):
|
||||
response = self.client.get("/register/", follow=True)
|
||||
self.assertContains(response, "About your organization")
|
||||
|
||||
|
||||
class FormTests(TestWithUser, WebTest):
|
||||
|
||||
"""Webtests for forms to test filling and submitting."""
|
||||
|
||||
# Doesn't work with CSRF checking
|
||||
# hypothesis is that CSRF_USE_SESSIONS is incompatible with WebTest
|
||||
csrf_checks = False
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.app.set_user(self.user.username)
|
||||
|
||||
def test_application_form_empty_submit(self):
|
||||
# 302 redirect to the first form
|
||||
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.assertIn("contact information", result)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue