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

106 statements  

« prev     ^ index     » next       coverage.py v7.10.2, created at 2025-08-08 16:26 +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.template.response import TemplateResponse 

8from django.urls import reverse 

9from django.utils.decorators import decorator_from_middleware 

10from django.views.generic import RedirectView, TemplateView, FormView 

11 

12from benefits.routes import routes 

13from benefits.core import recaptcha, session 

14from benefits.core.context.agency import AgencySlug 

15from benefits.core.context import formatted_gettext_lazy as _ 

16from benefits.core.middleware import AgencySessionRequired, RecaptchaEnabled, FlowSessionRequired 

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

18from benefits.core.models import EnrollmentFlow 

19from . import analytics, forms, verify 

20 

21TEMPLATE_CONFIRM = "eligibility/confirm.html" 

22 

23 

24class EligibilityIndex: 

25 def __init__(self, form_text): 

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

27 form_text = [form_text] 

28 

29 self.form_text = form_text 

30 

31 def dict(self): 

32 return dict(form_text=self.form_text) 

33 

34 

35class IndexView(AgencySessionRequiredMixin, RecaptchaEnabledMixin, FormView): 

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

37 

38 template_name = "eligibility/index.html" 

39 form_class = forms.EnrollmentFlowSelectionForm 

40 

41 def get_form_kwargs(self): 

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

43 kwargs = super().get_form_kwargs() 

44 kwargs["agency"] = self.agency 

45 return kwargs 

46 

47 def get_context_data(self, **kwargs): 

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

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

50 

51 eligiblity_index = { 

52 AgencySlug.CST.value: EligibilityIndex( 

53 form_text=_( 

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

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

56 ) 

57 ), 

58 AgencySlug.MST.value: EligibilityIndex( 

59 form_text=_( 

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

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

62 ) 

63 ), 

64 AgencySlug.NEVCO.value: EligibilityIndex( 

65 form_text=_( 

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

67 "All Nevada County Connects transit benefits reduce fares " 

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

69 ) 

70 ), 

71 AgencySlug.SACRT.value: EligibilityIndex( 

72 form_text=_( 

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

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

75 ) 

76 ), 

77 AgencySlug.SBMTD.value: EligibilityIndex( 

78 form_text=_( 

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

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

81 ) 

82 ), 

83 AgencySlug.VCTC.value: EligibilityIndex( 

84 form_text=_( 

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

86 "All Ventura County Transportation Commission transit benefits " 

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

88 ) 

89 ), 

90 } 

91 

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

93 return context 

94 

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

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

97 

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

99 session.logout(request) 

100 

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

102 

103 def form_valid(self, form): 

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

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

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

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

108 

109 analytics.selected_flow(self.request, flow) 

110 return redirect(routes.ELIGIBILITY_START) 

111 

112 def form_invalid(self, form): 

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

114 if recaptcha.has_error(form): 

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

116 return super().form_invalid(form) 

117 

118 

119class StartView(AgencySessionRequiredMixin, FlowSessionRequiredMixin, TemplateView): 

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

121 

122 template_name = "eligibility/start.html" 

123 

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

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

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

127 

128 def get_context_data(self, **kwargs): 

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

130 context.update(self.flow.eligibility_start_context) 

131 return context 

132 

133 

134@decorator_from_middleware(AgencySessionRequired) 

135@decorator_from_middleware(RecaptchaEnabled) 

136@decorator_from_middleware(FlowSessionRequired) 

137def confirm(request): 

138 """View handler for the eligibility verification form.""" 

139 

140 verified_view = VerifiedView() 

141 

142 # GET from an already verified user, no need to verify again 

143 if request.method == "GET" and session.eligible(request): 

144 return verified_view.setup_and_dispatch(request) 

145 

146 agency = session.agency(request) 

147 flow = session.flow(request) 

148 

149 form = flow.eligibility_form_instance() 

150 

151 # GET/POST for Eligibility API verification 

152 context = {"form": form} 

153 

154 # GET from an unverified user, present the form 

155 if request.method == "GET": 

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

157 return TemplateResponse(request, TEMPLATE_CONFIRM, context) 

158 # POST form submission, process form data, make Eligibility Verification API call 

159 elif request.method == "POST": 159 ↛ exitline 159 didn't return from function 'confirm' because the condition on line 159 was always true

160 analytics.started_eligibility(request, flow) 

161 

162 form = flow.eligibility_form_instance(data=request.POST) 

163 # form was not valid, allow for correction/resubmission 

164 if not form.is_valid(): 

165 if recaptcha.has_error(form): 

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

167 context["form"] = form 

168 return TemplateResponse(request, TEMPLATE_CONFIRM, context) 

169 

170 # form is valid, make Eligibility Verification request to get the verified confirmation 

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

172 

173 # form was not valid, allow for correction/resubmission 

174 if is_verified is None: 

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

176 context["form"] = form 

177 return TemplateResponse(request, TEMPLATE_CONFIRM, context) 

178 # no type was verified 

179 elif not is_verified: 

180 return redirect(routes.ELIGIBILITY_UNVERIFIED) 

181 # type was verified 

182 else: 

183 return verified_view.setup_and_dispatch(request) 

184 

185 

186class VerifiedView(AgencySessionRequiredMixin, FlowSessionRequiredMixin, RedirectView): 

187 """CBV for verified eligibility. 

188 

189 Note we do not register a URL for this view, as it should only be used 

190 after the user's eligibility is verified and not generally accessible. 

191 

192 GET requests simply forward along as part of the RedirectView logic. 

193 

194 POST requests represent a new verification success, triggering additional logic. 

195 

196 `setup_and_dispatch(request)` is a helper for external callers. 

197 """ 

198 

199 def get_redirect_url(self, *args, **kwargs): 

200 return reverse(routes.ENROLLMENT_INDEX) 

201 

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

203 session.update(request, eligible=True) 

204 analytics.returned_success(request, self.flow) 

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

206 

207 def setup_and_dispatch(self, request, *args, **kwargs): 

208 self.setup(request) 

209 return self.dispatch(request, *args, **kwargs) 

210 

211 

212class UnverifiedView(AgencySessionRequiredMixin, FlowSessionRequiredMixin, TemplateView): 

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

214 

215 template_name = "eligibility/unverified.html" 

216 

217 def get_context_data(self, **kwargs): 

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

219 context.update(self.flow.eligibility_unverified_context) 

220 return context 

221 

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

223 analytics.returned_fail(request, self.flow) 

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