Coverage for benefits/core/admin/enrollment.py: 97%

56 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-08 19:50 +0000

1from adminsortable2.admin import SortableAdminMixin 

2from django import forms 

3from django.contrib import admin 

4from django.core.exceptions import ValidationError 

5 

6from benefits.core import models 

7from benefits.core.models.common import template_path 

8from benefits.core.models.enrollment import EnrollmentFlow 

9 

10from .mixins import ProdReadOnlyPermissionMixin, StaffPermissionMixin, SuperuserPermissionMixin 

11 

12 

13@admin.register(models.EnrollmentEvent) 

14class EnrollmentEventAdmin(ProdReadOnlyPermissionMixin, admin.ModelAdmin): 

15 list_display = ("enrollment_datetime", "transit_agency", "enrollment_flow", "enrollment_method", "verified_by") 

16 ordering = ("-enrollment_datetime",) 

17 

18 

19@admin.register(models.EligibilityApiVerificationRequest) 

20class EligibilityApiVerificationRequestAdmin(SuperuserPermissionMixin, admin.ModelAdmin): 

21 list_display = ("label", "api_url") 

22 

23 

24class EnrollmentFlowForm(forms.ModelForm): 

25 def has_field(self, field_name): 

26 return self.fields.get(field_name) is not None 

27 

28 def get(self, cleaned_data, field_name): 

29 """ 

30 If the field is present on the form, simply get the value from the cleaned_data. 

31 

32 If the field is not present on the form, that means the user doesn't have access to the field, 

33 so get the value from the form's instance of the object. 

34 """ 

35 return cleaned_data.get(field_name) if self.has_field(field_name) else getattr(self.instance, field_name) 

36 

37 def clean(self): 

38 cleaned_data = super().clean() 

39 

40 field_errors = {} 

41 non_field_errors = [] 

42 

43 supports_expiration = cleaned_data.get("supports_expiration") 

44 

45 if supports_expiration: 

46 expiration_days = cleaned_data.get("expiration_days") 

47 expiration_reenrollment_days = cleaned_data.get("expiration_reenrollment_days") 

48 

49 message = "When support_expiration is True, this value must be greater than 0." 

50 if expiration_days is None or expiration_days <= 0: 50 ↛ 52line 50 didn't jump to line 52 because the condition on line 50 was always true

51 field_errors.update(expiration_days=ValidationError(message)) 

52 if expiration_reenrollment_days is None or expiration_reenrollment_days <= 0: 52 ↛ 56line 52 didn't jump to line 56 because the condition on line 52 was always true

53 field_errors.update(expiration_reenrollment_days=ValidationError(message)) 

54 

55 # these fields might not be on the form, so use helper method to correctly get the value 

56 eligibility_api_request = self.get(cleaned_data, "api_request") 

57 claims_request = self.get(cleaned_data, "claims_request") 

58 

59 supported_enrollment_methods = self.get(cleaned_data, "supported_enrollment_methods") 

60 supports_self_service = ( 

61 supported_enrollment_methods and models.EnrollmentMethods.SELF_SERVICE in supported_enrollment_methods 

62 ) 

63 

64 if supports_self_service and not (claims_request or eligibility_api_request): 

65 message = "Must configure either claims verification or Eligibility API verification." 

66 non_field_errors.append(ValidationError(message)) 

67 

68 system_name = self.get(cleaned_data, "system_name") 

69 selection_label_template_path = EnrollmentFlow.get_selection_label_template(system_name) 

70 if supports_self_service and not template_path(selection_label_template_path): 

71 # we can't add a field-level validation error 

72 # because the actual template for the self-service flow is derived from a pattern 

73 non_field_errors.append(ValidationError(f"Template not found: {selection_label_template_path}")) 

74 

75 supports_in_person = ( 

76 supported_enrollment_methods and models.EnrollmentMethods.IN_PERSON in supported_enrollment_methods 

77 ) 

78 in_person_policy = self.get(cleaned_data, "in_person_policy") 

79 if supports_in_person and not in_person_policy: 

80 field_errors.update( 

81 in_person_policy=ValidationError( 

82 "If the flow intends to support In-person Enrollment, it must be configured with associated policy language." # noqa: E501 

83 ) 

84 ) 

85 

86 for field_name, validation_error in field_errors.items(): 

87 self.add_error(field_name, validation_error) 

88 for validation_error in non_field_errors: 

89 self.add_error(None, validation_error) 

90 

91 

92@admin.register(models.EnrollmentFlow) 

93class SortableEnrollmentFlowAdmin(StaffPermissionMixin, SortableAdminMixin, admin.ModelAdmin): 

94 list_display = ("label", "supported_enrollment_methods") 

95 form = EnrollmentFlowForm