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

1""" 

2The eligibility application: Form definition for the eligibility verification flow. 

3""" 

4 

5import logging 

6 

7from django import forms 

8from django.core.validators import RegexValidator 

9from django.utils.translation import gettext_lazy as _ 

10 

11from benefits.core import models, widgets 

12from benefits.core.mixins import ValidateRecaptchaMixin 

13from benefits.routes import routes 

14 

15logger = logging.getLogger(__name__) 

16 

17 

18class EnrollmentFlowSelectionForm(ValidateRecaptchaMixin, forms.Form): 

19 """Form to capture enrollment flow selection.""" 

20 

21 action_url = routes.ELIGIBILITY_INDEX 

22 id = "form-flow-selection" 

23 method = "POST" 

24 

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") 

28 

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) 

32 

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 

40 

41 

42class EligibilityVerificationForm(ValidateRecaptchaMixin, forms.Form): 

43 """Base form to collect eligibility verification details.""" 

44 

45 action_url = routes.ELIGIBILITY_CONFIRM 

46 id = "form-eligibility-verification" 

47 method = "POST" 

48 

49 submit_value = _("Find my record") 

50 submitting_value = _("Checking") 

51 classes = "eligibility-verification-form" 

52 

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 

57 

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.") 

65 

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 

73 

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) 

77 

78 # Configure the 'sub' field (ID/Card Number) 

79 sub_widget = widgets.FormControlTextInput(placeholder=self.sub_placeholder) 

80 

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 

90 

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)) 

94 

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 ) 

102 

103 # Configure the 'name' field 

104 name_widget = widgets.FormControlTextInput(placeholder=self.name_placeholder) 

105 

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 

111 

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 ) 

118 

119 

120class MSTCourtesyCard(EligibilityVerificationForm): 

121 """EligibilityVerification form for the MST Courtesy Card.""" 

122 

123 blurb = _("We use the information on your MST Courtesy Card to find the record of your transit benefit in our system.") 

124 

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.") 

132 

133 

134class CSTAgencyCard(MSTCourtesyCard): 

135 """ 

136 EligibilityVerification form for the CST Agency Card. 

137 Inherits validation logic from MSTCourtesyCard but overrides specific text. 

138 """ 

139 

140 blurb = _("We use the information on your CST Agency Card to find the record of your transit benefit in our system.") 

141 

142 sub_label = _("Agency Card number") 

143 

144 

145class SBMTDMobilityPass(EligibilityVerificationForm): 

146 """EligibilityVerification form for the SBMTD Reduced Fare Mobility ID.""" 

147 

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 ) 

151 

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.")