Coverage for benefits/core/views.py: 90%
102 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-08 19:50 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-08 19:50 +0000
1"""
2The core application: view definition for the root of the webapp.
3"""
5import logging
6from dataclasses import asdict, dataclass
8from django.http import HttpResponse
9from django.utils import translation
10from django.utils.decorators import method_decorator
11from django.views.generic import RedirectView, TemplateView, View
12from django.views.generic.edit import FormView
14from benefits.core import models, session
15from benefits.core.context_processors import formatted_gettext_lazy as _
16from benefits.core.forms import ChooseAgencyForm
17from benefits.core.middleware import pageview_decorator, user_error
18from benefits.core.mixins import AgencySessionRequiredMixin
19from benefits.core.models import EligibilityApiVerificationRequest, SystemName
20from benefits.routes import routes
22logger = logging.getLogger(__name__)
25class IndexView(FormView):
26 """View handler for the main entry page."""
28 template_name = "core/index.html"
29 form_class = ChooseAgencyForm
31 # this form cannot use an action_url because the redirect is determined
32 # *after* user interaction
33 def form_valid(self, form):
34 agency = form.selected_transit_agency
35 session.reset(self.request)
36 session.update(self.request, agency=form.selected_transit_agency, origin=form.selected_transit_agency.index_url)
37 self.success_url = agency.entrypoint_url
38 return super().form_valid(form)
40 @method_decorator(pageview_decorator)
41 def get(self, request, *args, **kwargs):
42 session.reset(request)
43 language = translation.get_language()
44 logger.debug(f"Language: {language}")
45 return super().get(request, *args, **kwargs)
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")
63 short_name = agency.short_name
64 headline = _("Get your reduced fare when you tap to ride on {short_name}", short_name=short_name)
66 context |= {"headline": headline, "next_url": agency.entrypoint_url}
67 return context
70class AgencyCardView(RedirectView):
71 """View handler forwards the request to the agency's Agency Card (e.g. Eligibility API) flow, or returns a user error."""
73 pattern_name = routes.ELIGIBILITY_CONFIRM
75 @method_decorator(pageview_decorator)
76 def get(self, request, *args, **kwargs):
77 # keep a reference to the agency before removing from kwargs
78 # since the eventual reverse() lookup doesn't expect this key in the kwargs for routes.ELIGIBILITY_CONFIRM
79 # self.kwargs still contains the agency if needed
80 agency = kwargs.pop("agency")
81 session.reset(request)
82 session.update(request, agency=agency, origin=agency.index_url)
84 eligibility_api_flow = agency.enrollment_flows.exclude(api_request=None).order_by("id").last()
85 if eligibility_api_flow:
86 session.update(request, flow=eligibility_api_flow)
87 return super().get(request, *args, **kwargs)
88 else:
89 return user_error(request)
92class AgencyPublicKeyView(View):
93 """View handler returns an agency's public key as plain text."""
95 @method_decorator(pageview_decorator)
96 def get(self, request, *args, **kwargs):
97 # in the URL, a TransitAgency argument is required, but we just need to return the single
98 # EligibilityApiVerificationRequest client public key that is shared across all agencies
99 eligibility_api_public_key_data = EligibilityApiVerificationRequest.objects.first().client_public_key_data
100 return HttpResponse(eligibility_api_public_key_data, content_type="text/plain")
103@dataclass
104class FlowHelp:
106 id: str
107 headline: str
108 text: str
110 def dict(self):
111 return asdict(self)
114class HelpView(TemplateView):
115 """View handler for the help page."""
117 template_name = "core/help.html"
119 @method_decorator(pageview_decorator)
120 def get(self, request, *args, **kwargs):
121 return super().get(request, *args, **kwargs)
123 def get_context_data(self, **kwargs):
124 context = super().get_context_data(**kwargs)
126 if not session.active_agency(self.request):
127 choices = models.CardSchemes.CHOICES
128 context["all_card_schemes"] = [choices[card_scheme] for card_scheme in choices.keys()]
129 else:
130 agency = session.agency(self.request)
132 # build up a single list of all flow help contexts
133 flows_help = []
134 for flow in agency.enrollment_flows.all(): 134 ↛ 135line 134 didn't jump to line 135 because the loop on line 134 never started
135 help_contexts = self.get_help_contexts(flow)
136 if help_contexts:
137 flows_help.extend(help_contexts)
139 context["flows_help"] = flows_help
141 return context
143 def get_help_contexts(self, flow) -> list[dict]:
144 flows_help = {
145 SystemName.CALFRESH.value: [
146 FlowHelp(
147 id="calfresh-transit-benefit",
148 headline=_("How do I know if I’m eligible for the transit benefit for CalFresh Cardholders?"),
149 text=_(
150 "We verify your eligibility as a CalFresh Cardholder by confirming you have received funds in your "
151 "CalFresh account at any point in the last three months. This means you are eligible for a transit "
152 "benefit even if you did not receive funds in your CalFresh account this month or last month."
153 ),
154 ),
155 FlowHelp(
156 id="calfresh-transit-benefit-no-account-changes",
157 headline=_("Will this transit benefit change my CalFresh account?"),
158 text=_("No. Your monthly CalFresh allotment will not change."),
159 ),
160 FlowHelp(
161 id="calfresh-transit-benefit-enrollment",
162 headline=_("Do I need my Golden State Advantage card to enroll?"),
163 text=_(
164 "No, you do not need your physical EBT card to enroll. We use information from Login.gov and the "
165 "California Department of Social Services to enroll you in the benefit."
166 ),
167 ),
168 FlowHelp(
169 id="calfresh-transit-benefit-payment",
170 headline=_("Can I use my Golden State Advantage card to pay for transit rides?"),
171 text=_(
172 "No. You can not use your EBT or P-EBT card to pay for public transportation. "
173 "When you tap to ride, use your personal contactless debit or credit card to pay for public transportation." # noqa: E501
174 ),
175 ),
176 ],
177 SystemName.COURTESY_CARD.value: [
178 FlowHelp(
179 id="mst-agency-card",
180 headline=_("What is a Courtesy Card?"),
181 text=_(
182 "Monterey-Salinas Transit issues Courtesy Cards to riders who qualify for a number of reduced fare programs. " # noqa: E501
183 "This transit benefit may need to be renewed in the future based on the expiration date of the Courtesy Card. " # noqa: E501
184 '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
185 ),
186 )
187 ],
188 SystemName.MEDICARE.value: [
189 FlowHelp(
190 id="medicare-transit-benefit",
191 headline=_("How do I know if I qualify for the Medicare Cardholder option?"),
192 text=_(
193 "You qualify for this option if you have a Medicare card. To enroll you will need an account with Medicare.gov. " # noqa: E501
194 "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
195 ),
196 ),
197 FlowHelp(
198 id="medicare-transit-benefit-enrollment",
199 headline=_("Do I need my Medicare card to enroll?"),
200 text=_(
201 "No, you do not need your physical Medicare card to enroll in a transit benefit. "
202 "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
203 ),
204 ),
205 FlowHelp(
206 id="medicare-transit-benefit-payment",
207 headline=_("Do I need to bring my Medicare card when I ride public transportation?"),
208 text=_(
209 "No, you do not need your physical Medicare card to use your transit benefit on public transportation. " # noqa: E501
210 "Once you have enrolled you can use your contactless debit or credit card to tap to ride with a reduced fare." # noqa: E501
211 ),
212 ),
213 FlowHelp(
214 id="medicare-transit-benefit-recommended",
215 headline=_("What if I qualify for more than one option?"),
216 text=_(
217 "You can enroll in any option you qualify for. We recommend enrolling in the Medicare Cardholder option if you qualify for it." # noqa: 501
218 ),
219 ),
220 ],
221 SystemName.REDUCED_FARE_MOBILITY_ID.value: [
222 FlowHelp(
223 id="sbmtd-agency-card",
224 headline=_("What is a Reduced Fare Mobility ID?"),
225 text=_(
226 "The Santa Barbara Metropolitan Transit District issues Reduced Fare Mobility ID cards to eligible riders. " # noqa: E501
227 "This transit benefit may need to be renewed in the future based on the expiration date of the Reduced Fare Mobility ID. " # noqa: E501
228 'Learn more at the <a href="https://sbmtd.gov/fares-passes/" target="_blank" rel="noopener noreferrer">SBMTD Fares & Passes</a>.' # noqa: E501
229 ),
230 )
231 ],
232 }
234 ctx = flows_help.get(flow.system_name)
235 return [c.dict() for c in ctx] if ctx else []
238class LoggedOutView(TemplateView):
239 """View handler for the final log out confirmation message."""
241 template_name = "core/logged-out.html"
244class AdditionalAgenciesView(AgencySessionRequiredMixin, TemplateView):
245 """View handler for nearby/additional providers."""
247 template_name = "core/additional-agencies.html"
248 only_active_agencies = True
250 def get_context_data(self, **kwargs):
251 context = super().get_context_data(**kwargs)
252 agency = self.agency
253 agencies = agency.group_agency_short_names(only_active=self.only_active_agencies)
255 context |= {
256 "title": _("Nearby transit providers"),
257 "headline": _("Weʼll also enroll you at nearby transit providers"),
258 "blurb": _("Youʼll get reduced fares when you tap to pay at {count} transit providers.", count=len(agencies)),
259 "agencies": agencies,
260 }
262 return context