Merge pull request #4824 from edx/will/auto-register
Auto enroll students even if there are multiple course modes
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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"] = [
|
||||
|
||||
@@ -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
|
||||
|
||||
175
common/djangoapps/student/tests/test_enrollment.py
Normal file
175
common/djangoapps/student/tests/test_enrollment.py
Normal file
@@ -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)
|
||||
@@ -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
|
||||
|
||||
@@ -15,36 +15,45 @@ from xmodule.modulestore.tests.sample_courses import default_block_info_tree, TO
|
||||
from xmodule.modulestore.tests.mongo_connection import MONGO_PORT_NUM, MONGO_HOST
|
||||
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,6 +67,11 @@ $(document).ready(function() {
|
||||
|
||||
<%include file="/verify_student/_verification_header.html" args="course_name=course_name" />
|
||||
|
||||
## TODO (ECOM-16): This is part of an AB-test of auto-registration.
|
||||
## Once the test completes, we can make the winning configuration the default
|
||||
## and remove this flag.
|
||||
%if not autoreg:
|
||||
<!-- /experiment-control -->
|
||||
<div class="wrapper-register-choose wrapper-content-main">
|
||||
<article class="register-choose content-main">
|
||||
|
||||
@@ -214,6 +219,92 @@ $(document).ready(function() {
|
||||
</article>
|
||||
</div> <!-- /wrapper-content-main -->
|
||||
|
||||
%else:
|
||||
<!-- /experiment-variant-A -->
|
||||
<div class="wrapper-register-choose wrapper-content-main exp-variant-A">
|
||||
<article class="register-choose content-main">
|
||||
|
||||
%if not upgrade:
|
||||
<h3 class="title">${_("Now choose your course track:")}</h3>
|
||||
%endif
|
||||
|
||||
<form class="form-register-choose" method="post" name="enrollment_mode_form" id="enrollment_mode_form">
|
||||
|
||||
% if "verified" in modes:
|
||||
<div class="register-choice register-choice-certificate">
|
||||
<div class="wrapper-copy">
|
||||
<span class="deco-ribbon"></span>
|
||||
<h4 class="title">${_("Pursue a Verified Certificate")}</h4>
|
||||
|
||||
%if upgrade:
|
||||
<div class="copy">
|
||||
<p>${_("Plan to use your completed coursework for job applications, career advancement, or school applications? Upgrade to work toward a Verified Certificate of Achievement to document your accomplishment. A minimum fee applies.")}</p>
|
||||
</div>
|
||||
%else:
|
||||
<div class="copy">
|
||||
<p>${_("Plan to use your completed coursework for job applications, career advancement, or school applications? Then work toward a Verified Certificate of Achievement to document your accomplishment. A minimum fee applies.")}</p>
|
||||
</div>
|
||||
%endif
|
||||
</div>
|
||||
|
||||
<div class="field field-certificate-contribution">
|
||||
<h5 class="label">${_("Select your contribution for this course (min. $")} ${min_price} <span class="denomination-name">${currency}</span>${_("):")}</h5>
|
||||
|
||||
%if error:
|
||||
<div class="msg msg-error msg-inline">
|
||||
<div class="copy">
|
||||
<p><i class="msg-icon icon-warning-sign"></i> ${error}</p>
|
||||
</div>
|
||||
</div>
|
||||
%endif
|
||||
|
||||
<%include file="_contribution.html" args="suggested_prices=suggested_prices, currency=currency, chosen_price=chosen_price, min_price=min_price"/>
|
||||
|
||||
<ul class="list-actions">
|
||||
<li class="action action-select">
|
||||
%if upgrade:
|
||||
<input type="submit" name="certificate_mode" value="${_('Upgrade Your Registration')}" />
|
||||
%else:
|
||||
<input type="submit" name="certificate_mode" value="${_('Pursue a Verified Certificate')}" />
|
||||
%endif
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
% endif
|
||||
|
||||
%if not upgrade:
|
||||
|
||||
% if "audit" in modes:
|
||||
<span class="deco-divider">
|
||||
<span class="copy">${_("or")}</span>
|
||||
</span>
|
||||
<div class="register-choice register-choice-audit">
|
||||
<div class="wrapper-copy">
|
||||
<h4 class="title">${_("Audit This Course")}</h4>
|
||||
<div class="copy">
|
||||
<p>${_("You can audit this course and still have complete access to the course material. If you complete and pass the course you will automatically earn an Honor Code Certificate free of charge.")}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul class="list-actions">
|
||||
<li class="action action-select">
|
||||
<input type="submit" name="audit_mode" value="${_('Pursue an Honor Code Certificate')}" />
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
% endif
|
||||
|
||||
%endif
|
||||
|
||||
<input type="hidden" name="csrfmiddlewaretoken" value="${ csrf_token }">
|
||||
</form>
|
||||
</article>
|
||||
</div> <!-- /wrapper-content-main -->
|
||||
%endif
|
||||
|
||||
<%include file="/verify_student/_verification_support.html" />
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ FEATURES['ENABLE_SHOPPING_CART'] = True
|
||||
FEATURES['AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING'] = True
|
||||
FEATURES['ENABLE_S3_GRADE_DOWNLOADS'] = True
|
||||
FEATURES['IS_EDX_DOMAIN'] = True # Is this an edX-owned domain? (used on instructor dashboard)
|
||||
FEATURES['ENABLE_PAYMENT_FAKE'] = True
|
||||
|
||||
|
||||
FEEDBACK_SUBMISSION_EMAIL = "dummy@example.com"
|
||||
@@ -280,7 +281,8 @@ if SEGMENT_IO_LMS_KEY:
|
||||
CC_PROCESSOR['CyberSource']['SHARED_SECRET'] = os.environ.get('CYBERSOURCE_SHARED_SECRET', '')
|
||||
CC_PROCESSOR['CyberSource']['MERCHANT_ID'] = os.environ.get('CYBERSOURCE_MERCHANT_ID', '')
|
||||
CC_PROCESSOR['CyberSource']['SERIAL_NUMBER'] = os.environ.get('CYBERSOURCE_SERIAL_NUMBER', '')
|
||||
CC_PROCESSOR['CyberSource']['PURCHASE_ENDPOINT'] = os.environ.get('CYBERSOURCE_PURCHASE_ENDPOINT', '')
|
||||
#CC_PROCESSOR['CyberSource']['PURCHASE_ENDPOINT'] = os.environ.get('CYBERSOURCE_PURCHASE_ENDPOINT', '')
|
||||
CC_PROCESSOR['CyberSource']['PURCHASE_ENDPOINT'] = '/shoppingcart/payment_fake/'
|
||||
|
||||
########################## USER API ##########################
|
||||
EDX_API_KEY = None
|
||||
|
||||
@@ -415,6 +415,57 @@
|
||||
}
|
||||
}
|
||||
|
||||
// CASE: page header - experiment variant A overrides
|
||||
.page-header.exp-variant-A {
|
||||
margin-bottom: 0;
|
||||
border-bottom: none;
|
||||
|
||||
.title {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
|
||||
.sts-label {
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
border: none;
|
||||
padding: 0;
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
.sts-course-org {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.sts-label, .sts-course-org, .sts-course-number, .sts-course-name {
|
||||
@extend %t-title5;
|
||||
@extend %t-weight4;
|
||||
@include font-size(14);
|
||||
@include line-height(14);
|
||||
display: inline-block;
|
||||
color: $gray;
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
.wrapper-sts {
|
||||
display: inline-block;
|
||||
width: flex-grid(9,12);
|
||||
margin-bottom: ($baseline/4);
|
||||
}
|
||||
|
||||
.sts-course {
|
||||
width: initial;
|
||||
}
|
||||
|
||||
.title .sts-track {
|
||||
display: inline-block;
|
||||
|
||||
.sts-track-value {
|
||||
background: $m-blue-l1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ====================
|
||||
|
||||
// UI : progress
|
||||
@@ -1123,6 +1174,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
// CASE: supplemental content - experiment variant A overrides
|
||||
.wrapper-content-supplementary.exp-variant-A {
|
||||
|
||||
.help-item-technical {
|
||||
width: flex-grid(8,12);
|
||||
}
|
||||
}
|
||||
|
||||
// ====================
|
||||
|
||||
// VIEW: select a track
|
||||
@@ -1192,11 +1251,11 @@
|
||||
border-color: $m-blue-d1;
|
||||
|
||||
.wrapper-copy {
|
||||
width: flex-grid(5,8);
|
||||
width: flex-grid(8,8);
|
||||
}
|
||||
|
||||
.list-actions {
|
||||
width: flex-grid(3,8);
|
||||
width: flex-grid(8,8);
|
||||
}
|
||||
|
||||
.action-select input {
|
||||
@@ -1221,7 +1280,6 @@
|
||||
.list-actions {
|
||||
margin: ($baseline/2) 0;
|
||||
border-top: ($baseline/10) solid $m-gray-t1;
|
||||
padding-top: $baseline;
|
||||
}
|
||||
|
||||
.action-intro, .action-select {
|
||||
@@ -1336,6 +1394,39 @@
|
||||
}
|
||||
}
|
||||
|
||||
// CASE: select a track - experiment variant A overrides
|
||||
.wrapper-register-choose.exp-variant-A {
|
||||
|
||||
.register-choice {
|
||||
width: flex-grid(12,12);
|
||||
}
|
||||
|
||||
.deco-divider{
|
||||
width: flex-grid(12,12);
|
||||
}
|
||||
|
||||
.contribution-options {
|
||||
width: flex-grid(8,12);
|
||||
margin: 0;
|
||||
|
||||
&:after{
|
||||
clear: none;
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.register-choice-certificate .list-actions {
|
||||
border-top: none;
|
||||
width: flex-grid(4,12);
|
||||
float: right;
|
||||
margin: ($baseline/4) 0;
|
||||
|
||||
.action-select {
|
||||
width: initial;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// VIEW: requirements
|
||||
&.step-requirements {
|
||||
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
|
||||
## TODO (ECOM-16): This is part of an AB-test of auto-registration.
|
||||
## Once the test completes, we can make the winning configuration the default
|
||||
## and remove this flag.
|
||||
%if not autoreg:
|
||||
<!-- /experiment-control -->
|
||||
<header class="page-header">
|
||||
<h2 class="title">
|
||||
%if upgrade:
|
||||
@@ -31,3 +36,33 @@
|
||||
</span>
|
||||
</h2>
|
||||
</header>
|
||||
|
||||
%else:
|
||||
<!-- /experiment-variant-A -->
|
||||
<header class="page-header exp-variant-A">
|
||||
<h2 class="title">
|
||||
<span class="wrapper-sts">
|
||||
%if upgrade:
|
||||
<span class="sts-label">${_("You are upgrading your registration for")}</span>
|
||||
%elif reverify:
|
||||
<span class="sts-label">${_("You are re-verifying for")}</span>
|
||||
%else:
|
||||
<span class="sts-label">${_("Congrats! You are now registered to audit")}</span>
|
||||
%endif
|
||||
<span class="sts-course-org">${course_org}'s</span>
|
||||
<span class="sts-course-number">${course_num}</span>
|
||||
<span class="sts-course-name">${course_name}.</span>
|
||||
</span>
|
||||
<span class="sts-track">
|
||||
<span class="sts-track-value">
|
||||
%if upgrade:
|
||||
<span class="context">${_("Upgrading to:")}</span> ${_("ID Verified")}
|
||||
%elif reverify:
|
||||
<span class="context">${_("Re-verifying for:")}</span> ${_("ID Verified")}
|
||||
%else:
|
||||
<span class="context">${_("Registering as: ")}</span> ${_("ID Verified")}
|
||||
%endif
|
||||
</span>
|
||||
</h2>
|
||||
</header>
|
||||
%endif
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
|
||||
## TODO (ECOM-16): This is part of an AB-test of auto-registration.
|
||||
## Once the test completes, we can make the winning configuration the default
|
||||
## and remove this flag.
|
||||
%if not autoreg:
|
||||
<!-- /experiment-control -->
|
||||
<div class="wrapper-content-supplementary">
|
||||
<aside class="content-supplementary">
|
||||
<ul class="list-help">
|
||||
@@ -35,3 +40,26 @@
|
||||
</ul>
|
||||
</aside>
|
||||
</div> <!-- /wrapper-content-supplementary -->
|
||||
|
||||
%else:
|
||||
<!-- /experiment-variant-A -->
|
||||
<div class="wrapper-content-supplementary exp-variant-A">
|
||||
<aside class="content-supplementary">
|
||||
<ul class="list-help">
|
||||
<li class="help-item help-item-questions">
|
||||
<h3 class="title">${_("Have questions?")}</h3>
|
||||
<div class="copy">
|
||||
<p>${_("Please read {a_start}our FAQs to view common questions about our certificates{a_end}.").format(a_start='<a rel="external" href="'+ marketing_link('WHAT_IS_VERIFIED_CERT') + '">', a_end="</a>")}</p>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="help-item help-item-technical">
|
||||
<h3 class="title">${_("Technical Requirements")}</h3>
|
||||
<div class="copy">
|
||||
<p>${_("Please make sure your browser is updated to the {a_start}most recent version possible{a_end}. Also, please make sure your <strong>web cam is plugged in, turned on, and allowed to function in your web browser (commonly adjustable in your browser settings).</strong>").format(a_start='<strong><a rel="external" href="http://browsehappy.com/">', a_end="</a></strong>")}</p>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</aside>
|
||||
</div> <!-- /wrapper-content-supplementary -->
|
||||
%endif
|
||||
|
||||
11
lms/urls.py
11
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"),
|
||||
|
||||
Reference in New Issue
Block a user