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

1""" 

2The core application: view definition for the root of the webapp. 

3""" 

4 

5from dataclasses import asdict, dataclass 

6 

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 

11 

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 

19 

20 

21class IndexView(FormView): 

22 """View handler for the main entry page.""" 

23 

24 template_name = "core/index.html" 

25 form_class = ChooseAgencyForm 

26 

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) 

35 

36 @method_decorator(pageview_decorator) 

37 def get(self, request, *args, **kwargs): 

38 session.reset(request) 

39 return super().get(request, *args, **kwargs) 

40 

41 

42class AgencyIndexView(TemplateView): 

43 """View handler for an agency entry page.""" 

44 

45 template_name = "core/index--agency.html" 

46 

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) 

53 

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) 

59 

60 context |= {"headline": headline, "next_url": agency.entrypoint_url} 

61 return context 

62 

63 

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.""" 

66 

67 pattern_name = routes.ELIGIBILITY_CONFIRM 

68 

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) 

77 

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) 

84 

85 

86class AgencyPublicKeyView(View): 

87 """View handler returns an agency's public key as plain text.""" 

88 

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") 

95 

96 

97@dataclass 

98class FlowHelp: 

99 

100 id: str 

101 headline: str 

102 text: str 

103 

104 def dict(self): 

105 return asdict(self) 

106 

107 

108class HelpView(TemplateView): 

109 """View handler for the help page.""" 

110 

111 template_name = "core/help.html" 

112 

113 @method_decorator(pageview_decorator) 

114 def get(self, request, *args, **kwargs): 

115 return super().get(request, *args, **kwargs) 

116 

117 def get_context_data(self, **kwargs): 

118 context = super().get_context_data(**kwargs) 

119 

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) 

125 

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) 

132 

133 context["flows_help"] = flows_help 

134 

135 return context 

136 

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 } 

227 

228 ctx = flows_help.get(flow.system_name) 

229 return [c.dict() for c in ctx] if ctx else [] 

230 

231 

232class LoggedOutView(TemplateView): 

233 """View handler for the final log out confirmation message.""" 

234 

235 template_name = "core/logged-out.html" 

236 

237 

238class AdditionalAgenciesView(AgencySessionRequiredMixin, TemplateView): 

239 """View handler for nearby/additional providers.""" 

240 

241 template_name = "core/additional-agencies.html" 

242 

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() 

247 

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 } 

254 

255 return context