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
« 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"""
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
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
21TEMPLATE_CONFIRM = "eligibility/confirm.html"
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]
29 self.form_text = form_text
31 def dict(self):
32 return dict(form_text=self.form_text)
35class IndexView(AgencySessionRequiredMixin, RecaptchaEnabledMixin, FormView):
36 """View handler for the enrollment flow selection form."""
38 template_name = "eligibility/index.html"
39 form_class = forms.EnrollmentFlowSelectionForm
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
47 def get_context_data(self, **kwargs):
48 """Add agency-specific context data."""
49 context = super().get_context_data(**kwargs)
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 }
92 context.update(eligiblity_index[self.agency.slug].dict())
93 return context
95 def get(self, request, *args, **kwargs):
96 """Initialize session state before handling the request."""
98 session.update(request, eligible=False, origin=self.agency.index_url)
99 session.logout(request)
101 return super().get(request, *args, **kwargs)
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)
109 analytics.selected_flow(self.request, flow)
110 return redirect(routes.ELIGIBILITY_START)
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)
119class StartView(AgencySessionRequiredMixin, FlowSessionRequiredMixin, TemplateView):
120 """CBV for the eligibility verification getting started screen."""
122 template_name = "eligibility/start.html"
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)
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
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."""
140 verified_view = VerifiedView()
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)
146 agency = session.agency(request)
147 flow = session.flow(request)
149 form = flow.eligibility_form_instance()
151 # GET/POST for Eligibility API verification
152 context = {"form": form}
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)
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)
170 # form is valid, make Eligibility Verification request to get the verified confirmation
171 is_verified = verify.eligibility_from_api(flow, form, agency)
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)
186class VerifiedView(AgencySessionRequiredMixin, FlowSessionRequiredMixin, RedirectView):
187 """CBV for verified eligibility.
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.
192 GET requests simply forward along as part of the RedirectView logic.
194 POST requests represent a new verification success, triggering additional logic.
196 `setup_and_dispatch(request)` is a helper for external callers.
197 """
199 def get_redirect_url(self, *args, **kwargs):
200 return reverse(routes.ENROLLMENT_INDEX)
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)
207 def setup_and_dispatch(self, request, *args, **kwargs):
208 self.setup(request)
209 return self.dispatch(request, *args, **kwargs)
212class UnverifiedView(AgencySessionRequiredMixin, FlowSessionRequiredMixin, TemplateView):
213 """CBV for the unverified eligibility page."""
215 template_name = "eligibility/unverified.html"
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
222 def get(self, request, *args, **kwargs):
223 analytics.returned_fail(request, self.flow)
224 return super().get(request, *args, **kwargs)