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

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.utils.translation import gettext_lazy as _ 

10from django.views.generic import RedirectView, TemplateView, View 

11from django.views.generic.edit import FormView 

12 

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 

20 

21 

22class IndexView(FormView): 

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

24 

25 template_name = "core/index.html" 

26 form_class = ChooseAgencyForm 

27 

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) 

33 

34 @method_decorator(pageview_decorator) 

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

36 session.reset(request) 

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

38 

39 

40@dataclass 

41class AgencyIndex: 

42 headline: str 

43 

44 def dict(self): 

45 return asdict(self) 

46 

47 

48class AgencyIndexView(TemplateView): 

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

50 

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

52 

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) 

59 

60 def get_context_data(self, **kwargs): 

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

62 agency = self.kwargs.get("agency") 

63 

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 } 

90 

91 context |= agency_index[agency.slug].dict() 

92 return context 

93 

94 

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

97 

98 pattern_name = routes.ELIGIBILITY_CONFIRM 

99 

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) 

108 

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) 

115 

116 

117class AgencyEligibilityIndexView(RedirectView): 

118 """View handler forwards the request to the agency's Eligibility Index (e.g. flow selection) page.""" 

119 

120 pattern_name = routes.ELIGIBILITY_INDEX 

121 

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) 

131 

132 

133class AgencyPublicKeyView(View): 

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

135 

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

142 

143 

144@dataclass 

145class FlowHelp: 

146 

147 id: str 

148 headline: str 

149 text: str 

150 

151 def dict(self): 

152 return asdict(self) 

153 

154 

155class HelpView(TemplateView): 

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

157 

158 template_name = "core/help.html" 

159 

160 @method_decorator(pageview_decorator) 

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

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

163 

164 def get_context_data(self, **kwargs): 

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

166 

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) 

172 

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) 

179 

180 context["flows_help"] = flows_help 

181 

182 return context 

183 

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 } 

285 

286 ctx = flows_help.get(flow.system_name) 

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

288 

289 

290class LoggedOutView(TemplateView): 

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

292 

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