diff --git a/common/djangoapps/course_modes/tests/test_views.py b/common/djangoapps/course_modes/tests/test_views.py index 25dfad2a2b..16868037f4 100644 --- a/common/djangoapps/course_modes/tests/test_views.py +++ b/common/djangoapps/course_modes/tests/test_views.py @@ -1,97 +1,120 @@ import ddt import unittest -from django.test import TestCase from django.conf import settings +from django.test.utils import override_settings from django.core.urlresolvers import reverse -from mock import patch, Mock +from xmodule.modulestore.tests.django_utils import ( + ModuleStoreTestCase, mixed_store_config +) + +from xmodule.modulestore.tests.factories import CourseFactory from course_modes.tests.factories import CourseModeFactory from student.tests.factories import CourseEnrollmentFactory, UserFactory -from opaque_keys.edx.locations import SlashSeparatedCourseKey + + +# Since we don't need any XML course fixtures, use a modulestore configuration +# that disables the XML modulestore. +MODULESTORE_CONFIG = mixed_store_config(settings.COMMON_TEST_DATA_ROOT, {}, include_xml=False) @ddt.ddt -class CourseModeViewTest(TestCase): +@override_settings(MODULESTORE=MODULESTORE_CONFIG) +@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms') +class CourseModeViewTest(ModuleStoreTestCase): def setUp(self): - self.course_id = SlashSeparatedCourseKey('org', 'course', 'run') + super(CourseModeViewTest, self).setUp() + self.course = CourseFactory.create() + self.user = UserFactory.create(username="Bob", email="bob@example.com", password="edx") + self.client.login(username=self.user.username, password="edx") - for mode in ('audit', 'verified', 'honor'): - CourseModeFactory(mode_slug=mode, course_id=self.course_id) - - @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms') @ddt.data( - # is_active?, enrollment_mode, upgrade?, redirect? - (True, 'verified', True, True), # User is already verified - (True, 'verified', False, True), # User is already verified - (True, 'honor', True, False), # User isn't trying to upgrade - (True, 'honor', False, True), # User is trying to upgrade - (True, 'audit', True, False), # User isn't trying to upgrade - (True, 'audit', False, True), # User is trying to upgrade - (False, 'verified', True, False), # User isn't active - (False, 'verified', False, False), # User isn't active - (False, 'honor', True, False), # User isn't active - (False, 'honor', False, False), # User isn't active - (False, 'audit', True, False), # User isn't active - (False, 'audit', False, False), # User isn't active + # is_active?, enrollment_mode, upgrade?, redirect? auto_register? + (True, 'verified', True, True, False), # User is already verified + (True, 'verified', False, True, False), # User is already verified + (True, 'honor', True, False, False), # User isn't trying to upgrade + (True, 'honor', False, True, False), # User is trying to upgrade + (True, 'audit', True, False, False), # User isn't trying to upgrade + (True, 'audit', False, True, False), # User is trying to upgrade + (False, 'verified', True, False, False), # User isn't active + (False, 'verified', False, False, False), # User isn't active + (False, 'honor', True, False, False), # User isn't active + (False, 'honor', False, False, False), # User isn't active + (False, 'audit', True, False, False), # User isn't active + (False, 'audit', False, False, False), # User isn't active + + # When auto-registration is enabled, users may already be + # registered when they reach the "choose your track" + # page. In this case, we do NOT want to redirect them + # to the dashboard, because we want to give them the option + # to enter the verification/payment track. + # TODO (ECOM-16): based on the outcome of the auto-registration AB test, + # either keep these tests or remove them. In either case, + # remove the "auto_register" flag from this test case. + (True, 'verified', True, False, True), + (True, 'verified', False, True, True), + (True, 'honor', True, False, True), + (True, 'honor', False, False, True), + (True, 'audit', True, False, True), + (True, 'audit', False, False, True), ) @ddt.unpack - @patch('course_modes.views.modulestore', Mock()) - def test_reregister_redirect(self, is_active, enrollment_mode, upgrade, redirect): - enrollment = CourseEnrollmentFactory( + def test_redirect_to_dashboard(self, is_active, enrollment_mode, upgrade, redirect, auto_register): + + # TODO (ECOM-16): Remove once we complete the auto-reg AB test. + if auto_register: + session = self.client.session + session['auto_register'] = True + session.save() + + # Create the course modes + for mode in ('audit', 'honor', 'verified'): + CourseModeFactory(mode_slug=mode, course_id=self.course.id) + + # Enroll the user in the test course + CourseEnrollmentFactory( is_active=is_active, mode=enrollment_mode, - course_id=self.course_id - ) - - self.client.login( - username=enrollment.user.username, - password='test' + course_id=self.course.id, + user=self.user ) + # Configure whether we're upgrading or not + get_params = {} if upgrade: get_params = {'upgrade': True} - else: - get_params = {} - response = self.client.get( - reverse('course_modes_choose', args=[self.course_id.to_deprecated_string()]), - get_params, - follow=False, - ) + url = reverse('course_modes_choose', args=[unicode(self.course.id)]) + response = self.client.get(url, get_params) + # Check whether we were correctly redirected if redirect: - self.assertEquals(response.status_code, 302) - self.assertTrue(response['Location'].endswith(reverse('dashboard'))) + self.assertRedirects(response, reverse('dashboard')) else: self.assertEquals(response.status_code, 200) - # TODO: Fix it so that response.templates works w/ mako templates, and then assert - # that the right template rendered - @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms') @ddt.data( '', '1,,2', '1, ,2', '1, 2, 3' ) - @patch('course_modes.views.modulestore', Mock()) def test_suggested_prices(self, price_list): - course_id = SlashSeparatedCourseKey('org', 'course', 'price_course') - user = UserFactory() + # Create the course modes for mode in ('audit', 'honor'): - CourseModeFactory(mode_slug=mode, course_id=course_id) + CourseModeFactory(mode_slug=mode, course_id=self.course.id) - CourseModeFactory(mode_slug='verified', course_id=course_id, suggested_prices=price_list) - - self.client.login( - username=user.username, - password='test' + CourseModeFactory( + mode_slug='verified', + course_id=self.course.id, + suggested_prices=price_list ) + # Verify that the prices render correctly response = self.client.get( - reverse('course_modes_choose', args=[self.course_id.to_deprecated_string()]), + reverse('course_modes_choose', args=[unicode(self.course.id)]), follow=False, ) @@ -99,44 +122,84 @@ class CourseModeViewTest(TestCase): # TODO: Fix it so that response.templates works w/ mako templates, and then assert # that the right template rendered + # TODO (ECOM-16): Remove the auto-registration flag once the AB test is complete + # and we choose the winner as the default + @ddt.data(True, False) + def test_professional_registration(self, auto_register): -class ProfessionalModeViewTest(TestCase): - """ - Tests for redirects specific to the 'professional' course mode. - Can't really put this in the ddt-style tests in CourseModeViewTest, - since 'professional' mode implies it is the *only* mode for a course - """ - def setUp(self): - self.course_id = SlashSeparatedCourseKey('org', 'course', 'run') - CourseModeFactory(mode_slug='professional', course_id=self.course_id) - self.user = UserFactory() + # TODO (ECOM-16): Remove once we complete the auto-reg AB test. + if auto_register: + self.client.session['auto_register'] = True + self.client.session.save() - @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms') - def test_professional_registration(self): - self.client.login( - username=self.user.username, - password='test' - ) + # The only course mode is professional ed + CourseModeFactory(mode_slug='professional', course_id=self.course.id) - response = self.client.get( - reverse('course_modes_choose', args=[self.course_id.to_deprecated_string()]), - follow=False, - ) + # Go to the "choose your track" page + choose_track_url = reverse('course_modes_choose', args=[unicode(self.course.id)]) + response = self.client.get(choose_track_url) - self.assertEquals(response.status_code, 302) - self.assertTrue(response['Location'].endswith(reverse('verify_student_show_requirements', args=[unicode(self.course_id)]))) + # Expect that we're redirected immediately to the "show requirements" page + # (since the only available track is professional ed) + show_reqs_url = reverse('verify_student_show_requirements', args=[unicode(self.course.id)]) + self.assertRedirects(response, show_reqs_url) + # Now enroll in the course CourseEnrollmentFactory( user=self.user, is_active=True, mode="professional", - course_id=unicode(self.course_id), + course_id=unicode(self.course.id), ) - response = self.client.get( - reverse('course_modes_choose', args=[self.course_id.to_deprecated_string()]), - follow=False, - ) + # Expect that this time we're redirected to the dashboard (since we're already registered) + response = self.client.get(choose_track_url) + self.assertRedirects(response, reverse('dashboard')) - self.assertEquals(response.status_code, 302) - self.assertTrue(response['Location'].endswith(reverse('dashboard'))) + + # Mapping of course modes to the POST parameters sent + # when the user chooses that mode. + POST_PARAMS_FOR_COURSE_MODE = { + 'audit': {'audit_mode': True}, + 'honor': {'honor-code': True}, + 'verified': {'certificate_mode': True} + } + + # TODO (ECOM-16): Remove the auto-register flag once the AB-test completes + # and we default it to enabled or disabled. + @ddt.data( + (False, 'honor', 'dashboard'), + (False, 'verified', 'show_requirements'), + (False, 'audit', 'dashboard'), + (True, 'honor', 'dashboard'), + (True, 'verified', 'show_requirements'), + (True, 'audit', 'dashboard'), + ) + @ddt.unpack + def test_choose_mode_redirect(self, auto_register, course_mode, expected_redirect): + + # TODO (ECOM-16): Remove once we complete the auto-reg AB test. + if auto_register: + self.client.session['auto_register'] = True + self.client.session.save() + + # Create the course modes + for mode in ('audit', 'honor', 'verified'): + CourseModeFactory(mode_slug=mode, course_id=self.course.id) + + # Choose the mode (POST request) + choose_track_url = reverse('course_modes_choose', args=[unicode(self.course.id)]) + resp = self.client.post(choose_track_url, self.POST_PARAMS_FOR_COURSE_MODE[course_mode]) + + # Verify the redirect + if expected_redirect == 'dashboard': + redirect_url = reverse('dashboard') + elif expected_redirect == 'show_requirements': + redirect_url = reverse( + 'verify_student_show_requirements', + kwargs={'course_id': unicode(self.course.id)} + ) + "?upgrade=False" + else: + self.fail("Must provide a valid redirect URL name") + + self.assertRedirects(resp, redirect_url) diff --git a/common/djangoapps/course_modes/views.py b/common/djangoapps/course_modes/views.py index 0aa242b4b2..d5b9932842 100644 --- a/common/djangoapps/course_modes/views.py +++ b/common/djangoapps/course_modes/views.py @@ -30,20 +30,52 @@ class ChooseModeView(View): from the selection page, parses the response, and then sends user to the next step in the flow """ + @method_decorator(login_required) def get(self, request, course_id, error=None): - """ Displays the course mode choice page """ + """ Displays the course mode choice page + Args: + request (`Request`): The Django Request object. + course_id (unicode): The slash-separated course key. + + Keyword Args: + error (unicode): If provided, display this error message + on the page. + + Returns: + Response + + """ course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) enrollment_mode, is_active = CourseEnrollment.enrollment_mode_for_user(request.user, course_key) upgrade = request.GET.get('upgrade', False) request.session['attempting_upgrade'] = upgrade + # TODO (ECOM-16): Remove once the AB-test of auto-registration completes + auto_register = request.session.get('auto_register', False) + # Inactive users always need to re-register - # verified and professional users do not need to register or upgrade - # registered users who are not trying to upgrade do not need to re-register - if is_active and (upgrade is False or enrollment_mode == 'verified' or enrollment_mode == 'professional'): + # Verified and professional users do not need to register or upgrade + # Registered users who are not trying to upgrade do not need to re-register + if not auto_register: + go_to_dashboard = ( + is_active and + (not upgrade or enrollment_mode in ['verified', 'professional']) + ) + + # If auto-registration is enabled, then students might already be registered, + # but we should still show them the "choose your track" page so they have + # the option to enter the verification/payment flow. + # TODO (ECOM-16): Based on the results of the AB-test, set the default behavior to + # either enable or disable auto-registration. + else: + go_to_dashboard = ( + not upgrade and enrollment_mode in ['verified', 'professional'] + ) + + if go_to_dashboard: return redirect(reverse('dashboard')) modes = CourseMode.modes_for_course_dict(course_key) @@ -73,6 +105,7 @@ class ChooseModeView(View): "error": error, "upgrade": upgrade, "can_audit": "audit" in modes, + "autoreg": auto_register } if "verified" in modes: context["suggested_prices"] = [ diff --git a/common/djangoapps/external_auth/tests/test_shib.py b/common/djangoapps/external_auth/tests/test_shib.py index acf095ee8d..4397ba7e35 100644 --- a/common/djangoapps/external_auth/tests/test_shib.py +++ b/common/djangoapps/external_auth/tests/test_shib.py @@ -13,6 +13,7 @@ from django.test.client import RequestFactory, Client as DjangoTestClient from django.test.utils import override_settings from django.core.urlresolvers import reverse from django.contrib.auth.models import AnonymousUser, User +from django.contrib.sessions.middleware import SessionMiddleware from django.utils.importlib import import_module from xmodule.modulestore.tests.factories import CourseFactory @@ -513,6 +514,11 @@ class ShibSPTest(ModuleStoreTestCase): for course in [shib_course, open_enroll_course]: for student in [shib_student, other_ext_student, int_student]: request = self.request_factory.post('/change_enrollment') + + # Add a session to the request + SessionMiddleware().process_request(request) + request.session.save() + request.POST.update({'enrollment_action': 'enroll', 'course_id': course.id.to_deprecated_string()}) request.user = student diff --git a/common/djangoapps/student/tests/test_enrollment.py b/common/djangoapps/student/tests/test_enrollment.py new file mode 100644 index 0000000000..83fb38a58b --- /dev/null +++ b/common/djangoapps/student/tests/test_enrollment.py @@ -0,0 +1,175 @@ +""" +Tests for student enrollment. +""" +from datetime import datetime, timedelta +import pytz +import ddt +import unittest + +from django.test.utils import override_settings +from django.conf import settings +from django.core.urlresolvers import reverse +from xmodule.modulestore.tests.django_utils import ( + ModuleStoreTestCase, mixed_store_config +) +from xmodule.modulestore.tests.factories import CourseFactory +from student.tests.factories import UserFactory, CourseModeFactory +from student.models import CourseEnrollment + + +# Since we don't need any XML course fixtures, use a modulestore configuration +# that disables the XML modulestore. +MODULESTORE_CONFIG = mixed_store_config(settings.COMMON_TEST_DATA_ROOT, {}, include_xml=False) + + +@ddt.ddt +@override_settings(MODULESTORE=MODULESTORE_CONFIG) +@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms') +class EnrollmentTest(ModuleStoreTestCase): + """ + Test student enrollment, especially with different course modes. + """ + def setUp(self): + """ Create a course and user, then log in. """ + super(EnrollmentTest, self).setUp() + self.course = CourseFactory.create() + self.user = UserFactory.create(username="Bob", email="bob@example.com", password="edx") + self.client.login(username=self.user.username, password="edx") + + self.urls = [ + reverse('course_modes_choose', kwargs={'course_id': unicode(self.course.id)}) + ] + + # TODO (ECOM-16): We need separate test cases for both conditions in the auto-registration + # AB-test. Once we get the results of that test, we should + # remove the losing condition from this test. + @ddt.data( + # Default (no course modes in the database) + # Expect that we're redirected to the dashboard + # and automatically enrolled as "honor" + ([], '', 'honor', False), + ([], '', 'honor', True), + + # Audit / Verified / Honor + # We should always go to the "choose your course" page, + # If auto-registration is enabled, we should also be registered + # as "honor" by default. + (['honor', 'verified', 'audit'], 'course_modes_choose', None, False), + (['honor', 'verified', 'audit'], 'course_modes_choose', 'honor', True), + + # Professional ed + # Expect that we're sent to the "choose your track" page + # (which will, in turn, redirect us to a page where we can verify/pay) + # Even if auto registration is enabled, we should NOT be auto-registered, + # because that would be giving away an expensive course for free :) + (['professional'], 'course_modes_choose', None, False), + (['professional'], 'course_modes_choose', None, True), + + ) + @ddt.unpack + def test_enroll(self, course_modes, next_url, enrollment_mode, auto_reg): + # Create the course modes (if any) required for this test case + for mode_slug in course_modes: + CourseModeFactory.create( + course_id=self.course.id, + mode_slug=mode_slug, + mode_display_name=mode_slug, + expiration_datetime=datetime.now(pytz.UTC) + timedelta(days=1) + ) + + # Reverse the expected next URL, if one is provided + # (otherwise, use an empty string, which the JavaScript client + # interprets as a redirect to the dashboard) + full_url = ( + reverse(next_url, kwargs={'course_id': unicode(self.course.id)}) + if next_url else next_url + ) + + # Enroll in the course and verify the URL we get sent to + resp = self._change_enrollment('enroll', auto_reg=auto_reg) + self.assertEqual(resp.status_code, 200) + self.assertEqual(resp.content, full_url) + + # TODO (ECOM-16): If auto-registration is enabled, check that we're + # storing the auto-reg flag in the user's session + if auto_reg: + self.assertIn('auto_register', self.client.session) + self.assertTrue(self.client.session['auto_register']) + + # If we're not expecting to be enrolled, verify that this is the case + if enrollment_mode is None: + self.assertFalse(CourseEnrollment.is_enrolled(self.user, self.course.id)) + + # Otherwise, verify that we're enrolled with the expected course mode + else: + 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, enrollment_mode) + + def test_unenroll(self): + # Enroll the student in the course + CourseEnrollment.enroll(self.user, self.course.id, mode="honor") + + # Attempt to unenroll the student + resp = self._change_enrollment('unenroll') + self.assertEqual(resp.status_code, 200) + + # Expect that we're no longer enrolled + self.assertFalse(CourseEnrollment.is_enrolled(self.user, self.course.id)) + + def test_user_not_authenticated(self): + # Log out, so we're no longer authenticated + self.client.logout() + + # Try to enroll, expecting a forbidden response + resp = self._change_enrollment('enroll') + self.assertEqual(resp.status_code, 403) + + def test_missing_course_id_param(self): + resp = self.client.post( + reverse('change_enrollment'), + {'enrollment_action': 'enroll'} + ) + self.assertEqual(resp.status_code, 400) + + def test_unenroll_not_enrolled_in_course(self): + # Try unenroll without first enrolling in the course + resp = self._change_enrollment('unenroll') + self.assertEqual(resp.status_code, 400) + + def test_invalid_enrollment_action(self): + resp = self._change_enrollment('not_an_action') + self.assertEqual(resp.status_code, 400) + + def _change_enrollment(self, action, course_id=None, auto_reg=False): + """ + Change the student's enrollment status in a course. + + Args: + action (string): The action to perform (either "enroll" or "unenroll") + + Keyword Args: + course_id (unicode): If provided, use this course ID. Otherwise, use the + course ID created in the setup for this test. + + auto_reg (boolean): Whether to use the auto-registration hook. + TODO (ECOM-16): remove this once we complete the AB test for auto-registration. + + Returns: + Response + + """ + if course_id is None: + course_id = unicode(self.course.id) + + url = ( + reverse('change_enrollment') + if not auto_reg + else reverse('change_enrollment_autoreg') + ) + params = { + 'enrollment_action': action, + 'course_id': course_id + } + return self.client.post(url, params) diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index 4abdc0861c..a7a389359e 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -582,7 +582,7 @@ def try_change_enrollment(request): @require_POST -def change_enrollment(request): +def change_enrollment(request, auto_register=False): """ Modify the enrollment status for the logged-in user. @@ -598,6 +598,24 @@ def change_enrollment(request): happens. This function should only be called from an AJAX request or as a post-login/registration helper, so the error messages in the responses should never actually be user-visible. + The original version of the change enrollment handler, + which does NOT perform auto-registration. + + TODO (ECOM-16): We created a second variation of this handler that performs + auto-registration for an AB-test. Depending on the results of that test, + we should make the winning implementation the default. + + Args: + request (`Request`): The Django request object + + Keyword Args: + auto_register (boolean): If True, auto-register the user + for a default course mode when they first enroll + before sending them to the "choose your track" page + + Returns: + Response + """ user = request.user @@ -635,24 +653,79 @@ def change_enrollment(request): _("Student is already enrolled") ) - # If this course is available in multiple modes, redirect them to a page - # where they can choose which mode they want. - available_modes = CourseMode.modes_for_course(course_id) - if len(available_modes) > 1: - return HttpResponse( - reverse("course_modes_choose", kwargs={'course_id': unicode(course_id)}) - ) + # We use this flag to determine which condition of an AB-test + # for auto-registration we're currently in. + # (We have two URLs that both point to this view, but vary the + # value of `auto_register`) + # In the auto-registration case, we automatically register the student + # as "honor" before allowing them to choose a track. + # TODO (ECOM-16): Once the auto-registration AB-test is complete, delete + # one of these two conditions and remove the `auto_register` flag. + if auto_register: + # TODO (ECOM-16): This stores a flag in the session so downstream + # views will recognize that the user is in the "auto-registration" + # experimental condition. We can remove this once the AB test completes. + request.session['auto_register'] = True - current_mode = available_modes[0] - # only automatically enroll people if the only mode is 'honor' - if current_mode.slug != 'honor': - return HttpResponse( - reverse("course_modes_choose", kwargs={'course_id': unicode(course_id)}) - ) + available_modes = CourseMode.modes_for_course_dict(course_id) - CourseEnrollment.enroll(user, course.id, mode=current_mode.slug) + # Handle professional ed as a special case. + # If professional ed is included in the list of available modes, + # then do NOT automatically enroll the student (we want them to pay first!) + # By convention, professional ed should be the *only* available course mode, + # if it's included at all -- anything else is a misconfiguration. But if someone + # messes up and adds an additional course mode, we err on the side of NOT + # accidentally giving away free courses. + if "professional" not in available_modes: + # Enroll the user using the default mode (honor) + # We're assuming that users of the course enrollment table + # will NOT try to look up the course enrollment model + # by its slug. If they do, it's possible (based on the state of the database) + # for no such model to exist, even though we've set the enrollment type + # to "honor". + CourseEnrollment.enroll(user, course.id) + + # If we have more than one course mode or professional ed is enabled, + # then send the user to the choose your track page. + # (In the case of professional ed, this will redirect to a page that + # funnels users directly into the verification / payment flow) + if len(available_modes) > 1 or "professional" in available_modes: + return HttpResponse( + reverse("course_modes_choose", kwargs={'course_id': unicode(course_id)}) + ) + + # Otherwise, there is only one mode available (the default) + return HttpResponse() + + # If auto-registration is disabled, do NOT register the student + # before sending them to the "choose your track" page. + # This is the control for the auto-registration AB-test. + else: + # TODO (ECOM-16): If the user is NOT in the experimental condition, + # make sure their session reflects this. We can remove this + # once the AB test completes. + if 'auto_register' in request.session: + del request.session['auto_register'] + + # If this course is available in multiple modes, redirect them to a page + # where they can choose which mode they want. + available_modes = CourseMode.modes_for_course(course_id) + if len(available_modes) > 1: + return HttpResponse( + reverse("course_modes_choose", kwargs={'course_id': unicode(course_id)}) + ) + + current_mode = available_modes[0] + # only automatically enroll people if the only mode is 'honor' + if current_mode.slug != 'honor': + return HttpResponse( + reverse("course_modes_choose", kwargs={'course_id': unicode(course_id)}) + ) + + CourseEnrollment.enroll(user, course.id, mode=current_mode.slug) + + return HttpResponse() - return HttpResponse() elif action == "add_to_cart": # Pass the request handling to shoppingcart.views diff --git a/common/lib/xmodule/xmodule/modulestore/tests/django_utils.py b/common/lib/xmodule/xmodule/modulestore/tests/django_utils.py index 6a6d8843d3..59d2303da0 100644 --- a/common/lib/xmodule/xmodule/modulestore/tests/django_utils.py +++ b/common/lib/xmodule/xmodule/modulestore/tests/django_utils.py @@ -15,36 +15,45 @@ from xmodule.tabs import CoursewareTab, CourseInfoTab, StaticTab, DiscussionTab, from xmodule.modulestore.tests.sample_courses import default_block_info_tree, TOY_BLOCK_INFO_TREE -def mixed_store_config(data_dir, mappings): +def mixed_store_config(data_dir, mappings, include_xml=True): """ Return a `MixedModuleStore` configuration, which provides access to both Mongo- and XML-backed courses. - `data_dir` is the directory from which to load XML-backed courses. - `mappings` is a dictionary mapping course IDs to modulestores, for example: + Args: + data_dir (string): the directory from which to load XML-backed courses. + mappings (string): a dictionary mapping course IDs to modulestores, for example: - { - 'MITx/2.01x/2013_Spring': 'xml', - 'edx/999/2013_Spring': 'default' - } + { + 'MITx/2.01x/2013_Spring': 'xml', + 'edx/999/2013_Spring': 'default' + } + + where 'xml' and 'default' are the two options provided by this configuration, + mapping (respectively) to XML-backed and Mongo-backed modulestores.. + + Keyword Args: + + include_xml (boolean): If True, include an XML modulestore in the configuration. + Note that this will require importing multiple XML courses from disk, + so unless your tests really needs XML course fixtures or is explicitly + testing mixed modulestore, set this to False. - where 'xml' and 'default' are the two options provided by this configuration, - mapping (respectively) to XML-backed and Mongo-backed modulestores.. """ - draft_mongo_config = draft_mongo_store_config(data_dir) - xml_config = xml_store_config(data_dir) - split_mongo = split_mongo_store_config(data_dir) + stores = [ + draft_mongo_store_config(data_dir)['default'], + split_mongo_store_config(data_dir)['default'] + ] + + if include_xml: + stores.append(xml_store_config(data_dir)['default']) store = { 'default': { 'ENGINE': 'xmodule.modulestore.mixed.MixedModuleStore', 'OPTIONS': { 'mappings': mappings, - 'stores': [ - draft_mongo_config['default'], - split_mongo['default'], - xml_config['default'], - ] + 'stores': stores, } } } diff --git a/lms/djangoapps/verify_student/views.py b/lms/djangoapps/verify_student/views.py index 0400f7c269..1d28c2665c 100644 --- a/lms/djangoapps/verify_student/views.py +++ b/lms/djangoapps/verify_student/views.py @@ -111,6 +111,9 @@ class VerifyView(View): "upgrade": upgrade == u'True', "can_audit": CourseMode.mode_for_course(course_id, 'audit') is not None, "modes_dict": CourseMode.modes_for_course_dict(course_id), + + # TODO (ECOM-16): Remove once the AB test completes + "autoreg": request.session.get('auto_register', False), } return render_to_response('verify_student/photo_verification.html', context) @@ -160,6 +163,9 @@ class VerifiedView(View): "upgrade": upgrade == u'True', "can_audit": "audit" in modes_dict, "modes_dict": modes_dict, + + # TODO (ECOM-16): Remove once the AB test completes + "autoreg": request.session.get('auto_register', False), } return render_to_response('verify_student/verified.html', context) @@ -326,6 +332,9 @@ def show_requirements(request, course_id): "is_not_active": not request.user.is_active, "upgrade": upgrade == u'True', "modes_dict": modes_dict, + + # TODO (ECOM-16): Remove once the AB test completes + "autoreg": request.session.get('auto_register', False), } return render_to_response("verify_student/show_requirements.html", context) diff --git a/lms/urls.py b/lms/urls.py index 05df6193b4..d3d220c289 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -224,6 +224,17 @@ if settings.COURSEWARE_ENABLED: 'student.views.change_enrollment', name="change_enrollment"), url(r'^change_email_settings$', 'student.views.change_email_settings', name="change_email_settings"), + # Used for an AB-test of auto-registration + # TODO (ECOM-16): Based on the AB-test, update the default behavior and change + # this URL to point to the original view. Eventually, this URL + # should be removed, but not the AB test completes. + url( + r'^change_enrollment_autoreg$', + 'student.views.change_enrollment', + {'auto_register': True}, + name="change_enrollment_autoreg", + ), + #About the course url(r'^courses/{}/about$'.format(settings.COURSE_ID_PATTERN), 'courseware.views.course_about', name="about_course"),