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

101 statements  

« prev     ^ index     » next       coverage.py v7.13.0, created at 2025-12-22 19:08 +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.SLORTA.value: EligibilityIndex( 

92 form_text=_( 

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

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

95 ) 

96 ), 

97 AgencySlug.VCTC.value: EligibilityIndex( 

98 form_text=_( 

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

100 "All Ventura County Transportation Commission transit benefits " 

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

102 ) 

103 ), 

104 } 

105 

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

107 return context 

108 

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

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

111 

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

113 session.logout(request) 

114 

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

116 

117 def form_valid(self, form): 

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

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

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

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

122 

123 analytics.selected_flow(self.request, flow) 

124 return redirect(routes.ELIGIBILITY_START) 

125 

126 def form_invalid(self, form): 

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

128 if recaptcha.has_error(form): 

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

130 return super().form_invalid(form) 

131 

132 

133class StartView(AgencySessionRequiredMixin, FlowSessionRequiredMixin, TemplateView): 

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

135 

136 template_name = "eligibility/start.html" 

137 

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

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

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

141 

142 def get_context_data(self, **kwargs): 

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

144 context.update(self.flow.eligibility_start_context) 

145 return context 

146 

147 

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

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

150 

151 template_name = "eligibility/confirm.html" 

152 

153 def get_form_class(self): 

154 agency_slug = self.agency.slug 

155 flow_system_name = self.flow.system_name 

156 

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

158 form_class = forms.CSTAgencyCard 

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

160 form_class = forms.MSTCourtesyCard 

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

162 form_class = forms.SBMTDMobilityPass 

163 else: 

164 raise ValueError( 

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

166 ) 

167 

168 return form_class 

169 

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

171 if not session.eligible(request): 

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

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

174 else: 

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

176 return redirect(routes.ENROLLMENT_INDEX) 

177 

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

179 analytics.started_eligibility(request, self.flow) 

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

181 

182 def form_valid(self, form): 

183 agency = self.agency 

184 flow = self.flow 

185 request = self.request 

186 

187 # make Eligibility Verification request to get the verified confirmation 

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

189 

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

191 if is_verified is None: 

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

193 return self.form_invalid(form) 

194 # Eligibility API returned that no type was verified 

195 elif not is_verified: 

196 return redirect(routes.ELIGIBILITY_UNVERIFIED) 

197 # Eligibility API returned that type was verified 

198 else: 

199 session.update(request, eligible=True) 

200 analytics.returned_success(request, flow) 

201 

202 return redirect(routes.ENROLLMENT_INDEX) 

203 

204 def form_invalid(self, form): 

205 if recaptcha.has_error(form): 

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

207 

208 return self.get(self.request) 

209 

210 

211class UnverifiedView(AgencySessionRequiredMixin, FlowSessionRequiredMixin, TemplateView): 

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

213 

214 template_name = "eligibility/unverified.html" 

215 

216 def get_context_data(self, **kwargs): 

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

218 context.update(self.flow.eligibility_unverified_context) 

219 return context 

220 

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

222 analytics.returned_fail(request, self.flow) 

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