Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor: eligibility forms #1657

Merged
merged 4 commits into from
Aug 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 1 addition & 13 deletions benefits/core/migrations/0001_initial.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,19 +46,7 @@ class Migration(migrations.Migration):
("jws_signing_alg", models.TextField(null=True)),
("selection_label_template", models.TextField()),
("start_template", models.TextField(null=True)),
("form_title", models.TextField(null=True)),
("form_headline", models.TextField(null=True)),
("form_blurb", models.TextField(null=True)),
("form_sub_label", models.TextField(null=True)),
("form_sub_help_text", models.TextField(null=True)),
("form_sub_placeholder", models.TextField(null=True)),
("form_sub_pattern", models.TextField(null=True)),
("form_input_mode", models.TextField(null=True)),
("form_max_length", models.PositiveSmallIntegerField(null=True)),
("form_name_label", models.TextField(null=True)),
("form_name_help_text", models.TextField(null=True)),
("form_name_placeholder", models.TextField(null=True)),
("form_name_max_length", models.PositiveSmallIntegerField(null=True)),
("form_class", models.TextField(null=True)),
(
"auth_provider",
models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to="core.authprovider"),
Expand Down
15 changes: 1 addition & 14 deletions benefits/core/migrations/0002_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import os

from django.db import migrations
from django.utils.translation import gettext_lazy as _


def load_data(app, *args, **kwargs):
Expand Down Expand Up @@ -195,19 +194,7 @@ def load_data(app, *args, **kwargs):
auth_provider=None,
selection_label_template="eligibility/includes/selection-label--mst-courtesy-card.html",
start_template="eligibility/start--mst-courtesy-card.html",
form_title=_("Agency card information"),
form_headline=_("Let’s see if we can confirm your eligibility."),
form_blurb=_("Please input your Courtesy Card number and last name below to confirm your eligibility."),
form_sub_label=_("MST Courtesy Card number"),
form_sub_help_text=_("This is a 5-digit number on the front and back of your card."),
form_sub_placeholder="12345",
form_sub_pattern=r"\d{5}",
form_input_mode="numeric",
form_max_length=5,
form_name_label=_("Last name (as it appears on Courtesy Card)"),
form_name_help_text=_("We use this to help confirm your Courtesy Card."),
form_name_placeholder="Garcia",
form_name_max_length=255,
form_class="benefits.eligibility.forms.MSTCourtesyCard",
)

sacrt_senior_verifier = EligibilityVerifier.objects.create(
Expand Down
31 changes: 14 additions & 17 deletions benefits/core/models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""
The core application: Common model definitions.
"""
import importlib
import logging

from django.conf import settings
Expand Down Expand Up @@ -111,23 +112,8 @@ class EligibilityVerifier(models.Model):
auth_provider = models.ForeignKey(AuthProvider, on_delete=models.PROTECT, null=True)
selection_label_template = models.TextField()
start_template = models.TextField(null=True)
form_title = models.TextField(null=True)
form_headline = models.TextField(null=True)
form_blurb = models.TextField(null=True)
form_sub_label = models.TextField(null=True)
form_sub_help_text = models.TextField(null=True)
form_sub_placeholder = models.TextField(null=True)
# A regular expression used to validate the 'sub' API field before sending to this verifier
form_sub_pattern = models.TextField(null=True)
# Input mode can be "numeric", "tel", "search", etc. to override default "text" keyboard on mobile devices
form_input_mode = models.TextField(null=True)
# The maximum length accepted for the 'sub' API field before sending to this verifier
form_max_length = models.PositiveSmallIntegerField(null=True)
form_name_label = models.TextField(null=True)
form_name_help_text = models.TextField(null=True)
form_name_placeholder = models.TextField(null=True)
# The maximum length accepted for the 'name' API field before sending to this verifier
form_name_max_length = models.PositiveSmallIntegerField(null=True)
# reference to a form class used by this Verifier, e.g. benefits.app.forms.FormClass
form_class = models.TextField(null=True)

def __str__(self):
return self.name
Expand All @@ -147,6 +133,17 @@ def uses_auth_verification(self):
"""True if this Verifier verifies via the auth provider. False otherwise."""
return self.is_auth_required and self.auth_provider.supports_claims_verification

def form_instance(self, *args, **kwargs):
"""Return an instance of this verifier's form, or None."""
if not bool(self.form_class):
return None

# inspired by https://stackoverflow.com/a/30941292
module_name, class_name = self.form_class.rsplit(".", 1)
FormClass = getattr(importlib.import_module(module_name), class_name)

return FormClass(*args, **kwargs)

@staticmethod
def by_id(id):
"""Get an EligibilityVerifier instance by its ID."""
Expand Down
108 changes: 91 additions & 17 deletions benefits/eligibility/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,32 +53,106 @@ class EligibilityVerificationForm(forms.Form):
"missing": _("This field is required."),
}

