Coverage for benefits/eligibility/views.py: 95%

101 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-11-04 21:44 +0000

1""" 

2The eligibility application: view definitions for the eligibility verification flow. 

3""" 

4 

5from django.contrib import messages 

6from django.shortcuts import redirect 

7from django.urls import reverse 

8from django.views.generic import TemplateView, FormView 

9 

10from benefits.core.context.flow import SystemName 

11from benefits.routes import routes 

12from benefits.core import recaptcha, session 

13from benefits.core.context.agency import AgencySlug 

14from benefits.core.context import formatted_gettext_lazy as _ 

15from benefits.core.mixins import AgencySessionRequiredMixin, FlowSessionRequiredMixin, RecaptchaEnabledMixin 

16from benefits.core.models import EnrollmentFlow 

17from . import analytics, forms, verify 

18 

19 

20class EligibilityIndex: 

21 def __init__(self, form_text): 

22 if not isinstance(form_text, list): 22 ↛ 25line 22 didn't jump to line 25 because the condition on line 22 was always true

23 form_text = [form_text] 

24 

25 self.form_text = form_text 

26 

27 def dict(self): 

28 return dict(form_text=self.form_text) 

29 

30 

31class IndexView(AgencySessionRequiredMixin, RecaptchaEnabledMixin, FormView): 

32 """View handler for the enrollment flow selection form.""" 

33 

34 template_name = "eligibility/index.html" 

35 form_class = forms.EnrollmentFlowSelectionForm 

36 

37 def get_form_kwargs(self): 

38 """Return the keyword arguments for instantiating the form.""" 

39 kwargs = super().get_form_kwargs() 

40 kwargs["agency"] = self.agency 

41 return kwargs 

42 

43 def get_context_data(self, **kwargs): 

44 """Add agency-specific context data.""" 

45 context = super().get_context_data(**kwargs) 

46 

47 eligiblity_index = { 

48 AgencySlug.CST.value: EligibilityIndex( 

49 form_text=_( 

50 "Cal-ITP doesn’t save any of your information. " 

51 "All CST transit benefits reduce fares by 50%% for bus service on fixed routes.".replace("%%", "%") 

52 ) 

53 ), 

54 AgencySlug.EDCTA.value: EligibilityIndex( 

55 form_text=_( 

56 "Cal-ITP doesn’t save any of your information. " 

57 "All EDCTA transit benefits reduce fares by 50%% for bus service on fixed routes.".replace("%%", "%") 

58 ), 

59 ), 

60 AgencySlug.MST.value: EligibilityIndex( 

61 form_text=_( 

62 "Cal-ITP doesn’t save any of your information. " 

63 "All MST transit benefits reduce fares by 50%% for bus service on fixed routes.".replace("%%", "%") 

64 ) 

65 ), 

66 AgencySlug.NEVCO.value: EligibilityIndex( 

67 form_text=_( 

68 "Cal-ITP doesn’t save any of your information. " 

69 "All Nevada County Connects transit benefits reduce fares " 

70 "by 50%% for bus service on fixed routes.".replace("%%", "%") 

71 ) 

72 ), 

73 AgencySlug.RABA.value: EligibilityIndex( 

74 form_text=_( 

75 "Cal-ITP doesn’t save any of your information. " 

76 "All RABA transit benefits reduce fares by 50%% for bus service on fixed routes.".replace("%%", "%") 

77 ) 

78 ), 

79 AgencySlug.SACRT.value: EligibilityIndex( 

80 form_text=_( 

81 "Cal-ITP doesn’t save any of your information. " 

82 "All SacRT transit benefits reduce fares by 50%% for bus service on fixed routes.".replace("%%", "%") 

83 ) 

84 ), 

85 AgencySlug.SBMTD.value: EligibilityIndex( 

86 form_text=_( 

87 "Cal-ITP doesn’t save any of your information. " 

88 "All SBMTD transit benefits reduce fares by 50%% for bus service on fixed routes.".replace("%%", "%") 

89 ) 

90 ), 

91 AgencySlug.VCTC.value: EligibilityIndex( 

92 form_text=_( 

93 "Cal-ITP doesn’t save any of your information. " 

94 "All Ventura County Transportation Commission transit benefits " 

95 "reduce fares by 50%% for bus service on fixed routes.".replace("%%", "%") 

96 ) 

97 ), 

98 } 

99 

100 context.update(eligiblity_index[self.agency.slug].dict()) 

101 return context 

102 

103 def get(self, request, *args, **kwargs): 

104 """Initialize session state before handling the request.""" 

105 

106 session.update(request, eligible=False, origin=self.agency.index_url) 

107 session.logout(request) 

108 

