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

1import logging 

2 

3from django.conf import settings 

4from django.forms import ValidationError 

5 

6from benefits.core import analytics 

7 

8from . import recaptcha, session 

9from .middleware import user_error 

10 

11logger = logging.getLogger(__name__) 

12 

13 

14class AgencySessionRequiredMixin: 

15 """Mixin intended for use with a class-based view as the first in the MRO. 

16 

17 Gets the active `TransitAgency` out of session and sets an attribute on `self`. 

18 

19 Allows inactive agencies ONLY if the user is an authenticated staff member, otherwise returns a user error. 

20 """ 

21 

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) 

30 

31 

32class EligibleSessionRequiredMixin: 

33 """Mixin intended for use with a class-based view as the first in the MRO. 

34 

35 If the session is not marked as eligible (e.g. the user has verified their eligibility), return a user error. 

36 """ 

37 

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) 

44 

45 

46class FlowSessionRequiredMixin: 

47 """Mixin intended for use with a class-based view as the first in the MRO. 

48 

49 Gets the current `EnrollmentFlow` out of session and sets an attribute on `self`. 

50 

51 If the session is not configured with a flow, return a user error. 

52 """ 

53 

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) 

64 

65 

66class PageViewMixin: 

67 """ 

68 Mixin sends an analytics event for page views. 

69 """ 

70 

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) 

79 

80 

81class RecaptchaEnabledMixin: 

82 """Mixin intended for use with a class-based view as the first in the MRO. 

83 

84 Configures the request with required reCAPTCHA settings.""" 

85 

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) 

94 

95 

96class ValidateRecaptchaMixin: 

97 """Mixin intended for use with forms where we need to verify reCAPTCHA. 

98 

99 Raises a ValidationError if it fails; otherwise continues the standard form verification. 

100 """ 

101 

102 def clean(self): 

103 if not recaptcha.verify(self.data): 

104 raise ValidationError("reCAPTCHA failed, please try again.") 

105 return super().clean()