def __init__(self, verifier: models.EligibilityVerifier, *args, **kwargs):
super().__init__(*args, **kwargs)
def __init__(
self,
title,
headline,
blurb,
name_label,
name_placeholder,
name_help_text,
sub_label,
sub_placeholder,
sub_help_text,
name_max_length=None,
sub_input_mode=None,
sub_max_length=None,
sub_pattern=None,
Comment on lines +67 to +70
Copy link
Member

@machikoyasuda machikoyasuda Aug 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These deleted comments from the data file could become docstring or comments on this file to describe these:

    # A regular expression used to validate the 'sub' API field before sending to this verifier
    form_sub_pattern = models.TextField(null=True)
    # Input mode can be "numeric", "tel", "search", etc. to override default "text" keyboard on mobile devices
    form_input_mode = models.TextField(null=True)
    # The maximum length accepted for the 'sub' API field before sending to this verifier
    form_max_length = models.PositiveSmallIntegerField(null=True)
    # The maximum length accepted for the 'name' API field before sending to this verifier
    form_name_max_length = models.PositiveSmallIntegerField(null=True)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for pasting here 🙏 I'll add those.

Copy link
Member Author

@thekaveman thekaveman Aug 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉🎉🎉

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh, typo... one more rebase incoming

*args,
**kwargs,
):
"""Initialize a new EligibilityVerifier form.

Args:
title (str): The page (i.e. tab) title for the form's page.

headline (str): The <h1> on the form's page.

blurb (str): Intro <p> on the form's page.

name_label (str): Label for the name form field.

name_placeholder (str): Field placeholder for the name form field.

name_help_text (str): Extra help text for the name form field.

sub_label (str): Label for the sub form field.

sub_placeholder (str): Field placeholder for the sub form field.

sub_help_text (str): Extra help text for the sub form field.

name_max_length (int): The maximum length accepted for the 'name' API field before sending to this verifier

sub_input_mode (str): Input mode can be "numeric", "tel", "search", etc. to override default "text" keyboard on
mobile devices

sub_max_length (int): The maximum length accepted for the 'sub' API field before sending to this verifier

sub_pattern (str): A regular expression used to validate the 'sub' API field before sending to this verifier

Extra args and kwargs are passed through to the underlying django.forms.Form.
"""
super().__init__(auto_id=True, label_suffix="", *args, **kwargs)

self.title = title
self.headline = headline
self.blurb = blurb

self.classes = "col-lg-6"
sub_widget = widgets.FormControlTextInput(placeholder=verifier.form_sub_placeholder)
if verifier.form_sub_pattern:
sub_widget.attrs.update({"pattern": verifier.form_sub_pattern})
if verifier.form_input_mode:
sub_widget.attrs.update({"inputmode": verifier.form_input_mode})
if verifier.form_max_length:
sub_widget.attrs.update({"maxlength": verifier.form_max_length})
sub_widget = widgets.FormControlTextInput(placeholder=sub_placeholder)
if sub_pattern:
sub_widget.attrs.update({"pattern": sub_pattern})
if sub_input_mode:
sub_widget.attrs.update({"inputmode": sub_input_mode})
if sub_max_length:
sub_widget.attrs.update({"maxlength": sub_max_length})

self.fields["sub"] = forms.CharField(
label=_(verifier.form_sub_label),
label=sub_label,
widget=sub_widget,
help_text=_(verifier.form_sub_help_text),
help_text=sub_help_text,
)

name_widget = widgets.FormControlTextInput(placeholder=verifier.form_name_placeholder)
if verifier.form_name_max_length:
name_widget.attrs.update({"maxlength": verifier.form_name_max_length})
name_widget = widgets.FormControlTextInput(placeholder=name_placeholder)
if name_max_length:
name_widget.attrs.update({"maxlength": name_max_length})

self.fields["name"] = forms.CharField(
label=_(verifier.form_name_label), widget=name_widget, help_text=_(verifier.form_name_help_text)
)
self.fields["name"] = forms.CharField(label=name_label, widget=name_widget, help_text=name_help_text)

