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

1""" 

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

3""" 

4 

5import logging 

6from dataclasses import asdict, dataclass 

7 

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 

13 

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 

21 

22logger = logging.getLogger(__name__) 

23 

24 

25class IndexView(FormView): 

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

27 

28 template_name = "core/index.html" 

29 form_class = ChooseAgencyForm 

30 

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) 

39 

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) 

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 short_name = agency.short_name 

64 headline = _("Get your reduced fare when you tap to ride on {short_name}", short_name=short_name) 

65 

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

67 return context 

68 

69 

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

72 

73 pattern_name = routes.ELIGIBILITY_CONFIRM 

74 

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) 

83 

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) 

90 

91 

92class AgencyPublicKeyView(View): 

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

94 

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

101 

102 

103@dataclass 

104class FlowHelp: 

105 

106 id: str 

107 headline: str 

108 text: str 

109 

110 def dict(self): 

111 return asdict(self) 

112 

113 

114class HelpView(TemplateView): 

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

116 

117 template_name = "core/help.html" 

118 

119 @method_decorator(pageview_decorator) 

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

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

122 

123 def get_context_data(self, **kwargs): 

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

125 

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) 

131 

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) 

138 

139 context["flows_help"] = flows_help 

140 

141 return context 

142 

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 } 

233 

234 ctx = flows_help.get(flow.system_name) 

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

236 

237 

238class LoggedOutView(TemplateView): 

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

240 

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

242 

243 

244class AdditionalAgenciesView(AgencySessionRequiredMixin, TemplateView): 

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

246 

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

248 only_active_agencies = True 

249 

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) 

254 

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 } 

261 

262 return context