Merge pull request #20303 from edx/dcs/masters-access

Automatically add master's to the group access of verified content
This commit is contained in:
Dave St.Germain
2019-04-25 11:00:16 -04:00
committed by GitHub
16 changed files with 166 additions and 55 deletions

View File

@@ -1,3 +1,6 @@
"""Django admin for course_modes"""
from __future__ import absolute_import, unicode_literals
import six
from django import forms
from django.conf import settings
@@ -22,7 +25,8 @@ from lms.djangoapps.verify_student import models as verification_models
from openedx.core.lib.courses import clean_course_id
from util.date_utils import get_time_display
COURSE_MODE_SLUG_CHOICES = [(key, enrollment_mode['display_name']) for key, enrollment_mode in six.iteritems(settings.COURSE_ENROLLMENT_MODES)]
COURSE_MODE_SLUG_CHOICES = [(key, enrollment_mode['display_name'])
for key, enrollment_mode in six.iteritems(settings.COURSE_ENROLLMENT_MODES)]
class CourseModeForm(forms.ModelForm):

View File

@@ -1,6 +1,8 @@
"""
Course modes API serializers.
"""
from __future__ import absolute_import
from rest_framework import serializers
from course_modes.models import CourseMode

View File

@@ -1,8 +1,9 @@
"""
URL definitions for the course_modes API.
"""
from django.conf.urls import include, url
from __future__ import absolute_import
from django.conf.urls import include, url
app_name = 'common.djangoapps.course_modes.api'

View File

@@ -1,6 +1,7 @@
"""
URL definitions for the course_modes v1 API.
"""
from __future__ import absolute_import
from django.conf import settings
from django.conf.urls import url

View File