def clean(self):
if not recaptcha.verify(self.data):
raise forms.ValidationError("reCAPTCHA failed")


class MSTCourtesyCard(EligibilityVerificationForm):
"""EligibilityVerification form for the MST Courtesy Card."""

def __init__(self, *args, **kwargs):
super().__init__(
title=_("Agency card information"),
headline=_("Let’s see if we can confirm your eligibility."),
blurb=_("Please input your Courtesy Card number and last name below to confirm your eligibility."),
name_label=_("Last name (as it appears on Courtesy Card)"),
name_placeholder="Garcia",
name_help_text=_("We use this to help confirm your Courtesy Card."),
sub_label=_("MST Courtesy Card number"),
sub_help_text=_("This is a 5-digit number on the front and back of your card."),
sub_placeholder="12345",
name_max_length=255,
sub_input_mode="numeric",
sub_max_length=5,
sub_pattern=r"\d{5}",
*args,
**kwargs,
)
7 changes: 3 additions & 4 deletions benefits/eligibility/templates/eligibility/confirm.html
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
{% extends "core/base.html" %}
{% load i18n %}

{% block page-title %}
{% translate title %}
{{ form.title }}
{% endblock page-title %}

{% block nav-buttons %}
Expand All @@ -12,12 +11,12 @@

{% block headline %}
<div class="col-lg-8">
<h1>{% translate headline %}</h1>
<h1>{{ form.headline }}</h1>
</div>
{% endblock headline %}

{% block explanatory-text %}
<p class="pt-4 pb-4 pb-lg-8">{% translate blurb %}</p>
<p class="pt-4 pb-4 pb-lg-8">{{ form.blurb }}</p>
{% endblock explanatory-text %}

{% block inner-content %}
Expand Down
12 changes: 4 additions & 8 deletions benefits/eligibility/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,13 +111,10 @@ def confirm(request):
else:
return redirect(unverified_view)

form = verifier.form_instance()

# GET/POST for Eligibility API verification
context = {
"title": verifier.form_title,
"headline": verifier.form_headline,
"blurb": verifier.form_blurb,
"form": forms.EligibilityVerificationForm(auto_id=True, label_suffix="", verifier=verifier),
}
context = {"form": form}

# GET from an unverified user, present the form
if request.method == "GET":
Expand All @@ -126,12 +123,11 @@ def confirm(request):
elif request.method == "POST":
analytics.started_eligibility(request, types_to_verify)

form = forms.EligibilityVerificationForm(data=request.POST, verifier=verifier)
form = verifier.form_instance(data=request.POST)
# form was not valid, allow for correction/resubmission
if not form.is_valid():
if recaptcha.has_error(form):
messages.error(request, "Recaptcha failed. Please try again.")

context["form"] = form
return TemplateResponse(request, TEMPLATE_CONFIRM, context)

Expand Down
48 changes: 24 additions & 24 deletions benefits/locale/en/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -6,36 +6,13 @@
msgid ""
msgstr ""
"Report-Msgid-Bugs-To: https://github.com/cal-itp/benefits/issues \n"
"POT-Creation-Date: 2023-08-15 06:04+0000\n"
"POT-Creation-Date: 2023-08-15 22:45+0000\n"
"Language: English\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

msgid "Agency card information"
msgstr ""

msgid "Let’s see if we can confirm your eligibility."
msgstr ""

msgid ""
"Please input your Courtesy Card number and last name below to confirm your "
"eligibility."
msgstr ""

msgid "MST Courtesy Card number"
msgstr ""

msgid "This is a 5-digit number on the front and back of your card."
msgstr ""

msgid "Last name (as it appears on Courtesy Card)"
msgstr ""

msgid "We use this to help confirm your Courtesy Card."
msgstr ""

msgid "Get started"
msgstr ""

Expand Down Expand Up @@ -291,6 +268,29 @@ msgstr ""
msgid "This field is required."
msgstr ""

msgid "Agency card information"
msgstr ""

msgid "Let’s see if we can confirm your eligibility."
msgstr ""

msgid ""
"Please input your Courtesy Card number and last name below to confirm your "
"eligibility."
msgstr ""

msgid "Last name (as it appears on Courtesy Card)"
msgstr ""

msgid "We use this to help confirm your Courtesy Card."
msgstr ""

msgid "MST Courtesy Card number"
msgstr ""

msgid "This is a 5-digit number on the front and back of your card."
msgstr ""

msgid "Your contactless card details"
msgstr ""

Expand Down
Loading