Coverage for benefits / core / mixins.py: 97%

48 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-01 15:39 +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 If the session is not configured with an agency, return a user error. 

20 """ 

21 

22 def dispatch(self, request, *args, **kwargs): 

23 if session.active_agency(request): 

24 self.agency = session.agency(request) 

25 return super().dispatch(request, *args, **kwargs) 

26 else: 

27 logger.warning("Session not configured with an active agency") 

28 return user_error(request) 

29 

30 

31class EligibleSessionRequiredMixin: 

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

33 

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

35 """ 

36 

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

38 if session.eligible(request): 

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

40 else: 

41 logger.warning("Session does not have verified eligibility") 

42 return user_error(request) 

43 

44 

45class FlowSessionRequiredMixin: 

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

47 

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

49 

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

51 """ 

52 

53 def dispatch(self, request, *args, **kwargs): 

54 flow = session.flow(request) 

55 if flow: 

56 self.flow = flow 

57 self.agency = session.agency(request) 

58 self.group = session.group(request) 

59 return super().dispatch(request, *args, **kwargs) 

60 else: 

61 logger.warning("Session not configured with enrollment flow") 

62 return user_error(request) 

63 

64 

65class PageViewMixin: 

66 """ 

67 Mixin sends an analytics event for page views. 

68 """ 

69 

70 def dispatch(self, request, *args, **kwargs): 

71 event = analytics.ViewedPageEvent(request) 

72 try: 

73 analytics.send_event(event) 

74 except Exception: 

75 logger.warning(f"Failed to send event: {event}") 

76 finally: 

77 return super().dispatch(request, *args, **kwargs) 

78 

79 

80class RecaptchaEnabledMixin: 

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

82 

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

84 

85 def dispatch(self, request, *args, **kwargs): 

86 if settings.RECAPTCHA_ENABLED: 

87 request.recaptcha = { 

88 "data_field": recaptcha.DATA_FIELD, 

89 "script_api": settings.RECAPTCHA_API_KEY_URL, 

90 "site_key": settings.RECAPTCHA_SITE_KEY, 

91 } 

92 return super().dispatch(request, *args, **kwargs) 

93 

94 

95class ValidateRecaptchaMixin: 

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

97 

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

99 """ 

100 

101 def clean(self): 

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

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

104 return super().clean()