Initial architecture

This commit is contained in:
zandercymatics 2024-05-09 15:56:18 -06:00
parent 8b41e70840
commit 75499337e0
No known key found for this signature in database
GPG key ID: FF4636ABEC9682B7
12 changed files with 238 additions and 61 deletions

View file

@ -104,7 +104,7 @@ urlpatterns = [
# We embed the current user ID here, but we have a permission check
# that ensures the user is who they say they are.
"finish-user-setup/<int:pk>",
views.FinishContactProfileSetupView.as_view(),
views.ContactProfileSetupView.as_view(),
name="finish-contact-profile-setup",
),
path(

View file

@ -126,11 +126,11 @@ class UserFixture:
"last_name": "Osos-Analyst",
"email": "kosos@truss.works",
},
{
"username": "2cc0cde8-8313-4a50-99d8-5882e71443e8",
"first_name": "Zander-Analyst",
"last_name": "Adkinson-Analyst",
},
# {
# "username": "2cc0cde8-8313-4a50-99d8-5882e71443e8",
# "first_name": "Zander-Analyst",
# "last_name": "Adkinson-Analyst",
# },
{
"username": "57ab5847-7789-49fe-a2f9-21d38076d699",
"first_name": "Paul-Analyst",

View file

@ -0,0 +1,42 @@
from django import forms
from phonenumber_field.modelfields import PhoneNumberField # type: ignore
from django.core.validators import MaxLengthValidator
class ContactForm(forms.Form):
"""Form for adding or editing a contact"""
first_name = forms.CharField(
label="First name / given name",
error_messages={"required": "Enter your first name / given name."},
)
middle_name = forms.CharField(
required=False,
label="Middle name (optional)",
)
last_name = forms.CharField(
label="Last name / family name",
error_messages={"required": "Enter your last name / family name."},
)
title = forms.CharField(
label="Title or role in your organization",
error_messages={
"required": ("Enter your title or role in your organization (e.g., Chief Information Officer).")
},
)
email = forms.EmailField(
label="Email",
max_length=None,
error_messages={"invalid": ("Enter your email address in the required format, like name@example.com.")},
validators=[
MaxLengthValidator(
320,
message="Response must be less than 320 characters.",
)
],
)
phone = PhoneNumberField(
label="Phone",
error_messages={"invalid": "Enter a valid 10-digit phone number.", "required": "Enter your phone number."},
)

View file

@ -202,7 +202,7 @@ NameserverFormset = formset_factory(
validate_max=True,
)
# TODO - refactor, wait until daves PR
class ContactForm(forms.ModelForm):
"""Form for updating contacts."""

View file

@ -16,6 +16,7 @@ from registrar.forms.utility.wizard_form_helper import (
from registrar.models import Contact, DomainRequest, DraftDomain, Domain, FederalAgency
from registrar.templatetags.url_helpers import public_site_url
from registrar.utility.enums import ValidationReturnType
from registrar.forms import ContactForm
logger = logging.getLogger(__name__)
@ -385,7 +386,7 @@ class PurposeForm(RegistrarForm):
)
class YourContactForm(RegistrarForm):
class YourContactForm(RegistrarForm, ContactForm):
JOIN = "submitter"
def to_database(self, obj):
@ -408,40 +409,6 @@ class YourContactForm(RegistrarForm):
contact = getattr(obj, "submitter", None)
return super().from_database(contact)
first_name = forms.CharField(
label="First name / given name",
error_messages={"required": "Enter your first name / given name."},
)
middle_name = forms.CharField(
required=False,
label="Middle name (optional)",
)
last_name = forms.CharField(
label="Last name / family name",
error_messages={"required": "Enter your last name / family name."},
)
title = forms.CharField(
label="Title or role in your organization",
error_messages={
"required": ("Enter your title or role in your organization (e.g., Chief Information Officer).")
},
)
email = forms.EmailField(
label="Email",
max_length=None,
error_messages={"invalid": ("Enter your email address in the required format, like name@example.com.")},
validators=[
MaxLengthValidator(
320,
message="Response must be less than 320 characters.",
)
],
)
phone = PhoneNumberField(
label="Phone",
error_messages={"invalid": "Enter a valid 10-digit phone number.", "required": "Enter your phone number."},
)
class OtherContactsYesNoForm(BaseYesNoForm):
"""The yes/no field for the OtherContacts form."""

View file

@ -21,7 +21,12 @@ class RegistrarForm(forms.Form):
def __init__(self, *args, **kwargs):
kwargs.setdefault("label_suffix", "")
# save a reference to a domain request object
self.domain_request = kwargs.pop("domain_request", None)
if "domain_request" in kwargs:
self.domain_request = kwargs.pop("domain_request", None)
if "contact" in kwargs:
self.contact = kwargs.pop("contact", None)
super(RegistrarForm, self).__init__(*args, **kwargs)
def to_database(self, obj: DomainRequest | Contact):

View file

@ -1,10 +1,8 @@
from django.db import models
from .utility.time_stamped_model import TimeStampedModel
from phonenumber_field.modelfields import PhoneNumberField # type: ignore
class Contact(TimeStampedModel):
"""Contact information follows a similar pattern for each contact."""

View file

@ -1,7 +1,72 @@
{% extends "base.html" %}
{% load static url_helpers %}
{% load static form_helpers url_helpers field_helpers %}
{% block title %} Finish setting up your profile {% endblock %}
{% block content %}
<h2>TEST</h2>
<div class="grid-container">
<div class="grid-row grid-gap">
<div class="tablet:grid-col">
<main id="main-content" class="grid-container">
{% include "includes/form_messages.html" %}
{% comment %}
Repurposed from domain_request_form.html
{% endcomment %}
{% for outer in forms %}
{% if outer|isformset %}
{% for inner in outer.forms %}
{% include "includes/form_errors.html" with form=inner %}
{% endfor %}
{% else %}
{% include "includes/form_errors.html" with form=outer %}
{% endif %}
{% endfor %}
<h1>Finish setting up your profile</h1>
<p>
We <a href="#">require</a> that you maintain accurate contact information.
The details you provide will only be used to support the administration of .gov and wont be made public.
</p>
<h2>What contact information should we use to reach you?</h2>
<p>
Review the details below and update any required information.
Note that editing this information wont affect your Login.gov account information.
</p>
{# TODO: maybe remove this? #}
<p>Required information is marked with an asterisk (*).</p>
<form id="step__{{steps.current}}" class="usa-form usa-form--large" method="post" novalidate>
{% csrf_token %}
<fieldset class="usa-fieldset">
<legend class="usa-sr-only">
Your contact information
</legend>
{% input_with_errors forms.0.first_name %}
{% input_with_errors forms.0.middle_name %}
{% input_with_errors forms.0.last_name %}
{% input_with_errors forms.0.title %}
{% input_with_errors forms.0.email %}
{% with add_class="usa-input--medium" %}
{% input_with_errors forms.0.phone %}
{% endwith %}
</fieldset>
<div>
<button type="submit" name="submit_button" class="usa-button">
Save
</button>
</div>
{% block form_fields %}{% endblock %}
</main>
</div>
</div>
</div>
{% endblock content %}

View file

@ -14,5 +14,8 @@ from .domain import (
DomainInvitationDeleteView,
DomainDeleteUserView,
)
from .contact import (
ContactProfileSetupView,
)
from .health import *
from .index import *

View file

@ -0,0 +1,107 @@
from registrar.forms.contact import ContactForm
from registrar.views.utility.permission_views import ContactPermissionView
from django.views.generic.edit import FormMixin
# TODO we can and probably should generalize this at this rate.
class BaseContactView(ContactPermissionView):
def get(self, request, *args, **kwargs):
self._set_contact(request)
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
# TODO - this deserves a small refactor
def _set_contact(self, request):
"""
get domain from session cache or from db and set
to self.object
set session to self for downstream functions to
update session cache
"""
self.session = request.session
contact_pk = "contact:" + str(self.kwargs.get("pk"))
cached_contact = self.session.get(contact_pk)
if cached_contact:
self.object = cached_contact
else:
self.object = self.get_object()
self._update_session_with_contact()
def _update_session_with_contact(self):
"""
Set contact pk in the session cache
"""
domain_pk = "contact:" + str(self.kwargs.get("pk"))
self.session[domain_pk] = self.object
class ContactFormBaseView(BaseContactView, FormMixin):
def post(self, request, *args, **kwargs):
"""Form submission posts to this view.
This post method harmonizes using BaseContactView and FormMixin
"""
# Set the current contact object in cache
self._set_contact(request)
# Get the current form and validate it
form = self.get_form()
return self.check_form(form)
# TODO rename?
def check_form(self, form):
return self.form_valid(form) if form.is_valid() else self.form_invalid(form)
def form_valid(self, form):
# updates session cache with contact
self._update_session_with_contact()
# superclass has the redirect
return super().form_valid(form)
def form_invalid(self, form):
# updates session cache with contact
self._update_session_with_contact()
# superclass has the redirect
return super().form_invalid(form)
class ContactProfileSetupView(ContactPermissionView):
"""This view forces the user into providing additional details that
we may have missed from Login.gov"""
template_name = "finish_contact_setup.html"
form_class = ContactForm
def get(self, request, *args, **kwargs):
self._get_contact(request)
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
def _get_contact(self, request):
"""
get domain from session cache or from db and set
to self.object
set session to self for downstream functions to
update session cache
"""
self.session = request.session
contact_pk = "contact:" + str(self.kwargs.get("pk"))
cached_contact = self.session.get(contact_pk)
if cached_contact:
self.object = cached_contact
else:
self.object = self.get_object()
self._set_session_contact_pk()
def _set_session_contact_pk(self):
"""
Set contact pk in the session cache
"""
domain_pk = "contact:" + str(self.kwargs.get("pk"))
self.session[domain_pk] = self.object

View file

@ -14,7 +14,7 @@ from registrar.models.contact import Contact
from registrar.models.user import User
from registrar.utility import StrEnum
from registrar.views.utility import StepsHelper
from registrar.views.utility.permission_views import DomainRequestPermissionDeleteView, ContactPermissionView
from registrar.views.utility.permission_views import DomainRequestPermissionDeleteView
from .utility import (
DomainRequestPermissionView,
@ -820,9 +820,3 @@ class DomainRequestDeleteView(DomainRequestPermissionDeleteView):
duplicates = [item for item, count in object_dict.items() if count > 1]
return duplicates
class FinishContactProfileSetupView(ContactPermissionView):
"""This view forces the user into providing additional details that
we may have missed from Login.gov"""
template_name = "finish_contact_setup.html"
forms = [forms.YourContactForm]

View file

@ -153,11 +153,7 @@ class ContactPermissionView(ContactPermission, DetailView, abc.ABC):
# DetailView property for what model this is viewing
model = Contact
# variable name in template context for the model object
context_object_name = "Contact"
object: Contact
# Abstract property enforces NotImplementedError on an attribute.
@property
@abc.abstractmethod
def template_name(self):
raise NotImplementedError
# variable name in template context for the model object
context_object_name = "contact"