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

101 statements  

« prev     ^ index     » next       coverage.py v7.10.6, created at 2025-09-12 23:32 +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.SACRT.value: EligibilityIndex( 

74 form_text=_( 

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

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

77 ) 

78 ), 

79 AgencySlug.SBMTD.value: EligibilityIndex( 

80 form_text=_( 

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

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

83 ) 

84 ), 

85 AgencySlug.VCTC.value: EligibilityIndex( 

86 form_text=_( 

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

88 "All Ventura County Transportation Commission transit benefits " 

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

90 ) 

91 ), 

92 } 

93 

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

95 return context 

96 

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

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

99 

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

101 session.logout(request) 

102 

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

104 

105 def form_valid(self, form): 

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

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

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

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

110 

111 analytics.selected_flow(self.request, flow) 

112 return redirect(routes.ELIGIBILITY_START) 

113 

114 def form_invalid(self, form): 

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

116 if recaptcha.has_error(form): 

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

118 return super().form_invalid(form) 

119 

120 

121class StartView(AgencySessionRequiredMixin, FlowSessionRequiredMixin, TemplateView): 

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

123 

124 template_name = "eligibility/start.html" 

125 

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

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

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

129 

130 def get_context_data(self, **kwargs): 

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

132 context.update(self.flow.eligibility_start_context) 

133 return context 

134 

135 

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

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

138 

139 template_name = "eligibility/confirm.html" 

140 

141 def get_form_class(self): 

142 agency_slug = self.agency.slug 

143 flow_system_name = self.flow.system_name 

144 

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

146 form_class = forms.CSTAgencyCard 

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

148 form_class = forms.MSTCourtesyCard 

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

150 form_class = forms.SBMTDMobilityPass 

151 else: 

152 raise ValueError( 

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

154 ) 

155 

156 return form_class 

157 

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

159 if not session.eligible(request): 

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

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

162 else: 

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

164 return redirect(routes.ENROLLMENT_INDEX) 

165 

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

167 analytics.started_eligibility(request, self.flow) 

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

169 

170 def form_valid(self, form): 

171 agency = self.agency 

172 flow = self.flow 

173 request = self.request 

174 

175 # make Eligibility Verification request to get the verified confirmation 

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

177 

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

179 if is_verified is None: 

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

181 return self.form_invalid(form) 

182 # Eligibility API returned that no type was verified 

183 elif not is_verified: 

184 return redirect(routes.ELIGIBILITY_UNVERIFIED) 

185 # Eligibility API returned that type was verified 

186 else: 

187 session.update(request, eligible=True) 

188 analytics.returned_success(request, flow) 

189 

190 return redirect(routes.ENROLLMENT_INDEX) 

191 

192 def form_invalid(self, form): 

193 if recaptcha.has_error(form): 

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

195 

196 return self.get(self.request) 

197 

198 

199class UnverifiedView(AgencySessionRequiredMixin, FlowSessionRequiredMixin, TemplateView): 

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

201 

202 template_name = "eligibility/unverified.html" 

203 

204 def get_context_data(self, **kwargs): 

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

206 context.update(self.flow.eligibility_unverified_context) 

207 return context 

208 

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

210 analytics.returned_fail(request, self.flow) 

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