@@ -2,6 +2,7 @@
Defines the "ReSTful" API for course modes.
"""
from __future__ import absolute_import
import logging
from django.shortcuts import get_object_or_404

View File

@@ -1,3 +1,5 @@
"""Django App config for course_modes"""
from __future__ import absolute_import
from django.apps import AppConfig
@@ -7,4 +9,4 @@ class CourseModesConfig(AppConfig):
verbose_name = "Course Modes"
def ready(self):
import course_modes.signals # pylint: disable=unused-import
import course_modes.signals # pylint: disable=unused-variable

View File

@@ -1,4 +1,7 @@
""" Helper methods for CourseModes. """
from __future__ import absolute_import, unicode_literals
import six
from django.utils.translation import ugettext_lazy as _
from course_modes.models import CourseMode
@@ -48,10 +51,10 @@ def enrollment_mode_display(mode, verification_status, course_id):
enrollment_value = _("Professional Ed")
return {
'enrollment_title': unicode(enrollment_title),
'enrollment_value': unicode(enrollment_value),
'enrollment_title': six.text_type(enrollment_title),
'enrollment_value': six.text_type(enrollment_value),
'show_image': show_image,
'image_alt': unicode(image_alt),
'image_alt': six.text_type(image_alt),
'display_mode': _enrollment_mode_display(mode, verification_status, course_id)
}

View File

@@ -1,9 +1,12 @@
"""
Add and create new modes for running courses on this particular LMS
"""
from __future__ import absolute_import
from collections import defaultdict, namedtuple
from datetime import timedelta
import six
from config_models.models import ConfigurationModel
from django.conf import settings
from django.core.exceptions import ValidationError
@@ -14,8 +17,8 @@ from django.dispatch import receiver
from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _
from edx_django_utils.cache import RequestCache
from opaque_keys.edx.keys import CourseKey
from opaque_keys.edx.django.models import CourseKeyField
from opaque_keys.edx.keys import CourseKey
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from openedx.core.lib.cache_utils import request_cached
@@ -59,7 +62,7 @@ class CourseMode(models.Model):
@course_id.setter
def course_id(self, value):
if isinstance(value, basestring):
if isinstance(value, six.string_types):
self._course_id = CourseKey.from_string(value)
else:
self._course_id = value
@@ -296,7 +299,7 @@ class CourseMode(models.Model):
mode for mode in modes
if mode.expiration_datetime is None or mode.expiration_datetime >= now_dt
]
for course_id, modes in all_modes.iteritems()
for course_id, modes in six.iteritems(all_modes)
}
return (all_modes, unexpired_modes)
@@ -910,4 +913,4 @@ class CourseModeExpirationConfig(ConfigurationModel):
def __unicode__(self):
""" Returns the unicode date of the verification window. """
return unicode(self.verification_window)
return six.text_type(self.verification_window)

View File

@@ -1,13 +1,24 @@
"""
Signal handler for setting default course mode expiration dates
"""
from __future__ import absolute_import, unicode_literals
import logging
from crum import get_current_user
from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist
from django.db.models.signals import post_save
from django.dispatch.dispatcher import receiver
from xmodule.modulestore.django import SignalHandler, modulestore
from xmodule.modulestore.exceptions import ItemNotFoundError
from xmodule.partitions.partitions import ENROLLMENT_TRACK_PARTITION_ID
from .models import CourseMode, CourseModeExpirationConfig
log = logging.getLogger(__name__)
@receiver(SignalHandler.course_published)
def _listen_for_course_publish(sender, course_key, **kwargs): # pylint: disable=unused-argument
@@ -35,3 +46,38 @@ def _listen_for_course_publish(sender, course_key, **kwargs): # pylint: disable
def _should_update_date(verified_mode):
""" Returns whether or not the verified mode should be updated. """
return not(verified_mode is None or verified_mode.expiration_datetime_is_explicit)
@receiver(post_save, sender=CourseMode)
def update_masters_access_course(sender, instance, **kwargs): # pylint: disable=unused-argument
"""
Update all blocks in the verified content group to include the master's content group
"""
if instance.mode_slug != CourseMode.MASTERS:
return
masters_id = getattr(settings, 'COURSE_ENROLLMENT_MODES', {}).get('masters', {}).get('id', None)
verified_id = getattr(settings, 'COURSE_ENROLLMENT_MODES', {}).get('verified', {}).get('id', None)
if not (masters_id and verified_id):
log.error("Missing settings.COURSE_ENROLLMENT_MODES -> verified:%s masters:%s", verified, masters)
return
course_id = instance.course_id
user = get_current_user()
user_id = user.id if user else None
store = modulestore()
with store.bulk_operations(course_id):
try:
items = store.get_items(course_id, settings={'group_access': {'$exists': True}}, include_orphans=False)
except ItemNotFoundError:
return
for item in items:
group_access = item.group_access
enrollment_groups = group_access.get(ENROLLMENT_TRACK_PARTITION_ID, None)
if enrollment_groups is not None:
if verified_id in enrollment_groups and masters_id not in enrollment_groups:
enrollment_groups.append(masters_id)
item.group_access = group_access
log.info("Publishing %s with Master's group access", item.location)
store.update_item(item, user_id)
store.publish(item.location, user_id)

View File

@@ -1,6 +1,8 @@
"""
Factories for course mode models.
"""
from __future__ import absolute_import
import random
from factory import lazy_attribute

View File

@@ -1,10 +1,13 @@
"""
Tests for the course modes Django admin interface.
"""
from __future__ import absolute_import, unicode_literals
import unittest
from datetime import datetime, timedelta
import ddt
import six
from django.conf import settings
from django.urls import reverse
from pytz import UTC, timezone
@@ -12,12 +15,12 @@ from pytz import UTC, timezone
from course_modes.admin import CourseModeForm
from course_modes.models import CourseMode
from course_modes.tests.factories import CourseModeFactory
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
# Technically, we shouldn't be importing verify_student, since it's
# defined in the LMS and course_modes is in common. However, the benefits
# of putting all this configuration in one place outweigh the downsides.
# Once the course admin tool is deployed, we can remove this dependency.
from lms.djangoapps.verify_student.models import VerificationDeadline
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from student.tests.factories import UserFactory
from util.date_utils import get_time_display
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
@@ -44,7 +47,7 @@ class AdminCourseModePageTest(ModuleStoreTestCase):
CourseOverview.load_from_module_store(course.id)
data = {
'course': unicode(course.id),
'course': six.text_type(course.id),
'mode_slug': 'verified',
'mode_display_name': 'verified',
'min_price': 10,
@@ -199,7 +202,7 @@ class AdminCourseModeFormTest(ModuleStoreTestCase):
mode_slug=mode,
)
return CourseModeForm({
"course": unicode(self.course.id),
"course": six.text_type(self.course.id),
"mode_slug": mode,
"mode_display_name": mode,
"_expiration_datetime": upgrade_deadline,

View File

@@ -4,6 +4,7 @@ when you run "manage.py test".
Replace this with more appropriate tests for your application.
"""
from __future__ import absolute_import, unicode_literals
import itertools
from datetime import timedelta
@@ -14,14 +15,13 @@ from django.test import TestCase, override_settings
from django.utils.timezone import now
from mock import patch
from opaque_keys.edx.locator import CourseLocator
from six.moves import zip
from course_modes.helpers import enrollment_mode_display
from course_modes.models import CourseMode, Mode, invalidate_course_mode_cache, get_cosmetic_display_price
from course_modes.models import CourseMode, Mode, get_cosmetic_display_price, invalidate_course_mode_cache
from course_modes.tests.factories import CourseModeFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
from xmodule.modulestore.tests.django_utils import (
ModuleStoreTestCase,
)
@ddt.ddt
@@ -41,6 +41,7 @@ class CourseModeModelTest(TestCase):
CourseMode.objects.all().delete()
def tearDown(self):
super(CourseModeModelTest, self).tearDown()
invalidate_course_mode_cache(sender=None)
def create_mode(
@@ -398,11 +399,11 @@ class CourseModeModelTest(TestCase):
# Check the selectable modes, which should exclude credit
selectable_modes = CourseMode.modes_for_course_dict(self.course_key)
self.assertItemsEqual(selectable_modes.keys(), expected_selectable_modes)
self.assertItemsEqual(list(selectable_modes.keys()), expected_selectable_modes)
# When we get all unexpired modes, we should see credit as well
all_modes = CourseMode.modes_for_course_dict(self.course_key, only_selectable=False)
self.assertItemsEqual(all_modes.keys(), available_modes)
self.assertItemsEqual(list(all_modes.keys()), available_modes)
def _enrollment_display_modes_dicts(self, dict_type):
"""
@@ -421,11 +422,11 @@ class CourseModeModelTest(TestCase):
'professional']
}
if dict_type in ['verify_need_to_verify', 'verify_submitted']:
return dict(zip(dict_keys, display_values.get('verify_need_to_verify')))
return dict(list(zip(dict_keys, display_values.get('verify_need_to_verify'))))
elif dict_type is None or dict_type == 'dummy':
return dict(zip(dict_keys, display_values.get('verify_none')))
return dict(list(zip(dict_keys, display_values.get('verify_none'))))
else:
return dict(zip(dict_keys, display_values.get(dict_type)))
return dict(list(zip(dict_keys, display_values.get(dict_type))))
def test_expiration_datetime_explicitly_set(self):
""" Verify that setting the expiration_date property sets the explicit flag. """

View File

@@ -1,6 +1,7 @@
"""
Unit tests for the course_mode signals
"""
from __future__ import absolute_import, unicode_literals
from datetime import datetime, timedelta
@@ -10,8 +11,11 @@ from pytz import UTC
from course_modes.models import CourseMode
from course_modes.signals import _listen_for_course_publish
from django.conf import settings
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
from xmodule.partitions.partitions import ENROLLMENT_TRACK_PARTITION_ID
@ddt.ddt
@@ -87,3 +91,31 @@ class CourseModeSignalTest(ModuleStoreTestCase):
course_mode.refresh_from_db()
self.assertEqual(course_mode.expiration_datetime, self.end - timedelta(days=verification_window))
def test_masters_mode(self):
# create an xblock with verified group access
AUDIT_ID = settings.COURSE_ENROLLMENT_MODES['audit']['id']
VERIFIED_ID = settings.COURSE_ENROLLMENT_MODES['verified']['id']
MASTERS_ID = settings.COURSE_ENROLLMENT_MODES['masters']['id']
verified_section = ItemFactory.create(
category="sequential",
metadata={'group_access': {ENROLLMENT_TRACK_PARTITION_ID: [VERIFIED_ID]}}
)
# and a section with no restriction
section2 = ItemFactory.create(
category="sequential",
)
section3 = ItemFactory.create(
category='sequential',
metadata={'group_access': {ENROLLMENT_TRACK_PARTITION_ID: [AUDIT_ID]}}
)
with self.store.branch_setting(ModuleStoreEnum.Branch.draft_preferred):
# create the master's mode. signal will add masters to the verified section
self.create_mode('masters', 'masters')
verified_section_ret = self.store.get_item(verified_section.location)
section2_ret = self.store.get_item(section2.location)
section3_ret = self.store.get_item(section3.location)
# the verified section will now also be visible to master's
assert verified_section_ret.group_access[ENROLLMENT_TRACK_PARTITION_ID] == [VERIFIED_ID, MASTERS_ID]
assert section2_ret.group_access == {}
assert section3_ret.group_access == {ENROLLMENT_TRACK_PARTITION_ID: [AUDIT_ID]}

View File

@@ -2,6 +2,8 @@
Tests for course_modes views.
"""
from __future__ import absolute_import
import decimal
import unittest
from datetime import datetime, timedelta
@@ -10,6 +12,7 @@ import ddt
import freezegun
import httpretty
import pytz
import six
from django.conf import settings
from django.urls import reverse
from mock import patch
@@ -84,7 +87,7 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
)
# Configure whether we're upgrading or not
url = reverse('course_modes_choose', args=[unicode(course.id)])
url = reverse('course_modes_choose', args=[six.text_type(course.id)])
response = self.client.get(url)
# Check whether we were correctly redirected
@@ -111,11 +114,11 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
)
# Configure whether we're upgrading or not
url = reverse('course_modes_choose', args=[unicode(self.course.id)])
url = reverse('course_modes_choose', args=[six.text_type(self.course.id)])
response = self.client.get(url)
# Check whether we were correctly redirected
purchase_workflow = "?purchase_workflow=single"
start_flow_url = reverse('verify_student_start_flow', args=[unicode(self.course.id)]) + purchase_workflow
start_flow_url = reverse('verify_student_start_flow', args=[six.text_type(self.course.id)]) + purchase_workflow
self.assertRedirects(response, start_flow_url)
def test_no_id_redirect_otto(self):
@@ -132,7 +135,7 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
user=self.user
)
# Configure whether we're upgrading or not
url = reverse('course_modes_choose', args=[unicode(prof_course.id)])
url = reverse('course_modes_choose', args=[six.text_type(prof_course.id)])
response = self.client.get(url)
self.assertRedirects(response, 'http://testserver/test_basket/add/?sku=TEST', fetch_redirect_response=False)
ecomm_test_utils.update_commerce_config(enabled=False)
@@ -166,7 +169,7 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
# Verify that the prices render correctly
response = self.client.get(
reverse('course_modes_choose', args=[unicode(self.course.id)]),
reverse('course_modes_choose', args=[six.text_type(self.course.id)]),
follow=False,
)
@@ -187,7 +190,7 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
# Check whether credit upsell is shown on the page
# This should *only* be shown when a credit mode is available
url = reverse('course_modes_choose', args=[unicode(self.course.id)])
url = reverse('course_modes_choose', args=[six.text_type(self.course.id)])
response = self.client.get(url)
if show_upsell:
@@ -201,13 +204,13 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
CourseModeFactory.create(mode_slug=mode, course_id=self.course.id, min_price=1)
# Go to the "choose your track" page
choose_track_url = reverse('course_modes_choose', args=[unicode(self.course.id)])
choose_track_url = reverse('course_modes_choose', args=[six.text_type(self.course.id)])
response = self.client.get(choose_track_url)
# Since the only available track is professional ed, expect that
# we're redirected immediately to the start of the payment flow.
purchase_workflow = "?purchase_workflow=single"
start_flow_url = reverse('verify_student_start_flow', args=[unicode(self.course.id)]) + purchase_workflow
start_flow_url = reverse('verify_student_start_flow', args=[six.text_type(self.course.id)]) + purchase_workflow
self.assertRedirects(response, start_flow_url)
# Now enroll in the course
@@ -215,7 +218,7 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
user=self.user,
is_active=True,
mode=mode,
course_id=unicode(self.course.id),
course_id=six.text_type(self.course.id),
)
# Expect that this time we're redirected to the dashboard (since we're already registered)
@@ -244,7 +247,7 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
CourseModeFactory.create(mode_slug=mode, course_id=self.course.id, min_price=min_price)
# Choose the mode (POST request)
choose_track_url = reverse('course_modes_choose', args=[unicode(self.course.id)])
choose_track_url = reverse('course_modes_choose', args=[six.text_type(self.course.id)])
response = self.client.post(choose_track_url, self.POST_PARAMS_FOR_COURSE_MODE[course_mode])
# Verify the redirect
@@ -253,7 +256,7 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
elif expected_redirect == 'start-flow':
redirect_url = reverse(
'verify_student_start_flow',
kwargs={'course_id': unicode(self.course.id)}
kwargs={'course_id': six.text_type(self.course.id)}
)
else:
self.fail("Must provide a valid redirect URL name")
@@ -273,7 +276,7 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
self.assertIsNone(is_active)
# Choose the audit mode (POST request)
choose_track_url = reverse('course_modes_choose', args=[unicode(self.course.id)])
choose_track_url = reverse('course_modes_choose', args=[six.text_type(self.course.id)])
self.client.post(choose_track_url, self.POST_PARAMS_FOR_COURSE_MODE[audit_mode])
# Assert learner is enrolled in Audit track post-POST
@@ -301,14 +304,14 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
CourseModeFactory.create(mode_slug='verified', course_id=self.course.id, min_price=1)
# Choose the mode (POST request)
choose_track_url = reverse('course_modes_choose', args=[unicode(self.course.id)])
choose_track_url = reverse('course_modes_choose', args=[six.text_type(self.course.id)])
self.client.post(choose_track_url, self.POST_PARAMS_FOR_COURSE_MODE['verified'])
# Expect that the contribution amount is stored in the user's session
self.assertIn('donation_for_course', self.client.session)
self.assertIn(unicode(self.course.id), self.client.session['donation_for_course'])
self.assertIn(six.text_type(self.course.id), self.client.session['donation_for_course'])
actual_amount = self.client.session['donation_for_course'][unicode(self.course.id)]
actual_amount = self.client.session['donation_for_course'][six.text_type(self.course.id)]
expected_amount = decimal.Decimal(self.POST_PARAMS_FOR_COURSE_MODE['verified']['contribution'])
self.assertEqual(actual_amount, expected_amount)
@@ -321,12 +324,12 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
# automatic enrollment
params = {
'enrollment_action': 'enroll',
'course_id': unicode(self.course.id)
'course_id': six.text_type(self.course.id)
}
self.client.post(reverse('change_enrollment'), params)
# Explicitly select the honor mode (POST request)
choose_track_url = reverse('course_modes_choose', args=[unicode(self.course.id)])
choose_track_url = reverse('course_modes_choose', args=[six.text_type(self.course.id)])
self.client.post(choose_track_url, self.POST_PARAMS_FOR_COURSE_MODE[CourseMode.DEFAULT_MODE_SLUG])
# Verify that the user's enrollment remains unchanged
@@ -340,7 +343,7 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
CourseModeFactory.create(mode_slug=mode, course_id=self.course.id)
# Choose an unsupported mode (POST request)
choose_track_url = reverse('course_modes_choose', args=[unicode(self.course.id)])
choose_track_url = reverse('course_modes_choose', args=[six.text_type(self.course.id)])
response = self.client.post(choose_track_url, self.POST_PARAMS_FOR_COURSE_MODE['unsupported'])
self.assertEqual(400, response.status_code)
@@ -348,7 +351,7 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
def test_default_mode_creation(self):
# Hit the mode creation endpoint with no querystring params, to create an honor mode
url = reverse('create_mode', args=[unicode(self.course.id)])
url = reverse('create_mode', args=[six.text_type(self.course.id)])
response = self.client.get(url)
self.assertEquals(response.status_code, 200)
@@ -372,7 +375,7 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
parameters['suggested_prices'] = suggested_prices
parameters['currency'] = currency
url = reverse('create_mode', args=[unicode(self.course.id)])
url = reverse('create_mode', args=[six.text_type(self.course.id)])
response = self.client.get(url, parameters)
self.assertEquals(response.status_code, 200)
@@ -397,7 +400,7 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
def test_multiple_mode_creation(self):
# Create an honor mode
base_url = reverse('create_mode', args=[unicode(self.course.id)])
base_url = reverse('create_mode', args=[six.text_type(self.course.id)])
self.client.get(base_url)
# Excluding the currency parameter implicitly tests the mode creation endpoint's ability to
@@ -409,7 +412,7 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
parameters['suggested_prices'] = '10,20'
# Create a verified mode
url = reverse('create_mode', args=[unicode(self.course.id)])
url = reverse('create_mode', args=[six.text_type(self.course.id)])
self.client.get(url, parameters)
honor_mode = Mode(u'honor', u'Honor Code Certificate', 0, '', 'usd', None, None, None, None)
@@ -428,7 +431,7 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
CourseModeFactory.create(mode_slug=mode, course_id=self.course.id)
# Load the track selection page
url = reverse('course_modes_choose', args=[unicode(self.course.id)])
url = reverse('course_modes_choose', args=[six.text_type(self.course.id)])
response = self.client.get(url)
# Verify that the header navigation links are hidden for the edx.org version
@@ -445,7 +448,7 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
self.course.enrollment_end = datetime(2015, 1, 1)
modulestore().update_item(self.course, self.user.id)
url = reverse('course_modes_choose', args=[unicode(self.course.id)])
url = reverse('course_modes_choose', args=[six.text_type(self.course.id)])
response = self.client.get(url)
# URL-encoded version of 1/1/15, 12:00 AM
redirect_url = reverse('dashboard') + '?course_closed=1%2F1%2F15%2C+12%3A00+AM'
@@ -472,7 +475,7 @@ class TrackSelectionEmbargoTest(UrlResetMixin, ModuleStoreTestCase):
self.client.login(username=self.user.username, password="edx")
# Construct the URL for the track selection page
self.url = reverse('course_modes_choose', args=[unicode(self.course.id)])
self.url = reverse('course_modes_choose', args=[six.text_type(self.course.id)])
@patch.dict(settings.FEATURES, {'EMBARGO': True})
def test_embargo_restrict(self):

