diff --git a/common/djangoapps/entitlements/api.py b/common/djangoapps/entitlements/api.py new file mode 100644 index 0000000000..bf55a8e0d5 --- /dev/null +++ b/common/djangoapps/entitlements/api.py @@ -0,0 +1,16 @@ +""" +Python APIs exposed by the Entitlements app to other in-process apps. +""" + +from .models import CourseEntitlement as _CourseEntitlement + + +def get_active_entitlement_list_for_user(user): + """ + Arguments: + user (User): The user we are looking at the entitlements of. + + Returns: + List: Active entitlements for the provided User. + """ + return _CourseEntitlement.get_active_entitlements_for_user(user) diff --git a/common/djangoapps/entitlements/api/urls.py b/common/djangoapps/entitlements/api/urls.py deleted file mode 100644 index d3d5a52d63..0000000000 --- a/common/djangoapps/entitlements/api/urls.py +++ /dev/null @@ -1,8 +0,0 @@ - - -from django.conf.urls import include, url - -app_name = 'entitlements' -urlpatterns = [ - url(r'^v1/', include('entitlements.api.v1.urls')), -] diff --git a/common/djangoapps/entitlements/api/__init__.py b/common/djangoapps/entitlements/rest_api/__init__.py similarity index 100% rename from common/djangoapps/entitlements/api/__init__.py rename to common/djangoapps/entitlements/rest_api/__init__.py diff --git a/common/djangoapps/entitlements/rest_api/urls.py b/common/djangoapps/entitlements/rest_api/urls.py new file mode 100644 index 0000000000..cda3bb602e --- /dev/null +++ b/common/djangoapps/entitlements/rest_api/urls.py @@ -0,0 +1,10 @@ +""" +URLs file for the Entitlements API. +""" + +from django.conf.urls import include, url + +app_name = 'entitlements' +urlpatterns = [ + url(r'^v1/', include('entitlements.rest_api.v1.urls')), +] diff --git a/common/djangoapps/entitlements/api/v1/__init__.py b/common/djangoapps/entitlements/rest_api/v1/__init__.py similarity index 100% rename from common/djangoapps/entitlements/api/v1/__init__.py rename to common/djangoapps/entitlements/rest_api/v1/__init__.py diff --git a/common/djangoapps/entitlements/api/v1/filters.py b/common/djangoapps/entitlements/rest_api/v1/filters.py similarity index 93% rename from common/djangoapps/entitlements/api/v1/filters.py rename to common/djangoapps/entitlements/rest_api/v1/filters.py index e67f6958bb..984b746401 100644 --- a/common/djangoapps/entitlements/api/v1/filters.py +++ b/common/djangoapps/entitlements/rest_api/v1/filters.py @@ -1,4 +1,6 @@ - +""" +Filters for the Entitlements API. +""" from django_filters import rest_framework as filters @@ -34,6 +36,7 @@ class UUIDListFilter(CharListFilter): class CourseEntitlementFilter(filters.FilterSet): + """Filter for CourseEntitlements""" uuid = UUIDListFilter() user = filters.CharFilter(field_name='user__username') diff --git a/common/djangoapps/entitlements/api/v1/permissions.py b/common/djangoapps/entitlements/rest_api/v1/permissions.py similarity index 100% rename from common/djangoapps/entitlements/api/v1/permissions.py rename to common/djangoapps/entitlements/rest_api/v1/permissions.py diff --git a/common/djangoapps/entitlements/api/v1/serializers.py b/common/djangoapps/entitlements/rest_api/v1/serializers.py similarity index 100% rename from common/djangoapps/entitlements/api/v1/serializers.py rename to common/djangoapps/entitlements/rest_api/v1/serializers.py diff --git a/common/djangoapps/entitlements/api/v1/tests/__init__.py b/common/djangoapps/entitlements/rest_api/v1/tests/__init__.py similarity index 100% rename from common/djangoapps/entitlements/api/v1/tests/__init__.py rename to common/djangoapps/entitlements/rest_api/v1/tests/__init__.py diff --git a/common/djangoapps/entitlements/api/v1/tests/test_serializers.py b/common/djangoapps/entitlements/rest_api/v1/tests/test_serializers.py similarity index 88% rename from common/djangoapps/entitlements/api/v1/tests/test_serializers.py rename to common/djangoapps/entitlements/rest_api/v1/tests/test_serializers.py index 88831aa4d4..c1e9a70b34 100644 --- a/common/djangoapps/entitlements/api/v1/tests/test_serializers.py +++ b/common/djangoapps/entitlements/rest_api/v1/tests/test_serializers.py @@ -1,4 +1,6 @@ - +""" +Tests for the API Serializers. +""" import unittest @@ -9,14 +11,15 @@ from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase # Entitlements is not in CMS' INSTALLED_APPS so these imports will error during test collection if settings.ROOT_URLCONF == 'lms.urls': - from entitlements.api.v1.serializers import CourseEntitlementSerializer + from entitlements.rest_api.v1.serializers import CourseEntitlementSerializer from entitlements.tests.factories import CourseEntitlementFactory @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms') class EntitlementsSerializerTests(ModuleStoreTestCase): - def setUp(self): - super(EntitlementsSerializerTests, self).setUp() + """ + Tests for the Entitlement Serializers. + """ def test_data(self): entitlement = CourseEntitlementFactory() diff --git a/common/djangoapps/entitlements/api/v1/tests/test_views.py b/common/djangoapps/entitlements/rest_api/v1/tests/test_views.py similarity index 94% rename from common/djangoapps/entitlements/api/v1/tests/test_views.py rename to common/djangoapps/entitlements/rest_api/v1/tests/test_views.py index eda867d7b7..3ba57fe7a2 100644 --- a/common/djangoapps/entitlements/api/v1/tests/test_views.py +++ b/common/djangoapps/entitlements/rest_api/v1/tests/test_views.py @@ -1,4 +1,6 @@ - +""" +Test file to test the Entitlement API Views. +""" import json import logging @@ -30,12 +32,15 @@ log = logging.getLogger(__name__) if settings.ROOT_URLCONF == 'lms.urls': from entitlements.tests.factories import CourseEntitlementFactory from entitlements.models import CourseEntitlement, CourseEntitlementPolicy, CourseEntitlementSupportDetail - from entitlements.api.v1.serializers import CourseEntitlementSerializer - from entitlements.api.v1.views import set_entitlement_policy + from entitlements.rest_api.v1.serializers import CourseEntitlementSerializer + from entitlements.rest_api.v1.views import set_entitlement_policy @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms') class EntitlementViewSetTest(ModuleStoreTestCase): + """ + Tests for the Entitlements API Views. + """ ENTITLEMENTS_DETAILS_PATH = 'entitlements_api:v1:entitlements-detail' def setUp(self): @@ -44,7 +49,7 @@ class EntitlementViewSetTest(ModuleStoreTestCase): self.client.login(username=self.user.username, password=TEST_PASSWORD) self.course = CourseFactory() self.course_mode = CourseModeFactory( - course_id=self.course.id, + course_id=self.course.id, # pylint: disable=no-member mode_slug=CourseMode.VERIFIED, # This must be in the future to ensure it is returned by downstream code. expiration_datetime=now() + timedelta(days=1) @@ -363,7 +368,7 @@ class EntitlementViewSetTest(ModuleStoreTestCase): ) assert course_entitlement.policy == policy - @patch("entitlements.api.v1.views.get_owners_for_course") + @patch("entitlements.rest_api.v1.views.get_owners_for_course") def test_email_opt_in_single_org(self, mock_get_owners): course_uuid = uuid.uuid4() entitlement_data = self._get_data_set(self.user, str(course_uuid)) @@ -382,7 +387,7 @@ class EntitlementViewSetTest(ModuleStoreTestCase): result_obj = UserOrgTag.objects.get(user=self.user, org=org, key='email-optin') self.assertEqual(result_obj.value, u"True") - @patch("entitlements.api.v1.views.get_owners_for_course") + @patch("entitlements.rest_api.v1.views.get_owners_for_course") def test_email_opt_in_multiple_orgs(self, mock_get_owners): course_uuid = uuid.uuid4() entitlement_data = self._get_data_set(self.user, str(course_uuid)) @@ -431,7 +436,7 @@ class EntitlementViewSetTest(ModuleStoreTestCase): ) assert results == CourseEntitlementSerializer(course_entitlement).data - @patch("entitlements.api.v1.views.get_course_runs_for_course") + @patch("entitlements.rest_api.v1.views.get_course_runs_for_course") def test_add_entitlement_and_upgrade_audit_enrollment(self, mock_get_course_runs): """ Verify that if an entitlement is added for a user, if the user has one upgradeable enrollment @@ -439,10 +444,13 @@ class EntitlementViewSetTest(ModuleStoreTestCase): """ course_uuid = uuid.uuid4() entitlement_data = self._get_data_set(self.user, str(course_uuid)) - mock_get_course_runs.return_value = [{'key': str(self.course.id)}] + mock_get_course_runs.return_value = [{'key': str(self.course.id)}] # pylint: disable=no-member # Add an audit course enrollment for user. - enrollment = CourseEnrollment.enroll(self.user, self.course.id, mode=CourseMode.AUDIT) + enrollment = CourseEnrollment.enroll( + self.user, + self.course.id, # pylint: disable=no-member + mode=CourseMode.AUDIT) response = self.client.post( self.entitlements_list_url, @@ -457,12 +465,15 @@ class EntitlementViewSetTest(ModuleStoreTestCase): course_uuid=course_uuid ) # Assert that enrollment mode is now verified - enrollment_mode = CourseEnrollment.enrollment_mode_for_user(self.user, self.course.id)[0] + enrollment_mode = CourseEnrollment.enrollment_mode_for_user( + self.user, + self.course.id # pylint: disable=no-member + )[0] assert enrollment_mode == course_entitlement.mode assert course_entitlement.enrollment_course_run == enrollment assert results == CourseEntitlementSerializer(course_entitlement).data - @patch("entitlements.api.v1.views.get_course_runs_for_course") + @patch("entitlements.rest_api.v1.views.get_course_runs_for_course") def test_add_entitlement_and_upgrade_audit_enrollment_with_dynamic_deadline(self, mock_get_course_runs): """ Verify that if an entitlement is added for a user, if the user has one upgradeable enrollment @@ -511,7 +522,7 @@ class EntitlementViewSetTest(ModuleStoreTestCase): assert course_entitlement.enrollment_course_run == enrollment assert results == CourseEntitlementSerializer(course_entitlement).data - @patch("entitlements.api.v1.views.get_course_runs_for_course") + @patch("entitlements.rest_api.v1.views.get_course_runs_for_course") def test_add_entitlement_inactive_audit_enrollment(self, mock_get_course_runs): """ Verify that if an entitlement is added for a user, if the user has an inactive audit enrollment @@ -519,10 +530,14 @@ class EntitlementViewSetTest(ModuleStoreTestCase): """ course_uuid = uuid.uuid4() entitlement_data = self._get_data_set(self.user, str(course_uuid)) - mock_get_course_runs.return_value = [{'key': str(self.course.id)}] + mock_get_course_runs.return_value = [{'key': str(self.course.id)}] # pylint: disable=no-member # Add an audit course enrollment for user. - enrollment = CourseEnrollment.enroll(self.user, self.course.id, mode=CourseMode.AUDIT) + enrollment = CourseEnrollment.enroll( + self.user, + self.course.id, # pylint: disable=no-member + mode=CourseMode.AUDIT + ) enrollment.update_enrollment(is_active=False) response = self.client.post( self.entitlements_list_url, @@ -537,7 +552,10 @@ class EntitlementViewSetTest(ModuleStoreTestCase): course_uuid=course_uuid ) # Assert that enrollment mode is now verified - enrollment_mode, enrollment_active = CourseEnrollment.enrollment_mode_for_user(self.user, self.course.id) + enrollment_mode, enrollment_active = CourseEnrollment.enrollment_mode_for_user( + self.user, + self.course.id # pylint: disable=no-member + ) assert enrollment_mode == CourseMode.AUDIT assert enrollment_active is False assert course_entitlement.enrollment_course_run is None @@ -669,7 +687,11 @@ class EntitlementViewSetTest(ModuleStoreTestCase): @patch("entitlements.models.get_course_uuid_for_course") def test_revoke_unenroll_entitlement(self, mock_course_uuid): - enrollment = CourseEnrollmentFactory.create(user=self.user, course_id=self.course.id, is_active=True) + enrollment = CourseEnrollmentFactory.create( + user=self.user, + course_id=self.course.id, # pylint: disable=no-member + is_active=True + ) course_entitlement = CourseEntitlementFactory.create(user=self.user, enrollment_course_run=enrollment) mock_course_uuid.return_value = course_entitlement.course_uuid url = reverse(self.ENTITLEMENTS_DETAILS_PATH, args=[str(course_entitlement.uuid)]) @@ -822,7 +844,7 @@ class EntitlementEnrollmentViewSetTest(ModuleStoreTestCase): {'key': str(self.course2.id)} ] - @patch("entitlements.api.v1.views.get_course_runs_for_course") + @patch("entitlements.rest_api.v1.views.get_course_runs_for_course") def test_user_can_enroll(self, mock_get_course_runs): course_entitlement = CourseEntitlementFactory.create(user=self.user, mode=CourseMode.VERIFIED) mock_get_course_runs.return_value = self.return_values @@ -847,7 +869,7 @@ class EntitlementEnrollmentViewSetTest(ModuleStoreTestCase): assert course_entitlement.enrollment_course_run is not None @patch("entitlements.models.get_course_uuid_for_course") - @patch("entitlements.api.v1.views.get_course_runs_for_course") + @patch("entitlements.rest_api.v1.views.get_course_runs_for_course") def test_user_can_unenroll(self, mock_get_course_runs, mock_get_course_uuid): course_entitlement = CourseEntitlementFactory.create(user=self.user, mode=CourseMode.VERIFIED) mock_get_course_runs.return_value = self.return_values @@ -882,7 +904,7 @@ class EntitlementEnrollmentViewSetTest(ModuleStoreTestCase): assert not CourseEnrollment.is_enrolled(self.user, self.course.id) assert course_entitlement.enrollment_course_run is None - @patch("entitlements.api.v1.views.get_course_runs_for_course") + @patch("entitlements.rest_api.v1.views.get_course_runs_for_course") def test_user_can_switch(self, mock_get_course_runs): mock_get_course_runs.return_value = self.return_values course_entitlement = CourseEntitlementFactory.create(user=self.user, mode=CourseMode.VERIFIED) @@ -920,7 +942,7 @@ class EntitlementEnrollmentViewSetTest(ModuleStoreTestCase): assert CourseEnrollment.is_enrolled(self.user, self.course2.id) assert course_entitlement.enrollment_course_run is not None - @patch("entitlements.api.v1.views.get_course_runs_for_course") + @patch("entitlements.rest_api.v1.views.get_course_runs_for_course") def test_user_already_enrolled(self, mock_get_course_runs): course_entitlement = CourseEntitlementFactory.create(user=self.user, mode=CourseMode.VERIFIED) mock_get_course_runs.return_value = self.return_values @@ -945,7 +967,7 @@ class EntitlementEnrollmentViewSetTest(ModuleStoreTestCase): assert CourseEnrollment.is_enrolled(self.user, self.course.id) assert course_entitlement.enrollment_course_run is not None - @patch("entitlements.api.v1.views.get_course_runs_for_course") + @patch("entitlements.rest_api.v1.views.get_course_runs_for_course") def test_user_already_enrolled_in_unpaid_mode(self, mock_get_course_runs): course_entitlement = CourseEntitlementFactory.create(user=self.user, mode=CourseMode.VERIFIED) mock_get_course_runs.return_value = self.return_values @@ -972,7 +994,7 @@ class EntitlementEnrollmentViewSetTest(ModuleStoreTestCase): assert is_active and (enrolled_mode == course_entitlement.mode) assert course_entitlement.enrollment_course_run is not None - @patch("entitlements.api.v1.views.get_course_runs_for_course") + @patch("entitlements.rest_api.v1.views.get_course_runs_for_course") def test_user_cannot_enroll_in_unknown_course_run_id(self, mock_get_course_runs): fake_course_str = str(self.course.id) + 'fake' fake_course_key = CourseKey.from_string(fake_course_str) @@ -999,7 +1021,7 @@ class EntitlementEnrollmentViewSetTest(ModuleStoreTestCase): assert not CourseEnrollment.is_enrolled(self.user, fake_course_key) @patch('entitlements.models.refund_entitlement', return_value=True) - @patch('entitlements.api.v1.views.get_course_runs_for_course') + @patch('entitlements.rest_api.v1.views.get_course_runs_for_course') @patch("entitlements.models.get_course_uuid_for_course") def test_user_can_revoke_and_refund(self, mock_course_uuid, mock_get_course_runs, mock_refund_entitlement): course_entitlement = CourseEntitlementFactory.create(user=self.user, mode=CourseMode.VERIFIED) @@ -1040,14 +1062,14 @@ class EntitlementEnrollmentViewSetTest(ModuleStoreTestCase): assert course_entitlement.enrollment_course_run is None assert course_entitlement.expired_at is not None - @patch('entitlements.api.v1.views.CourseEntitlement.is_entitlement_refundable', return_value=False) + @patch('entitlements.rest_api.v1.views.CourseEntitlement.is_entitlement_refundable', return_value=False) @patch('entitlements.models.refund_entitlement', return_value=True) - @patch('entitlements.api.v1.views.get_course_runs_for_course') + @patch('entitlements.rest_api.v1.views.get_course_runs_for_course') def test_user_can_revoke_and_no_refund_available( self, mock_get_course_runs, - mock_refund_entitlement, - mock_is_refundable + mock_refund_entitlement, # pylint: disable=unused-argument + mock_is_refundable # pylint: disable=unused-argument ): course_entitlement = CourseEntitlementFactory.create(user=self.user, mode=CourseMode.VERIFIED) mock_get_course_runs.return_value = self.return_values @@ -1084,14 +1106,14 @@ class EntitlementEnrollmentViewSetTest(ModuleStoreTestCase): assert course_entitlement.enrollment_course_run is not None assert course_entitlement.expired_at is None - @patch('entitlements.api.v1.views.CourseEntitlement.is_entitlement_refundable', return_value=True) + @patch('entitlements.rest_api.v1.views.CourseEntitlement.is_entitlement_refundable', return_value=True) @patch('entitlements.models.refund_entitlement', return_value=False) - @patch("entitlements.api.v1.views.get_course_runs_for_course") + @patch("entitlements.rest_api.v1.views.get_course_runs_for_course") def test_user_is_not_unenrolled_on_failed_refund( self, mock_get_course_runs, - mock_refund_entitlement, - mock_is_refundable + mock_refund_entitlement, # pylint: disable=unused-argument + mock_is_refundable # pylint: disable=unused-argument ): course_entitlement = CourseEntitlementFactory.create(user=self.user, mode=CourseMode.VERIFIED) mock_get_course_runs.return_value = self.return_values diff --git a/common/djangoapps/entitlements/api/v1/urls.py b/common/djangoapps/entitlements/rest_api/v1/urls.py similarity index 92% rename from common/djangoapps/entitlements/api/v1/urls.py rename to common/djangoapps/entitlements/rest_api/v1/urls.py index 78444019fb..35a204df09 100644 --- a/common/djangoapps/entitlements/api/v1/urls.py +++ b/common/djangoapps/entitlements/rest_api/v1/urls.py @@ -1,4 +1,6 @@ - +""" +URLs for the V1 of the Entitlements API. +""" from django.conf.urls import include, url from rest_framework.routers import DefaultRouter diff --git a/common/djangoapps/entitlements/api/v1/views.py b/common/djangoapps/entitlements/rest_api/v1/views.py similarity index 98% rename from common/djangoapps/entitlements/api/v1/views.py rename to common/djangoapps/entitlements/rest_api/v1/views.py index 4d0f6bd312..63b27718b2 100644 --- a/common/djangoapps/entitlements/api/v1/views.py +++ b/common/djangoapps/entitlements/rest_api/v1/views.py @@ -1,4 +1,6 @@ - +""" +Views for the Entitlements v1 API. +""" import logging @@ -15,9 +17,9 @@ from rest_framework.authentication import SessionAuthentication from rest_framework.response import Response from course_modes.models import CourseMode -from entitlements.api.v1.filters import CourseEntitlementFilter -from entitlements.api.v1.permissions import IsAdminOrSupportOrAuthenticatedReadOnly -from entitlements.api.v1.serializers import CourseEntitlementSerializer +from entitlements.rest_api.v1.filters import CourseEntitlementFilter +from entitlements.rest_api.v1.permissions import IsAdminOrSupportOrAuthenticatedReadOnly +from entitlements.rest_api.v1.serializers import CourseEntitlementSerializer from entitlements.models import CourseEntitlement, CourseEntitlementPolicy, CourseEntitlementSupportDetail from entitlements.utils import is_course_run_entitlement_fulfillable from openedx.core.djangoapps.catalog.utils import get_course_runs_for_course, get_owners_for_course @@ -102,6 +104,9 @@ def set_entitlement_policy(entitlement, site): class EntitlementViewSet(viewsets.ModelViewSet): + """ + ViewSet for the Entitlements API. + """ ENTITLEMENT_UUID4_REGEX = '[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}' authentication_classes = (JwtAuthentication, SessionAuthenticationCrossDomainCsrf,) @@ -295,7 +300,7 @@ class EntitlementViewSet(viewsets.ModelViewSet): ) CourseEntitlementSupportDetail.objects.create(**support_detail) - return super(EntitlementViewSet, self).partial_update(request, *args, **kwargs) + return super(EntitlementViewSet, self).partial_update(request, *args, **kwargs) # pylint: disable=no-member class EntitlementEnrollmentViewSet(viewsets.GenericViewSet): diff --git a/common/djangoapps/student/tests/test_views.py b/common/djangoapps/student/tests/test_views.py index 5b0d3a769f..bfdc87c662 100644 --- a/common/djangoapps/student/tests/test_views.py +++ b/common/djangoapps/student/tests/test_views.py @@ -389,7 +389,7 @@ class StudentDashboardTests(SharedModuleStoreTestCase, MilestonesTestCaseMixin, response = self.client.get(self.path) self.assertNotContains(response, '