Commit eb5fc311 by Albert St. Aubin

Refactored the API to be part of the Entitlement API, removed it from

the Enrollment API
parent b0a19e94
...@@ -5,7 +5,6 @@ import datetime ...@@ -5,7 +5,6 @@ import datetime
import itertools import itertools
import json import json
import unittest import unittest
import uuid
import ddt import ddt
import httpretty import httpretty
...@@ -25,11 +24,9 @@ from xmodule.modulestore.tests.factories import CourseFactory, check_mongo_calls ...@@ -25,11 +24,9 @@ from xmodule.modulestore.tests.factories import CourseFactory, check_mongo_calls
from course_modes.models import CourseMode from course_modes.models import CourseMode
from course_modes.tests.factories import CourseModeFactory from course_modes.tests.factories import CourseModeFactory
from entitlements.tests.factories import CourseEntitlementFactory
from enrollment import api from enrollment import api
from enrollment.errors import CourseEnrollmentError from enrollment.errors import CourseEnrollmentError
from enrollment.views import EnrollmentUserThrottle from enrollment.views import EnrollmentUserThrottle
from entitlements.models import CourseEntitlement
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from openedx.core.djangoapps.embargo.models import Country, CountryAccessRule, RestrictedCourse from openedx.core.djangoapps.embargo.models import Country, CountryAccessRule, RestrictedCourse
from openedx.core.djangoapps.embargo.test_utils import restrict_course from openedx.core.djangoapps.embargo.test_utils import restrict_course
...@@ -38,7 +35,7 @@ from openedx.core.lib.django_test_client_utils import get_absolute_url ...@@ -38,7 +35,7 @@ from openedx.core.lib.django_test_client_utils import get_absolute_url
from openedx.features.enterprise_support.tests.mixins.enterprise import EnterpriseServiceMockMixin from openedx.features.enterprise_support.tests.mixins.enterprise import EnterpriseServiceMockMixin
from student.models import CourseEnrollment from student.models import CourseEnrollment
from student.roles import CourseStaffRole from student.roles import CourseStaffRole
from student.tests.factories import AdminFactory, UserFactory, TEST_PASSWORD from student.tests.factories import AdminFactory, UserFactory
from util.models import RateLimitConfiguration from util.models import RateLimitConfiguration
from util.testing import UrlResetMixin from util.testing import UrlResetMixin
...@@ -50,7 +47,6 @@ class EnrollmentTestMixin(object): ...@@ -50,7 +47,6 @@ class EnrollmentTestMixin(object):
def assert_enrollment_status( def assert_enrollment_status(
self, self,
course_id=None, course_id=None,
course_uuid=None,
username=None, username=None,
expected_status=status.HTTP_200_OK, expected_status=status.HTTP_200_OK,
email_opt_in=None, email_opt_in=None,
...@@ -81,9 +77,6 @@ class EnrollmentTestMixin(object): ...@@ -81,9 +77,6 @@ class EnrollmentTestMixin(object):
'enrollment_attributes': enrollment_attributes 'enrollment_attributes': enrollment_attributes
} }
if course_uuid:
data['course_details']['course_uuid'] = course_uuid
if is_active is not None: if is_active is not None:
data['is_active'] = is_active data['is_active'] = is_active
...@@ -140,93 +133,6 @@ class EnrollmentTestMixin(object): ...@@ -140,93 +133,6 @@ class EnrollmentTestMixin(object):
self.assertEqual(actual_mode, expected_mode) self.assertEqual(actual_mode, expected_mode)
# @override_settings(EDX_API_KEY="i am a key")
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
class EntitlementEnrollmentTest(EnrollmentTestMixin, ModuleStoreTestCase, APITestCase):
def setUp(self):
super(EntitlementEnrollmentTest, self).setUp()
self.course = CourseFactory()
self.user = UserFactory()
CourseModeFactory.create(
course_id=self.course.id,
mode_slug=CourseMode.VERIFIED,
mode_display_name=CourseMode.VERIFIED,
)
self.client.login(username=self.user.username, password=TEST_PASSWORD)
def test_enroll_entitlement(self):
entitlement = CourseEntitlementFactory.create(user=self.user, mode='verified')
resp = self.assert_enrollment_status(
course_id=unicode(self.course.id),
course_uuid=str(entitlement.course_uuid),
is_active=True,
mode=None,
max_mongo_calls=4
)
data = json.loads(resp.content)
self.assertEqual(self.course.display_name_with_default, data['course_details']['course_name'])
# Verify that the enrollment was created correctly
self.assertTrue(CourseEnrollment.is_enrolled(self.user, self.course.id))
course_mode, is_active = CourseEnrollment.enrollment_mode_for_user(self.user, self.course.id)
self.assertTrue(is_active)
self.assertEqual(course_mode, entitlement.mode)
entitlement.refresh_from_db()
# Verify the Entitlement settings are correct
self.assertIsNotNone(entitlement.enrollment_course_run)
self.assertEqual(entitlement.enrollment_course_run.course_id, self.course.id)
def test_unenroll_entitlement(self):
entitlement = CourseEntitlementFactory.create(user=self.user, mode='verified')
# Enroll user
self.assert_enrollment_status(
course_id=unicode(self.course.id),
course_uuid=str(entitlement.course_uuid),
is_active=True,
mode=None,
max_mongo_calls=4
)
# Unenroll the user
resp = self.assert_enrollment_status(
course_id=unicode(self.course.id),
course_uuid=str(entitlement.course_uuid),
is_active=False,
mode=None,
max_mongo_calls=4
)
data = json.loads(resp.content)
self.assertEqual(self.course.display_name_with_default, data['course_details']['course_name'])
# Verify that the enrollment was created correctly
self.assertFalse(CourseEnrollment.is_enrolled(self.user, self.course.id))
course_mode, is_active = CourseEnrollment.enrollment_mode_for_user(self.user, self.course.id)
self.assertFalse(is_active)
self.assertEqual(course_mode, entitlement.mode)
entitlement.refresh_from_db()
self.assertIsNone(entitlement.enrollment_course_run)
def test_enroll_no_entitlement(self):
resp = self.assert_enrollment_status(
course_id=unicode(self.course.id),
course_uuid=str(uuid.uuid4()),
is_active=True,
mode=None,
max_mongo_calls=4,
expected_status=status.HTTP_400_BAD_REQUEST
)
data = json.loads(resp.content)
self.assertEqual(self.course.display_name_with_default, data['course_details']['course_name'])
# Verify that the enrollment was created correctly
self.assertFalse(CourseEnrollment.is_enrolled(self.user, self.course.id))
@attr(shard=3) @attr(shard=3)
@override_settings(EDX_API_KEY="i am a key") @override_settings(EDX_API_KEY="i am a key")
@ddt.ddt @ddt.ddt
......
from django.conf.urls import url, include from django.conf.urls import url, include
from rest_framework.routers import DefaultRouter from rest_framework.routers import DefaultRouter
from .views import EntitlementViewSet from .views import EntitlementViewSet, EntitlementEnrollmentViewSet
router = DefaultRouter() router = DefaultRouter()
router.register(r'entitlements', EntitlementViewSet, base_name='entitlements') router.register(r'entitlements', EntitlementViewSet, base_name='entitlements')
enrollments_view = EntitlementEnrollmentViewSet.as_view({
'post': 'create',
'delete': 'destroy',
})
urlpatterns = [ urlpatterns = [
url(r'', include(router.urls)), url(r'', include(router.urls)),
url(
r'entitlements/(?P<uuid>[0-9a-f-]+)/enrollments/$',
enrollments_view,
name='enrollments'
)
] ]
...@@ -3,13 +3,19 @@ import logging ...@@ -3,13 +3,19 @@ import logging
from django.utils import timezone from django.utils import timezone
from django_filters.rest_framework import DjangoFilterBackend from django_filters.rest_framework import DjangoFilterBackend
from edx_rest_framework_extensions.authentication import JwtAuthentication from edx_rest_framework_extensions.authentication import JwtAuthentication
from rest_framework import permissions, viewsets from rest_framework import permissions, viewsets, status
from rest_framework.response import Response
from rest_framework.authentication import SessionAuthentication
from openedx.core.djangoapps.catalog.utils import get_course_runs_for_course
from entitlements.api.v1.filters import CourseEntitlementFilter from entitlements.api.v1.filters import CourseEntitlementFilter
from entitlements.api.v1.permissions import IsAdminOrAuthenticatedReadOnly from entitlements.api.v1.permissions import IsAdminOrAuthenticatedReadOnly
from entitlements.models import CourseEntitlement
from entitlements.api.v1.serializers import CourseEntitlementSerializer from entitlements.api.v1.serializers import CourseEntitlementSerializer
from entitlements.models import CourseEntitlement from entitlements.models import CourseEntitlement
from openedx.core.djangoapps.cors_csrf.authentication import SessionAuthenticationCrossDomainCsrf from openedx.core.djangoapps.cors_csrf.authentication import SessionAuthenticationCrossDomainCsrf
from opaque_keys.edx.keys import CourseKey
from student.models import CourseEnrollment from student.models import CourseEnrollment
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -57,3 +63,105 @@ class EntitlementViewSet(viewsets.ModelViewSet): ...@@ -57,3 +63,105 @@ class EntitlementViewSet(viewsets.ModelViewSet):
) )
if save_model: if save_model:
instance.save() instance.save()
class EntitlementEnrollmentViewSet(viewsets.GenericViewSet):
authentication_classes = (JwtAuthentication, SessionAuthentication,)
permission_classes = (permissions.IsAuthenticated,)
queryset = CourseEntitlement.objects.all()
serializer_class = CourseEntitlementSerializer
def _enroll_entitlement(self, entitlement, course_session_key, user):
enrollment = CourseEnrollment.enroll(
user=user,
course_key=course_session_key,
mode=entitlement.mode,
)
CourseEntitlement.set_enrollment(entitlement, enrollment)
def _unenroll_entitlement(self, entitlement, course_session_key, user):
CourseEnrollment.unenroll(user, course_session_key, skip_refund=True)
CourseEntitlement.set_enrollment(entitlement, None)
def create(self, request, uuid):
course_session_id = request.data.get('course_session_id', None)
if not course_session_id:
return Response(
status=status.HTTP_400_BAD_REQUEST,
data="The Course Run ID was not provided."
)
# Verify that the user has an Entitlement for the provided Course UUID.
try:
entitlement = CourseEntitlement.objects.get(uuid=uuid, user=request.user, expired_at=None)
except CourseEntitlement.DoesNotExist:
return Response(
status=status.HTTP_400_BAD_REQUEST,
data="The Entitlement for this UUID does not exist or is Expired."
)
# Verify the course run ID is of the same type as the Course entitlement.
course_run_valid = False
course_runs = get_course_runs_for_course(entitlement.course_uuid)
for run in course_runs:
if course_session_id == run.get('key', ''):
course_run_valid = True
if not course_run_valid:
return Response(
status=status.HTTP_400_BAD_REQUEST,
data="The Course Run ID is not a match for this Course Entitlement."
)
# Determine if this is a Switch session or a simple enroll and handle both.
if entitlement.enrollment_course_run is None:
self._enroll_entitlement(
entitlement=entitlement,
course_session_key=CourseKey.from_string(course_session_id),
user=request.user
)
else:
if entitlement.enrollment_course_run.course_id != course_session_id:
self._unenroll_entitlement(
entitlement=entitlement,
course_session_key=entitlement.enrollment_course_run.course_id,
user=request.user
)
self._enroll_entitlement(
entitlement=entitlement,
course_session_key=CourseKey.from_string(course_session_id),
user=request.user
)
return Response(
status=status.HTTP_201_CREATED,
data={
'uuid': entitlement.uuid,
'course_run_id': course_session_id,
'is_active': True
}
)
def destroy(self, request, uuid):
"""
On DELETE call to this API we will unenroll the course enrollment for the provided uuid
"""
try:
entitlement = CourseEntitlement.objects.get(uuid=uuid, user=request.user, expired_at=None)
except CourseEntitlement.DoesNotExist:
return Response(
status=status.HTTP_400_BAD_REQUEST,
data="The Entitlement for this UUID does not exist or is Expired."
)
if entitlement.enrollment_course_run is None:
return Response()
self._unenroll_entitlement(
entitlement=entitlement,
course_session_key=entitlement.enrollment_course_run.course_id,
user=request.user
)
return Response(status=status.HTTP_204_NO_CONTENT)
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
import uuid
class Migration(migrations.Migration):
dependencies = [
('entitlements', '0002_auto_20171102_0719'),
]
operations = [
migrations.AlterField(
model_name='courseentitlement',
name='uuid',
field=models.UUIDField(default=uuid.uuid4, unique=True, editable=False),
),
]
...@@ -11,7 +11,7 @@ class CourseEntitlement(TimeStampedModel): ...@@ -11,7 +11,7 @@ class CourseEntitlement(TimeStampedModel):
""" """
user = models.ForeignKey(settings.AUTH_USER_MODEL) user = models.ForeignKey(settings.AUTH_USER_MODEL)
uuid = models.UUIDField(default=uuid_tools.uuid4, editable=False) uuid = models.UUIDField(default=uuid_tools.uuid4, editable=False, unique=True)
course_uuid = models.UUIDField(help_text='UUID for the Course, not the Course Run') course_uuid = models.UUIDField(help_text='UUID for the Course, not the Course Run')
expired_at = models.DateTimeField( expired_at = models.DateTimeField(
null=True, null=True,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment