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