Commit a164e2fa by Bill DeRusha

Merge pull request #10934 from edx/release

Hotfix 2015-12-09a and Hotfix 2015-12-09b
parents 259de407 75bbed72
...@@ -7,6 +7,8 @@ import importlib ...@@ -7,6 +7,8 @@ import importlib
import logging import logging
from django.conf import settings from django.conf import settings
from django.core.cache import cache from django.core.cache import cache
from opaque_keys.edx.keys import CourseKey
from course_modes.models import CourseMode from course_modes.models import CourseMode
from enrollment import errors from enrollment import errors
...@@ -133,7 +135,7 @@ def get_enrollment(user_id, course_id): ...@@ -133,7 +135,7 @@ def get_enrollment(user_id, course_id):
return _data_api().get_course_enrollment(user_id, course_id) return _data_api().get_course_enrollment(user_id, course_id)
def add_enrollment(user_id, course_id, mode=CourseMode.DEFAULT_MODE_SLUG, is_active=True): def add_enrollment(user_id, course_id, mode=None, is_active=True):
"""Enrolls a user in a course. """Enrolls a user in a course.
Enrolls a user in a course. If the mode is not specified, this will default to `CourseMode.DEFAULT_MODE_SLUG`. Enrolls a user in a course. If the mode is not specified, this will default to `CourseMode.DEFAULT_MODE_SLUG`.
...@@ -180,6 +182,8 @@ def add_enrollment(user_id, course_id, mode=CourseMode.DEFAULT_MODE_SLUG, is_act ...@@ -180,6 +182,8 @@ def add_enrollment(user_id, course_id, mode=CourseMode.DEFAULT_MODE_SLUG, is_act
} }
} }
""" """
if mode is None:
mode = _default_course_mode(course_id)
_validate_course_mode(course_id, mode, is_active=is_active) _validate_course_mode(course_id, mode, is_active=is_active)
return _data_api().create_course_enrollment(user_id, course_id, mode, is_active) return _data_api().create_course_enrollment(user_id, course_id, mode, is_active)
...@@ -359,16 +363,36 @@ def get_enrollment_attributes(user_id, course_id): ...@@ -359,16 +363,36 @@ def get_enrollment_attributes(user_id, course_id):
return _data_api().get_enrollment_attributes(user_id, course_id) return _data_api().get_enrollment_attributes(user_id, course_id)
def _default_course_mode(course_id):
"""Return the default enrollment for a course.
Special case the default enrollment to return if nothing else is found.
Arguments:
course_id (str): The course to check against for available course modes.
Returns:
str
"""
course_modes = CourseMode.modes_for_course(CourseKey.from_string(course_id))
available_modes = [m.slug for m in course_modes]
if CourseMode.DEFAULT_MODE_SLUG in available_modes:
return CourseMode.DEFAULT_MODE_SLUG
elif 'audit' in available_modes:
return 'audit'
elif 'honor' in available_modes:
return 'honor'
return CourseMode.DEFAULT_MODE_SLUG
def _validate_course_mode(course_id, mode, is_active=None): def _validate_course_mode(course_id, mode, is_active=None):
"""Checks to see if the specified course mode is valid for the course. """Checks to see if the specified course mode is valid for the course.
If the requested course mode is not available for the course, raise an error with corresponding If the requested course mode is not available for the course, raise an error with corresponding
course enrollment information. course enrollment information.
'honor' is special cased. If there are no course modes configured, and the specified mode is
'honor', return true, allowing the enrollment to be 'honor' even if the mode is not explicitly
set for the course.
Arguments: Arguments:
course_id (str): The course to check against for available course modes. course_id (str): The course to check against for available course modes.
mode (str): The slug for the course mode specified in the enrollment. mode (str): The slug for the course mode specified in the enrollment.
......
""" """
Tests for student enrollment. Tests for student enrollment.
""" """
from mock import patch, Mock
import ddt import ddt
from django.core.cache import cache from django.core.cache import cache
from nose.tools import raises from nose.tools import raises
...@@ -8,6 +10,8 @@ import unittest ...@@ -8,6 +10,8 @@ import unittest
from django.test import TestCase from django.test import TestCase
from django.test.utils import override_settings from django.test.utils import override_settings
from django.conf import settings from django.conf import settings
from course_modes.models import CourseMode
from enrollment import api from enrollment import api
from enrollment.errors import EnrollmentApiLoadError, EnrollmentNotFoundError, CourseModeNotFoundError from enrollment.errors import EnrollmentApiLoadError, EnrollmentNotFoundError, CourseModeNotFoundError
from enrollment.tests import fake_data_api from enrollment.tests import fake_data_api
...@@ -56,6 +60,37 @@ class EnrollmentTest(TestCase): ...@@ -56,6 +60,37 @@ class EnrollmentTest(TestCase):
get_result = api.get_enrollment(self.USERNAME, self.COURSE_ID) get_result = api.get_enrollment(self.USERNAME, self.COURSE_ID)
self.assertEquals(result, get_result) self.assertEquals(result, get_result)
@ddt.data(
([CourseMode.DEFAULT_MODE_SLUG, 'verified', 'credit'], CourseMode.DEFAULT_MODE_SLUG),
(['audit', 'verified', 'credit'], 'audit'),
(['honor', 'verified', 'credit'], 'honor'),
)
@ddt.unpack
def test_enroll_no_mode_success(self, course_modes, expected_mode):
# Add a fake course enrollment information to the fake data API
fake_data_api.add_course(self.COURSE_ID, course_modes=course_modes)
with patch('enrollment.api.CourseMode.modes_for_course') as mock_modes_for_course:
mock_course_modes = [Mock(slug=mode) for mode in course_modes]
mock_modes_for_course.return_value = mock_course_modes
# Enroll in the course and verify the URL we get sent to
result = api.add_enrollment(self.USERNAME, self.COURSE_ID)
self.assertIsNotNone(result)
self.assertEquals(result['student'], self.USERNAME)
self.assertEquals(result['course']['course_id'], self.COURSE_ID)
self.assertEquals(result['mode'], expected_mode)
@ddt.data(
['professional'],
['verified'],
['verified', 'professional'],
)
@raises(CourseModeNotFoundError)
def test_enroll_no_mode_error(self, course_modes):
# Add a fake course enrollment information to the fake data API
fake_data_api.add_course(self.COURSE_ID, course_modes=course_modes)
# Enroll in the course and verify that we raise CourseModeNotFoundError
api.add_enrollment(self.USERNAME, self.COURSE_ID)
@raises(CourseModeNotFoundError) @raises(CourseModeNotFoundError)
def test_prof_ed_enroll(self): def test_prof_ed_enroll(self):
# Add a fake course enrollment information to the fake data API # Add a fake course enrollment information to the fake data API
......
...@@ -520,7 +520,7 @@ class EnrollmentListView(APIView, ApiKeyPermissionMixIn): ...@@ -520,7 +520,7 @@ class EnrollmentListView(APIView, ApiKeyPermissionMixIn):
} }
) )
mode = request.data.get('mode', CourseMode.DEFAULT_MODE_SLUG) mode = request.data.get('mode')
has_api_key_permissions = self.has_api_key_permissions(request) has_api_key_permissions = self.has_api_key_permissions(request)
...@@ -532,7 +532,7 @@ class EnrollmentListView(APIView, ApiKeyPermissionMixIn): ...@@ -532,7 +532,7 @@ class EnrollmentListView(APIView, ApiKeyPermissionMixIn):
# other users, do not let them deduce the existence of an enrollment. # other users, do not let them deduce the existence of an enrollment.
return Response(status=status.HTTP_404_NOT_FOUND) return Response(status=status.HTTP_404_NOT_FOUND)
if mode != CourseMode.DEFAULT_MODE_SLUG and not has_api_key_permissions: if mode not in (CourseMode.AUDIT, CourseMode.HONOR, None) and not has_api_key_permissions:
return Response( return Response(
status=status.HTTP_403_FORBIDDEN, status=status.HTTP_403_FORBIDDEN,
data={ data={
......
...@@ -47,6 +47,7 @@ from xmodule_django.models import CourseKeyField, NoneToEmptyManager ...@@ -47,6 +47,7 @@ from xmodule_django.models import CourseKeyField, NoneToEmptyManager
from certificates.models import GeneratedCertificate from certificates.models import GeneratedCertificate
from course_modes.models import CourseMode from course_modes.models import CourseMode
from enrollment.api import _default_course_mode
import lms.lib.comment_client as cc import lms.lib.comment_client as cc
from openedx.core.djangoapps.commerce.utils import ecommerce_api_client, ECOMMERCE_DATE_FORMAT from openedx.core.djangoapps.commerce.utils import ecommerce_api_client, ECOMMERCE_DATE_FORMAT
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
...@@ -1090,7 +1091,7 @@ class CourseEnrollment(models.Model): ...@@ -1090,7 +1091,7 @@ class CourseEnrollment(models.Model):
) )
@classmethod @classmethod
def enroll(cls, user, course_key, mode=CourseMode.DEFAULT_MODE_SLUG, check_access=False): def enroll(cls, user, course_key, mode=None, check_access=False):
""" """
Enroll a user in a course. This saves immediately. Enroll a user in a course. This saves immediately.
...@@ -1124,6 +1125,8 @@ class CourseEnrollment(models.Model): ...@@ -1124,6 +1125,8 @@ class CourseEnrollment(models.Model):
Also emits relevant events for analytics purposes. Also emits relevant events for analytics purposes.
""" """
if mode is None:
mode = _default_course_mode(unicode(course_key))
# All the server-side checks for whether a user is allowed to enroll. # All the server-side checks for whether a user is allowed to enroll.
try: try:
course = CourseOverview.get_from_id(course_key) course = CourseOverview.get_from_id(course_key)
...@@ -1165,7 +1168,7 @@ class CourseEnrollment(models.Model): ...@@ -1165,7 +1168,7 @@ class CourseEnrollment(models.Model):
return enrollment return enrollment
@classmethod @classmethod
def enroll_by_email(cls, email, course_id, mode=CourseMode.DEFAULT_MODE_SLUG, ignore_errors=True): def enroll_by_email(cls, email, course_id, mode=None, ignore_errors=True):
""" """
Enroll a user in a course given their email. This saves immediately. Enroll a user in a course given their email. This saves immediately.
......
...@@ -119,7 +119,7 @@ def enroll_email(course_id, student_email, auto_enroll=False, email_students=Fal ...@@ -119,7 +119,7 @@ def enroll_email(course_id, student_email, auto_enroll=False, email_students=Fal
if CourseMode.is_white_label(course_id): if CourseMode.is_white_label(course_id):
course_mode = CourseMode.DEFAULT_SHOPPINGCART_MODE_SLUG course_mode = CourseMode.DEFAULT_SHOPPINGCART_MODE_SLUG
else: else:
course_mode = CourseMode.DEFAULT_MODE_SLUG course_mode = None
if previous_state.enrollment: if previous_state.enrollment:
course_mode = previous_state.mode course_mode = previous_state.mode
......
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