View File

@@ -1,3 +1,6 @@
"""URLs for course_mode API"""
from __future__ import absolute_import, unicode_literals
from django.conf import settings
from django.conf.urls import url

View File

@@ -1,11 +1,15 @@
"""
Views for the course_mode module
"""
from __future__ import absolute_import, unicode_literals
import decimal
import json
import urllib
import six
import six.moves.urllib.error
import six.moves.urllib.parse
import six.moves.urllib.request
import waffle
from babel.dates import format_datetime
from django.contrib.auth.decorators import login_required
@@ -96,7 +100,7 @@ class ChooseModeView(View):
has_enrolled_professional = (CourseMode.is_professional_slug(enrollment_mode) and is_active)
if CourseMode.has_professional_mode(modes) and not has_enrolled_professional:
purchase_workflow = request.GET.get("purchase_workflow", "single")
verify_url = reverse('verify_student_start_flow', kwargs={'course_id': unicode(course_key)})
verify_url = reverse('verify_student_start_flow', kwargs={'course_id': six.text_type(course_key)})
redirect_url = "{url}?purchase_workflow={workflow}".format(url=verify_url, workflow=purchase_workflow)
if ecommerce_service.is_enabled(request.user):
professional_mode = modes.get(CourseMode.NO_ID_PROFESSIONAL_MODE) or modes.get(CourseMode.PROFESSIONAL)
@@ -121,12 +125,12 @@ class ChooseModeView(View):
return redirect(reverse('dashboard'))
donation_for_course = request.session.get("donation_for_course", {})
chosen_price = donation_for_course.get(unicode(course_key), None)
chosen_price = donation_for_course.get(six.text_type(course_key), None)
if CourseEnrollment.is_enrollment_closed(request.user, course):
locale = to_locale(get_language())
enrollment_end_date = format_datetime(course.enrollment_end, 'short', locale=locale)
params = urllib.urlencode({'course_closed': enrollment_end_date})
params = six.moves.urllib.parse.urlencode({'course_closed': enrollment_end_date})
return redirect('{0}?{1}'.format(reverse('dashboard'), params))
# When a credit mode is available, students will be given the option
@@ -278,13 +282,13 @@ class ChooseModeView(View):
return self.get(request, course_id, error=error_msg)
donation_for_course = request.session.get("donation_for_course", {})
donation_for_course[unicode(course_key)] = amount_value
donation_for_course[six.text_type(course_key)] = amount_value
request.session["donation_for_course"] = donation_for_course
return redirect(
reverse(
'verify_student_start_flow',
kwargs={'course_id': unicode(course_key)}
kwargs={'course_id': six.text_type(course_key)}
)
)
@@ -342,7 +346,7 @@ def create_mode(request, course_id):
}
# Try pulling querystring parameters out of the request
for parameter, default in PARAMETERS.iteritems():
for parameter, default in six.iteritems(PARAMETERS):
PARAMETERS[parameter] = request.GET.get(parameter, default)
# Attempt to create the new mode for the given course