Coverage for benefits / eligibility / forms.py: 98%
88 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-02-13 19:35 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-02-13 19:35 +0000
1"""
2The eligibility application: Form definition for the eligibility verification flow.
3"""
5import logging
7from django import forms
8from django.core.validators import RegexValidator
9from django.utils.translation import gettext_lazy as _
11from benefits.core import models, widgets
12from benefits.core.mixins import ValidateRecaptchaMixin
13from benefits.routes import routes
15logger = logging.getLogger(__name__)
18class EnrollmentFlowSelectionForm(ValidateRecaptchaMixin, forms.Form):
19 """Form to capture enrollment flow selection."""
21 action_url = routes.ELIGIBILITY_INDEX
22 id = "form-flow-selection"
23 method = "POST"
25 flow = forms.ChoiceField(label="", widget=widgets.FlowRadioSelect)
26 # sets label to empty string so the radio_select template can override the label style
27 submit_value = _("Choose this benefit")
29 def __init__(self, agency: models.TransitAgency, *args, **kwargs):
30 super().__init__(*args, **kwargs)
31 flows = agency.enrollment_flows.filter(supported_enrollment_methods__contains=models.EnrollmentMethods.DIGITAL)
33 # second element is not used since we render the whole label using selection_label_template,
34 # therefore set to None
35 flow_field = self.fields["flow"]
36 flow_field.choices = [(f.id, None) for f in flows]
37 flow_field.widget.selection_label_templates = {f.id: f.selection_label_template for f in flows}
38 flow_field.widget.attrs.update({"data-custom-validity": _("Please choose a transit benefit.")})
39 self.use_custom_validity = True
42class EligibilityVerificationForm(ValidateRecaptchaMixin, forms.Form):
43 """Base form to collect eligibility verification details."""
45 action_url = routes.ELIGIBILITY_CONFIRM
46 id = "form-eligibility-verification"
47 method = "POST"
49 submit_value = _("Find my record")
50 submitting_value = _("Checking")
51 classes = "eligibility-verification-form"
53 # Default configuration attributes (override in subclasses)
54 title = _("Agency card information")
55 headline = _("Let’s find the record of your transit benefit.")
56 blurb = None
58 name_label = _("Last Name")
59 name_placeholder = "Garcia"
60 name_help_text = _(
61 "Please enter your last name the same way it is printed on your card, including capital letters and hyphens."
62 )
63 name_max_length = 255
64 name_custom_validity = _("Please enter your last name.")
66 sub_label = None
67 sub_placeholder = None
68 sub_help_text = None
69 sub_input_mode = None
70 sub_max_length = None
71 sub_pattern = None
72 sub_custom_validity = None
74 def __init__(self, *args, **kwargs):
75 """Initialize the form using class attributes for configuration."""
76 super().__init__(auto_id=True, label_suffix="", *args, **kwargs)
78 # Configure the 'sub' field (ID/Card Number)
79 sub_widget = widgets.FormControlTextInput(placeholder=self.sub_placeholder)
81 if self.sub_pattern:
82 sub_widget.attrs.update({"pattern": self.sub_pattern})
83 if self.sub_input_mode:
84 sub_widget.attrs.update({"inputmode": self.sub_input_mode})
85 if self.sub_max_length:
86 sub_widget.attrs.update({"maxlength": self.sub_max_length})
87 if self.sub_custom_validity:
88 sub_widget.attrs.update({"data-custom-validity": self.sub_custom_validity})
89 self.use_custom_validity = True
91 sub_validators = []
92 if self.sub_pattern and self.sub_custom_validity:
93 sub_validators.append(RegexValidator(regex=self.sub_pattern, message=self.sub_custom_validity))
95 self.fields["sub"] = forms.CharField(
96 label=self.sub_label,
97 widget=sub_widget,
98 help_text=self.sub_help_text,
99 max_length=self.sub_max_length,
100 validators=sub_validators,
101 )
103 # Configure the 'name' field
104 name_widget = widgets.FormControlTextInput(placeholder=self.name_placeholder)
106 if self.name_max_length: 106 ↛ 108line 106 didn't jump to line 108 because the condition on line 106 was always true
107 name_widget.attrs.update({"maxlength": self.name_max_length})
108 if self.name_custom_validity: 108 ↛ 112line 108 didn't jump to line 112 because the condition on line 108 was always true
109 name_widget.attrs.update({"data-custom-validity": self.name_custom_validity})
110 self.use_custom_validity = True
112 self.fields["name"] = forms.CharField(
113 label=self.name_label,
114 widget=name_widget,
115 help_text=self.name_help_text,
116 max_length=self.name_max_length,
117 )
120class MSTCourtesyCard(EligibilityVerificationForm):
121 """EligibilityVerification form for the MST Courtesy Card."""
123 blurb = _("We use the information on your MST Courtesy Card to find the record of your transit benefit in our system.")
125 sub_label = _("Courtesy Card number")
126 sub_placeholder = "12345"
127 sub_help_text = _("This is a 5-digit number on the front and back of your card.")
128 sub_input_mode = "numeric"
129 sub_max_length = 5
130 sub_pattern = r"\d{5}"
131 sub_custom_validity = _("Please enter a 5-digit number.")
134class CSTAgencyCard(MSTCourtesyCard):
135 """
136 EligibilityVerification form for the CST Agency Card.
137 Inherits validation logic from MSTCourtesyCard but overrides specific text.
138 """
140 blurb = _("We use the information on your CST Agency Card to find the record of your transit benefit in our system.")
142 sub_label = _("Agency Card number")
145class SBMTDMobilityPass(EligibilityVerificationForm):
146 """EligibilityVerification form for the SBMTD Reduced Fare Mobility ID."""
148 blurb = _(
149 "We use the information on your SBMTD Reduced Fare Mobility ID card to find the record of your transit benefit in our system." # noqa: E501
150 )
152 sub_label = _("Reduced Fare Mobility ID card number")
153 sub_placeholder = "12345"
154 sub_help_text = _("This is a 4- or 5-digit number on the back of your card.")
155 sub_input_mode = "numeric"
156 sub_max_length = 5
157 sub_pattern = r"\d{4,5}"
158 sub_custom_validity = _("Please enter a 4- or 5-digit number.")