diff --git a/enterprise_access/apps/subsidy_access_policy/admin/__init__.py b/enterprise_access/apps/subsidy_access_policy/admin/__init__.py index 1d688c76..a8eeebe2 100644 --- a/enterprise_access/apps/subsidy_access_policy/admin/__init__.py +++ b/enterprise_access/apps/subsidy_access_policy/admin/__init__.py @@ -26,7 +26,7 @@ SubsidyAccessPolicySetLateRedemptionView ) -from .forms import ForcedPolicyRedemptionForm +from .forms import ForcedPolicyRedemptionForm, SubsidyAccessPolicyForm logger = logging.getLogger(__name__) @@ -199,6 +199,8 @@ class PerLearnerEnrollmentCreditAccessPolicy(DjangoQLSearchMixin, BaseSubsidyAcc """ Admin configuration for PerLearnerEnrollmentCreditAccessPolicy. """ + form = SubsidyAccessPolicyForm + list_display = BaseSubsidyAccessPolicyMixin.list_display + ( 'per_learner_enrollment_limit', ) @@ -250,6 +252,8 @@ class PerLearnerSpendCreditAccessPolicy(DjangoQLSearchMixin, BaseSubsidyAccessPo """ Admin configuration for PerLearnerSpendCreditAccessPolicy. """ + form = SubsidyAccessPolicyForm + list_display = BaseSubsidyAccessPolicyMixin.list_display + ( 'per_learner_spend_limit_dollars', ) @@ -313,6 +317,8 @@ class LearnerContentAssignmentAccessPolicy(DjangoQLSearchMixin, BaseSubsidyAcces """ Admin configuration for AssignedLearnerCreditAccessPolicy. """ + form = SubsidyAccessPolicyForm + search_fields = ( 'uuid', 'display_name', diff --git a/enterprise_access/apps/subsidy_access_policy/admin/forms.py b/enterprise_access/apps/subsidy_access_policy/admin/forms.py index b202adf5..5236f4c0 100644 --- a/enterprise_access/apps/subsidy_access_policy/admin/forms.py +++ b/enterprise_access/apps/subsidy_access_policy/admin/forms.py @@ -1,11 +1,15 @@ """ Forms to be used for subsidy_access_policy django admin. """ +import requests from django import forms from django.conf import settings +from django.core.exceptions import ValidationError from django.utils.translation import gettext as _ -from ..models import ForcedPolicyRedemption +from enterprise_access.apps.subsidy_access_policy.utils import get_versioned_subsidy_client + +from ..models import ForcedPolicyRedemption, SubsidyAccessPolicy class LateRedemptionDaysFromNowChoices: @@ -92,3 +96,30 @@ class ForcedPolicyRedemptionForm(forms.ModelForm): class Meta: model = ForcedPolicyRedemption fields = '__all__' + + +class SubsidyAccessPolicyForm(forms.ModelForm): + """ + Admin form for the SubsidyAccessPolicy model. + """ + def clean_subsidy_uuid(self): + """ + Validate that the subsidy exists and is assigned to the same enterprise customer as the budget. + """ + # 1. check if the subsidy_uuid actually exists + # 2. subsidy is assigned to the same enterprise customer as the budget + # if any of these checks fail, raise a ValidationError + client = get_versioned_subsidy_client(version=1) + try: + subsidy = client.retrieve_subsidy(self.cleaned_data["subsidy_uuid"]) + except requests.exceptions.HTTPError as exc: + raise ValidationError("Subsidy does not exist") from exc + + if str(subsidy["enterprise_customer_uuid"]) != str(self.cleaned_data["enterprise_customer_uuid"]): + raise ValidationError("Subsidy is not assigned to the same enterprise customer as the budget") + + return self.cleaned_data["subsidy_uuid"] + + class Meta: + model = SubsidyAccessPolicy + fields = '__all__' diff --git a/enterprise_access/apps/subsidy_access_policy/tests/test_forms.py b/enterprise_access/apps/subsidy_access_policy/tests/test_forms.py new file mode 100644 index 00000000..82c22c05 --- /dev/null +++ b/enterprise_access/apps/subsidy_access_policy/tests/test_forms.py @@ -0,0 +1,77 @@ +""" +Unittests for forms. +""" +import uuid +from unittest import mock + +import requests +from django.test import TestCase + +from enterprise_access.apps.subsidy_access_policy.admin.forms import SubsidyAccessPolicyForm +from enterprise_access.apps.subsidy_access_policy.constants import AccessMethods + + +class TestSubsidyAccessPolicyForm(TestCase): + """ + Tests for SubsidyAccessPolicyForm. + """ + def setUp(self): + super().setUp() + self.enterprise_customer_uuid = uuid.uuid4() + self.subsidy_uuid = uuid.uuid4() + self.catalog_uuid = uuid.uuid4() + self.form_data = { + 'enterprise_customer_uuid': self.enterprise_customer_uuid, + 'subsidy_uuid': self.subsidy_uuid, + 'catalog_uuid': self.catalog_uuid, + 'access_method': AccessMethods.DIRECT, + } + + @mock.patch('enterprise_access.apps.subsidy_access_policy.admin.forms.get_versioned_subsidy_client') + def test_clean_subsidy_uuid_success(self, mock_get_client): + """ + Test successful validation when subsidy exists and belongs to enterprise customer. + """ + mock_client = mock.MagicMock() + mock_client.retrieve_subsidy.return_value = { + 'enterprise_customer_uuid': self.enterprise_customer_uuid + } + mock_get_client.return_value = mock_client + + form = SubsidyAccessPolicyForm(data=self.form_data) + self.assertTrue(form.is_valid()) + self.assertEqual(form.cleaned_data['subsidy_uuid'], self.subsidy_uuid) + + @mock.patch('enterprise_access.apps.subsidy_access_policy.admin.forms.get_versioned_subsidy_client') + def test_clean_subsidy_uuid_not_found(self, mock_get_client): + """ + Verify that a validation error is raised when the subsidy does not exist. + """ + mock_client = mock.MagicMock() + mock_client.retrieve_subsidy.side_effect = requests.exceptions.HTTPError() + mock_get_client.return_value = mock_client + + form = SubsidyAccessPolicyForm(data=self.form_data) + self.assertFalse(form.is_valid()) + self.assertIn('subsidy_uuid', form.errors) + self.assertEqual(form.errors['subsidy_uuid'], ['Subsidy does not exist']) + + @mock.patch('enterprise_access.apps.subsidy_access_policy.admin.forms.get_versioned_subsidy_client') + def test_clean_subsidy_uuid_wrong_enterprise(self, mock_get_client): + """ + Verify that a validation error is raised when the subsidy belongs to a different enterprise customer. + """ + different_enterprise_uuid = uuid.uuid4() + mock_client = mock.MagicMock() + mock_client.retrieve_subsidy.return_value = { + 'enterprise_customer_uuid': different_enterprise_uuid + } + mock_get_client.return_value = mock_client + + form = SubsidyAccessPolicyForm(data=self.form_data) + self.assertFalse(form.is_valid()) + self.assertIn('subsidy_uuid', form.errors) + self.assertEqual( + form.errors['subsidy_uuid'], + ['Subsidy is not assigned to the same enterprise customer as the budget'] + )