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
« 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"""
5from django.contrib import messages
6from django.shortcuts import redirect
7from django.urls import reverse
8from django.views.generic import TemplateView, FormView
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
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]
25 self.form_text = form_text
27 def dict(self):
28 return dict(form_text=self.form_text)
31class IndexView(AgencySessionRequiredMixin, RecaptchaEnabledMixin, FormView):
32 """View handler for the enrollment flow selection form."""
34 template_name = "eligibility/index.html"
35 form_class = forms.EnrollmentFlowSelectionForm
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
43 def get_context_data(self, **kwargs):
44 """Add agency-specific context data."""
45 context = super().get_context_data(**kwargs)
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 }
106 context.update(eligiblity_index[self.agency.slug].dict())
107 return context
109 def get(self, request, *args, **kwargs):
110 """Initialize session state before handling the request."""
112 session.update(request, eligible=False, origin=self.agency.index_url)
113 session.logout(request)
115 return super().get(request, *args, **kwargs)
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)
123 analytics.selected_flow(self.request, flow)
124 return redirect(routes.ELIGIBILITY_START)
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)
133class StartView(AgencySessionRequiredMixin, FlowSessionRequiredMixin, TemplateView):
134 """CBV for the eligibility verification getting started screen."""
136 template_name = "eligibility/start.html"
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)
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
148class ConfirmView(AgencySessionRequiredMixin, FlowSessionRequiredMixin, RecaptchaEnabledMixin, FormView):
149 """View handler for Eligiblity Confirm form, used only by flows that support Eligibility API verification."""
151 template_name = "eligibility/confirm.html"
153 def get_form_class(self):
154 agency_slug = self.agency.slug
155 flow_system_name = self.flow.system_name
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 )
168 return form_class
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)
178 def post(self, request, *args, **kwargs):
179 analytics.started_eligibility(request, self.flow)
180 return super().post(request, *args, **kwargs)
182 def form_valid(self, form):
183 agency = self.agency
184 flow = self.flow
185 request = self.request
187 # make Eligibility Verification request to get the verified confirmation
188 is_verified = verify.eligibility_from_api(flow, form, agency)
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)
202 return redirect(routes.ENROLLMENT_INDEX)
204 def form_invalid(self, form):
205 if recaptcha.has_error(form):
206 messages.error(self.request, "Recaptcha failed. Please try again.")
208 return self.get(self.request)
211class UnverifiedView(AgencySessionRequiredMixin, FlowSessionRequiredMixin, TemplateView):
212 """CBV for the unverified eligibility page."""
214 template_name = "eligibility/unverified.html"
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
221 def get(self, request, *args, **kwargs):
222 analytics.returned_fail(request, self.flow)
223 return super().get(request, *args, **kwargs)