109 return super().get(request, *args, **kwargs) 

110 

111 def form_valid(self, form): 

112 """If the form is valid, set enrollment flow and redirect.""" 

113 flow_id = form.cleaned_data.get("flow") 

114 flow = EnrollmentFlow.objects.get(id=flow_id) 

115 session.update(self.request, flow=flow) 

116 

117 analytics.selected_flow(self.request, flow) 

118 return redirect(routes.ELIGIBILITY_START) 

119 

120 def form_invalid(self, form): 

121 """If the form is invalid, display error messages.""" 

122 if recaptcha.has_error(form): 

123 messages.error(self.request, "Recaptcha failed. Please try again.") 

124 return super().form_invalid(form) 

125 

126 

127class StartView(AgencySessionRequiredMixin, FlowSessionRequiredMixin, TemplateView): 

128 """CBV for the eligibility verification getting started screen.""" 

129 

130 template_name = "eligibility/start.html" 

131 

132 def get(self, request, *args, **kwargs): 

133 session.update(request, eligible=False, origin=reverse(routes.ELIGIBILITY_START)) 

134 return super().get(request, *args, **kwargs) 

135 

136 def get_context_data(self, **kwargs): 

137 context = super().get_context_data(**kwargs) 

138 context.update(self.flow.eligibility_start_context) 

139 return context 

140 

141 

142class ConfirmView(AgencySessionRequiredMixin, FlowSessionRequiredMixin, RecaptchaEnabledMixin, FormView): 

143 """View handler for Eligiblity Confirm form, used only by flows that support Eligibility API verification.""" 

144 

145 template_name = "eligibility/confirm.html" 

146 

147 def get_form_class(self): 

148 agency_slug = self.agency.slug 

149 flow_system_name = self.flow.system_name 

150 

151 if agency_slug == AgencySlug.CST and flow_system_name == SystemName.AGENCY_CARD: 

152 form_class = forms.CSTAgencyCard 

153 elif agency_slug == AgencySlug.MST and flow_system_name == SystemName.COURTESY_CARD: 

154 form_class = forms.MSTCourtesyCard 

155 elif agency_slug == AgencySlug.SBMTD and flow_system_name == SystemName.REDUCED_FARE_MOBILITY_ID: 

156 form_class = forms.SBMTDMobilityPass 

157 else: 

158 raise ValueError( 

159 f"This agency/flow combination does not support Eligibility API verification: {agency_slug}, {flow_system_name}" # noqa 

160 ) 

161 

162 return form_class 

163 

164 def get(self, request, *args, **kwargs): 

165 if not session.eligible(request): 

166 session.update(request, origin=reverse(routes.ELIGIBILITY_CONFIRM)) 

167 return super().get(request, *args, **kwargs) 

168 else: 

169 # an already verified user, no need to verify again 

170 return redirect(routes.ENROLLMENT_INDEX) 

171 

172 def post(self, request, *args, **kwargs): 

173 analytics.started_eligibility(request, self.flow) 

174 return super().post(request, *args, **kwargs) 

175 

176 def form_valid(self, form): 

177 agency = self.agency 

178 flow = self.flow 

179 request = self.request 

180 

181 # make Eligibility Verification request to get the verified confirmation 

182 is_verified = verify.eligibility_from_api(flow, form, agency) 

183 

184 # Eligibility API returned errors (so eligibility is unknown), allow for correction/resubmission 

185 if is_verified is None: 

186 analytics.returned_error(request, flow, form.errors) 

187 return self.form_invalid(form) 

188 # Eligibility API returned that no type was verified 

189 elif not is_verified: 

190 return redirect(routes.ELIGIBILITY_UNVERIFIED) 

191 # Eligibility API returned that type was verified 

192 else: 

193 session.update(request, eligible=True) 

194 analytics.returned_success(request, flow) 

195 

196 return redirect(routes.ENROLLMENT_INDEX) 

197 

198 def form_invalid(self, form): 

199 if recaptcha.has_error(form): 

200 messages.error(self.request, "Recaptcha failed. Please try again.") 

201 

202 return self.get(self.request) 

203 

204 

205class UnverifiedView(AgencySessionRequiredMixin, FlowSessionRequiredMixin, TemplateView): 

206 """CBV for the unverified eligibility page.""" 

207 

208 template_name = "eligibility/unverified.html" 

209 

210 def get_context_data(self, **kwargs): 

211 context = super().get_context_data(**kwargs) 

212 context.update(self.flow.eligibility_unverified_context) 

213 return context 

214 

215 def get(self, request, *args, **kwargs): 

216 analytics.returned_fail(request, self.flow) 

217 return super().get(request, *args, **kwargs)