Coverage for benefits / core / views.py: 90%
98 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-02-13 19:35 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-02-13 19:35 +0000
1"""
2The core application: view definition for the root of the webapp.
3"""
5from dataclasses import asdict, dataclass
7from django.http import HttpResponse
8from django.utils.decorators import method_decorator
9from django.utils.translation import gettext_lazy as _
10from django.views.generic import RedirectView, TemplateView, View
11from django.views.generic.edit import FormView
13from benefits.core import models, session
14from benefits.core.context import SystemName
15from benefits.core.forms import ChooseAgencyForm
16from benefits.core.middleware import pageview_decorator, user_error
17from benefits.core.models import AgencySlug
18from benefits.core.models.enrollment import EligibilityApiVerificationRequest
19from benefits.routes import routes
22class IndexView(FormView):
23 """View handler for the main entry page."""
25 template_name = "core/index.html"
26 form_class = ChooseAgencyForm
28 # this form cannot use an action_url because the redirect is determined
29 # *after* user interaction
30 def form_valid(self, form):
31 self.success_url = form.selected_transit_agency.eligibility_index_url
32 return super().form_valid(form)
34 @method_decorator(pageview_decorator)
35 def get(self, request, *args, **kwargs):
36 session.reset(request)
37 return super().get(request, *args, **kwargs)
40@dataclass
41class AgencyIndex:
42 headline: str
44 def dict(self):
45 return asdict(self)
48class AgencyIndexView(TemplateView):
49 """View handler for an agency entry page."""
51 template_name = "core/index--agency.html"
53 @method_decorator(pageview_decorator)
54 def get(self, request, *args, **kwargs):
55 agency = self.kwargs.get("agency")
56 session.reset(request)
57 session.update(request, agency=agency, origin=agency.index_url)
58 return super().get(request, *args, **kwargs)
60 def get_context_data(self, **kwargs):
61 context = super().get_context_data(**kwargs)
62 agency = self.kwargs.get("agency")
64 agency_index = {
65 AgencySlug.CST.value: AgencyIndex(headline=_("Get your reduced fare on CST public transit when you tap to ride")),
66 AgencySlug.EDCTA.value: AgencyIndex(
67 headline=_("Get your reduced fare on EDCTA public transit when you tap to ride")
68 ),
69 AgencySlug.MST.value: AgencyIndex(headline=_("Get your reduced fare on MST public transit when you tap to ride")),
70 AgencySlug.NEVCO.value: AgencyIndex(
71 headline=_("Get your reduced fare on Nevada County Connects public transit when you tap to ride")
72 ),
73 AgencySlug.RABA.value: AgencyIndex(
74 headline=_("Get your reduced fare on RABA public transit when you tap to ride")
75 ),
76 AgencySlug.ROSEVILLE.value: AgencyIndex(
77 headline=_("Get your reduced fare on Roseville public transit when you tap to ride")
78 ),
79 AgencySlug.SACRT.value: AgencyIndex(headline=_("Get your reduced fare on SacRT buses when you tap to ride")),
80 AgencySlug.SBMTD.value: AgencyIndex(
81 headline=_("Get your reduced fare on Santa Barbara MTD buses when you tap to ride")
82 ),
83 AgencySlug.SLORTA.value: AgencyIndex(
84 headline=_("Get your reduced fare on San Luis Obispo RTA buses when you tap to ride")
85 ),
86 AgencySlug.VCTC.value: AgencyIndex(
87 headline=_("Get your reduced fare on Ventura County Transportation Commission buses when you tap to ride")
88 ),
89 }
91 context |= agency_index[agency.slug].dict()
92 return context
95class AgencyCardView(RedirectView):
96 """View handler forwards the request to the agency's Agency Card (e.g. Eligibility API) flow, or returns a user error."""
98 pattern_name = routes.ELIGIBILITY_CONFIRM
100 @method_decorator(pageview_decorator)
101 def get(self, request, *args, **kwargs):
102 # keep a reference to the agency before removing from kwargs
103 # since the eventual reverse() lookup doesn't expect this key in the kwargs for routes.ELIGIBILITY_CONFIRM
104 # self.kwargs still contains the agency if needed
105 agency = kwargs.pop("agency")
106 session.reset(request)
107 session.update(request, agency=agency, origin=agency.index_url)
109 eligibility_api_flow = agency.enrollment_flows.exclude(api_request=None).order_by("id").last()
110 if eligibility_api_flow:
111 session.update(request, flow=eligibility_api_flow)
112 return super().get(request, *args, **kwargs)
113 else:
114 return user_error(request)
117class AgencyEligibilityIndexView(RedirectView):
118 """View handler forwards the request to the agency's Eligibility Index (e.g. flow selection) page."""
120 pattern_name = routes.ELIGIBILITY_INDEX
122 @method_decorator(pageview_decorator)
123 def get(self, request, *args, **kwargs):
124 # keep a reference to the agency before removing from kwargs
125 # since the eventual reverse() lookup doesn't expect this key in the kwargs for routes.ELIGIBILITY_INDEX
126 # self.kwargs still contains the agency if needed
127 agency = kwargs.pop("agency")
128 session.reset(request)
129 session.update(request, agency=agency, origin=agency.index_url)
130 return super().get(request, *args, **kwargs)
133class AgencyPublicKeyView(View):
134 """View handler returns an agency's public key as plain text."""
136 @method_decorator(pageview_decorator)
137 def get(self, request, *args, **kwargs):
138 # in the URL, a TransitAgency argument is required, but we just need to return the single
139 # EligibilityApiVerificationRequest client public key that is shared across all agencies
140 eligibility_api_public_key_data = EligibilityApiVerificationRequest.objects.first().client_public_key_data
141 return HttpResponse(eligibility_api_public_key_data, content_type="text/plain")
144@dataclass
145class FlowHelp:
147 id: str
148 headline: str
149 text: str
151 def dict(self):
152 return asdict(self)
155class HelpView(TemplateView):
156 """View handler for the help page."""
158 template_name = "core/help.html"
160 @method_decorator(pageview_decorator)
161 def get(self, request, *args, **kwargs):
162 return super().get(request, *args, **kwargs)
164 def get_context_data(self, **kwargs):
165 context = super().get_context_data(**kwargs)
167 if not session.active_agency(self.request):
168 choices = models.CardSchemes.CHOICES
169 context["all_card_schemes"] = [choices[card_scheme] for card_scheme in choices.keys()]
170 else:
171 agency = session.agency(self.request)
173 # build up a single list of all flow help contexts
174 flows_help = []
175 for flow in agency.enrollment_flows.all(): 175 ↛ 176line 175 didn't jump to line 176 because the loop on line 175 never started
176 help_contexts = self.get_help_contexts(flow)
177 if help_contexts:
178 flows_help.extend(help_contexts)
180 context["flows_help"] = flows_help
182 return context
184 def get_help_contexts(self, flow) -> list[dict]:
185 flows_help = {
186 SystemName.AGENCY_CARD.value: [
187 FlowHelp(
188 id="cst-agency-card",
189 headline=_("What is an Agency Card?"),
190 text=_(
191 "California State Transit issues Agency Cards to riders who qualify for a number of reduced fare "
192 "programs. This transit benefit may need to be renewed in the future based on the expiration date of the " # noqa: E501
193 'Agency Card. Learn more at the <a href="https://www.agency-website.com" target="_blank" rel="noopener noreferrer">www.agency-website.com</a>.' # noqa: E501
194 ),
195 )
196 ],
197 SystemName.CALFRESH.value: [
198 FlowHelp(
199 id="calfresh-transit-benefit",
200 headline=_("How do I know if I’m eligible for the transit benefit for CalFresh Cardholders?"),
201 text=_(
202 "We verify your eligibility as a CalFresh Cardholder by confirming you have received funds in your "
203 "CalFresh account at any point in the last three months. This means you are eligible for a transit "
204 "benefit even if you did not receive funds in your CalFresh account this month or last month."
205 ),
206 ),
207 FlowHelp(
208 id="calfresh-transit-benefit-no-account-changes",
209 headline=_("Will this transit benefit change my CalFresh account?"),
210 text=_("No. Your monthly CalFresh allotment will not change."),
211 ),
212 FlowHelp(
213 id="calfresh-transit-benefit-enrollment",
214 headline=_("Do I need my Golden State Advantage card to enroll?"),
215 text=_(
216 "No, you do not need your physical EBT card to enroll. We use information from Login.gov and the "
217 "California Department of Social Services to enroll you in the benefit."
218 ),
219 ),
220 FlowHelp(
221 id="calfresh-transit-benefit-payment",
222 headline=_("Can I use my Golden State Advantage card to pay for transit rides?"),
223 text=_(
224 "No. You can not use your EBT or P-EBT card to pay for public transportation. "
225 "When you tap to ride, use your personal contactless debit or credit card to pay for public transportation." # noqa: E501
226 ),
227 ),
228 ],
229 SystemName.COURTESY_CARD.value: [
230 FlowHelp(
231 id="mst-agency-card",
232 headline=_("What is a Courtesy Card?"),
233 text=_(
234 "Monterey-Salinas Transit issues Courtesy Cards to riders who qualify for a number of reduced fare programs. " # noqa: E501
235 "This transit benefit may need to be renewed in the future based on the expiration date of the Courtesy Card. " # noqa: E501
236 'Learn more at the <a href="https://mst.org/riders-guide/how-to-ride/courtesy-card/" target="_blank" rel="noopener noreferrer">MST Riders Guide</a>.' # noqa: E501
237 ),
238 )
239 ],
240 SystemName.MEDICARE.value: [
241 FlowHelp(
242 id="medicare-transit-benefit",
243 headline=_("How do I know if I qualify for the Medicare Cardholder option?"),
244 text=_(
245 "You qualify for this option if you have a Medicare card. To enroll you will need an account with Medicare.gov. " # noqa: E501
246 "You will need to sign up for a Medicare.gov account if you do not currently have one. Deceased Medicare cardholders do not qualify." # noqa: E501
247 ),
248 ),
249 FlowHelp(
250 id="medicare-transit-benefit-enrollment",
251 headline=_("Do I need my Medicare card to enroll?"),
252 text=_(
253 "No, you do not need your physical Medicare card to enroll in a transit benefit. "
254 "You will need the information on your card to create an account at Medicare.gov if you do not currently have an online account." # noqa: E501
255 ),
256 ),
257 FlowHelp(
258 id="medicare-transit-benefit-payment",
259 headline=_("Do I need to bring my Medicare card when I ride public transportation?"),
260 text=_(
261 "No, you do not need your physical Medicare card to use your transit benefit on public transportation. " # noqa: E501
262 "Once you have enrolled you can use your contactless debit or credit card to tap to ride with a reduced fare." # noqa: E501
263 ),
264 ),
265 FlowHelp(
266 id="medicare-transit-benefit-recommended",
267 headline=_("What if I qualify for more than one option?"),
268 text=_(
269 "You can enroll in any option you qualify for. We recommend enrolling in the Medicare Cardholder option if you qualify for it." # noqa: 501
270 ),
271 ),
272 ],
273 SystemName.REDUCED_FARE_MOBILITY_ID.value: [
274 FlowHelp(
275 id="sbmtd-agency-card",
276 headline=_("What is a Reduced Fare Mobility ID?"),
277 text=_(
278 "The Santa Barbara Metropolitan Transit District issues Reduced Fare Mobility ID cards to eligible riders. " # noqa: E501
279 "This transit benefit may need to be renewed in the future based on the expiration date of the Reduced Fare Mobility ID. " # noqa: E501
280 'Learn more at the <a href="https://sbmtd.gov/fares-passes/" target="_blank" rel="noopener noreferrer">SBMTD Fares & Passes</a>.' # noqa: E501
281 ),
282 )
283 ],
284 }
286 ctx = flows_help.get(flow.system_name)
287 return [c.dict() for c in ctx] if ctx else []
290class LoggedOutView(TemplateView):
291 """View handler for the final log out confirmation message."""
293 template_name = "core/logged-out.html"