Coverage for benefits/core/mixins.py: 97%
49 statements
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-07 00:01 +0000
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-07 00:01 +0000
1import logging
3from django.conf import settings
4from django.forms import ValidationError
6from benefits.core import analytics
8from . import recaptcha, session
9from .middleware import user_error
11logger = logging.getLogger(__name__)
14class AgencySessionRequiredMixin:
15 """Mixin intended for use with a class-based view as the first in the MRO.
17 Gets the active `TransitAgency` out of session and sets an attribute on `self`.
19 Allows inactive agencies ONLY if the user is an authenticated staff member, otherwise returns a user error.
20 """
22 def dispatch(self, request, *args, **kwargs):
23 agency = session.agency(request)
24 if agency and (agency.active or getattr(request.user, "is_staff", False)):
25 self.agency = agency
26 return super().dispatch(request, *args, **kwargs)
27 else:
28 logger.warning("Session missing an agency, or user lacks permission to access an inactive agency")
29 return user_error(request)
32class EligibleSessionRequiredMixin:
33 """Mixin intended for use with a class-based view as the first in the MRO.
35 If the session is not marked as eligible (e.g. the user has verified their eligibility), return a user error.
36 """
38 def dispatch(self, request, *args, **kwargs):
39 if session.eligible(request):
40 return super().dispatch(request, *args, **kwargs)
41 else:
42 logger.warning("Session does not have verified eligibility")
43 return user_error(request)
46class FlowSessionRequiredMixin:
47 """Mixin intended for use with a class-based view as the first in the MRO.
49 Gets the current `EnrollmentFlow` out of session and sets an attribute on `self`.
51 If the session is not configured with a flow, return a user error.
52 """
54 def dispatch(self, request, *args, **kwargs):
55 flow = session.flow(request)
56 if flow:
57 self.flow = flow
58 self.agency = session.agency(request)
59 self.group = session.group(request)
60 return super().dispatch(request, *args, **kwargs)
61 else:
62 logger.warning("Session not configured with enrollment flow")
63 return user_error(request)
66class PageViewMixin:
67 """
68 Mixin sends an analytics event for page views.
69 """
71 def dispatch(self, request, *args, **kwargs):
72 event = analytics.ViewedPageEvent(request)
73 try:
74 analytics.send_event(event)
75 except Exception:
76 logger.warning(f"Failed to send event: {event}")
77 finally:
78 return super().dispatch(request, *args, **kwargs)
81class RecaptchaEnabledMixin:
82 """Mixin intended for use with a class-based view as the first in the MRO.
84 Configures the request with required reCAPTCHA settings."""
86 def dispatch(self, request, *args, **kwargs):
87 if settings.RECAPTCHA_ENABLED:
88 request.recaptcha = {
89 "data_field": recaptcha.DATA_FIELD,
90 "script_api": settings.RECAPTCHA_API_KEY_URL,
91 "site_key": settings.RECAPTCHA_SITE_KEY,
92 }
93 return super().dispatch(request, *args, **kwargs)
96class ValidateRecaptchaMixin:
97 """Mixin intended for use with forms where we need to verify reCAPTCHA.
99 Raises a ValidationError if it fails; otherwise continues the standard form verification.
100 """
102 def clean(self):
103 if not recaptcha.verify(self.data):
104 raise ValidationError("reCAPTCHA failed, please try again.")
105 return super().clean()