Pyupgrade in common/djangoapps/coursemodes.

This commit is contained in:
Awais Qureshi
2021-03-18 11:19:46 +05:00
parent 3b65406a0d
commit 356f169a47
27 changed files with 134 additions and 168 deletions

View File

@@ -26,7 +26,7 @@ from openedx.core.lib.courses import clean_course_id
from common.djangoapps.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)]
for key, enrollment_mode in settings.COURSE_ENROLLMENT_MODES.items()]
class CourseModeForm(forms.ModelForm):
@@ -34,7 +34,7 @@ class CourseModeForm(forms.ModelForm):
Admin form for adding a course mode.
"""
class Meta(object):
class Meta:
model = CourseMode
fields = '__all__'
@@ -63,7 +63,7 @@ class CourseModeForm(forms.ModelForm):
args_copy['course'] = CourseKey.from_string(args_copy['course'])
args = [args_copy]
super(CourseModeForm, self).__init__(*args, **kwargs) # lint-amnesty, pylint: disable=super-with-arguments
super().__init__(*args, **kwargs)
try:
if self.data.get('course'):
@@ -122,7 +122,7 @@ class CourseModeForm(forms.ModelForm):
Clean the form fields.
This is the place to perform checks that involve multiple form fields.
"""
cleaned_data = super(CourseModeForm, self).clean() # lint-amnesty, pylint: disable=super-with-arguments
cleaned_data = super().clean()
mode_slug = cleaned_data.get("mode_slug")
upgrade_deadline = cleaned_data.get("_expiration_datetime")
verification_deadline = cleaned_data.get("verification_deadline")
@@ -173,7 +173,7 @@ class CourseModeForm(forms.ModelForm):
verification_deadline
)
return super(CourseModeForm, self).save(commit=commit) # lint-amnesty, pylint: disable=super-with-arguments
return super().save(commit=commit)
@admin.register(CourseMode)

View File

@@ -58,10 +58,10 @@ def enrollment_mode_display(mode, verification_status, course_id):
enrollment_value = _("Professional Ed")
return {
'enrollment_title': six.text_type(enrollment_title),
'enrollment_value': six.text_type(enrollment_value),
'enrollment_title': str(enrollment_title),
'enrollment_value': str(enrollment_value),
'show_image': show_image,
'image_alt': six.text_type(image_alt),
'image_alt': str(image_alt),
'display_mode': _enrollment_mode_display(mode, verification_status, course_id)
}

View File

@@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from django.db import migrations, models
from opaque_keys.edx.django.models import CourseKeyField
@@ -19,12 +16,12 @@ class Migration(migrations.Migration):
('mode_slug', models.CharField(max_length=100, verbose_name='Mode')),
('mode_display_name', models.CharField(max_length=255, verbose_name='Display Name')),
('min_price', models.IntegerField(default=0, verbose_name='Price')),
('currency', models.CharField(default=u'usd', max_length=8)),
('currency', models.CharField(default='usd', max_length=8)),
('expiration_datetime', models.DateTimeField(default=None, help_text='OPTIONAL: After this date/time, users will no longer be able to enroll in this mode. Leave this blank if users can enroll in this mode until enrollment closes for the course.', null=True, verbose_name='Upgrade Deadline', blank=True)),
('expiration_date', models.DateField(default=None, null=True, blank=True)),
('suggested_prices', models.CommaSeparatedIntegerField(default=u'', max_length=255, blank=True)),
('suggested_prices', models.CommaSeparatedIntegerField(default='', max_length=255, blank=True)),
('description', models.TextField(null=True, blank=True)),
('sku', models.CharField(help_text='OPTIONAL: This is the SKU (stock keeping unit) of this mode in the external ecommerce service. Leave this blank if the course has not yet been migrated to the ecommerce service.', max_length=255, null=True, verbose_name=u'SKU', blank=True)),
('sku', models.CharField(help_text='OPTIONAL: This is the SKU (stock keeping unit) of this mode in the external ecommerce service. Leave this blank if the course has not yet been migrated to the ecommerce service.', max_length=255, null=True, verbose_name='SKU', blank=True)),
],
),
migrations.CreateModel(
@@ -35,14 +32,14 @@ class Migration(migrations.Migration):
('mode_slug', models.CharField(max_length=100)),
('mode_display_name', models.CharField(max_length=255)),
('min_price', models.IntegerField(default=0)),
('suggested_prices', models.CommaSeparatedIntegerField(default=u'', max_length=255, blank=True)),
('currency', models.CharField(default=u'usd', max_length=8)),
('suggested_prices', models.CommaSeparatedIntegerField(default='', max_length=255, blank=True)),
('currency', models.CharField(default='usd', max_length=8)),
('expiration_date', models.DateField(default=None, null=True, blank=True)),
('expiration_datetime', models.DateTimeField(default=None, null=True, blank=True)),
],
),
migrations.AlterUniqueTogether(
name='coursemode',
unique_together=set([('course_id', 'mode_slug', 'currency')]),
unique_together={('course_id', 'mode_slug', 'currency')},
),
]

View File

@@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from django.db import migrations, models

View File

@@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from django.db import migrations, models

View File

@@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from datetime import timedelta
import django.db.models.deletion

View File

@@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from django.db import migrations, models
@@ -21,7 +18,7 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='coursemode',
name='_expiration_datetime',
field=models.DateTimeField(db_column=u'expiration_datetime', default=None, blank=True, help_text='OPTIONAL: After this date/time, users will no longer be able to enroll in this mode. Leave this blank if users can enroll in this mode until enrollment closes for the course.', null=True, verbose_name='Upgrade Deadline'),
field=models.DateTimeField(db_column='expiration_datetime', default=None, blank=True, help_text='OPTIONAL: After this date/time, users will no longer be able to enroll in this mode. Leave this blank if users can enroll in this mode until enrollment closes for the course.', null=True, verbose_name='Upgrade Deadline'),
),
]
)

View File

@@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from django.db import migrations, models

View File

@@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from django.db import migrations, models
@@ -14,6 +11,6 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='coursemode',
name='bulk_sku',
field=models.CharField(default=None, max_length=255, blank=True, help_text='This is the bulk SKU (stock keeping unit) of this mode in the external ecommerce service.', null=True, verbose_name=u'Bulk SKU'),
field=models.CharField(default=None, max_length=255, blank=True, help_text='This is the bulk SKU (stock keeping unit) of this mode in the external ecommerce service.', null=True, verbose_name='Bulk SKU'),
),
]

View File

@@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from django.db import migrations, models
from opaque_keys.edx.django.models import CourseKeyField
@@ -52,6 +49,6 @@ class Migration(migrations.Migration):
# since the database column constraint already exists).
migrations.AlterUniqueTogether(
name='coursemode',
unique_together=set([('course', 'mode_slug', 'currency')]),
unique_together={('course', 'mode_slug', 'currency')},
),
]

View File

@@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
import re
import django.core.validators
@@ -17,6 +14,6 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='coursemode',
name='suggested_prices',
field=models.CharField(default=u'', max_length=255, blank=True, validators=[django.core.validators.RegexValidator(re.compile('^[\\d,]+\\Z'), 'Enter only digits separated by commas.', 'invalid')]),
field=models.CharField(default='', max_length=255, blank=True, validators=[django.core.validators.RegexValidator(re.compile('^[\\d,]+\\Z'), 'Enter only digits separated by commas.', 'invalid')]),
),
]

View File

@@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
import re
import django.core.validators
@@ -17,6 +14,6 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='coursemodesarchive',
name='suggested_prices',
field=models.CharField(default=u'', max_length=255, blank=True, validators=[django.core.validators.RegexValidator(re.compile('^[\\d,]+\\Z'), 'Enter only digits separated by commas.', 'invalid')]),
field=models.CharField(default='', max_length=255, blank=True, validators=[django.core.validators.RegexValidator(re.compile('^[\\d,]+\\Z'), 'Enter only digits separated by commas.', 'invalid')]),
),
]

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.8 on 2018-01-30 17:38
@@ -18,11 +17,11 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='coursemode',
name='suggested_prices',
field=models.CharField(blank=True, default=u'', max_length=255, validators=[django.core.validators.RegexValidator(re.compile('^\d+(?:{escaped_comma}\d+)*\Z'.format(escaped_comma=re.escape(','))), code='invalid', message='Enter only digits separated by commas.')]),
field=models.CharField(blank=True, default='', max_length=255, validators=[django.core.validators.RegexValidator(re.compile(r'^\d+(?:{escaped_comma}\d+)*\Z'.format(escaped_comma=re.escape(','))), code='invalid', message='Enter only digits separated by commas.')]),
),
migrations.AlterField(
model_name='coursemodesarchive',
name='suggested_prices',
field=models.CharField(blank=True, default=u'', max_length=255, validators=[django.core.validators.RegexValidator(re.compile('^\d+(?:{escaped_comma}\d+)*\Z'.format(escaped_comma=re.escape(','))), code='invalid', message='Enter only digits separated by commas.')]),
field=models.CharField(blank=True, default='', max_length=255, validators=[django.core.validators.RegexValidator(re.compile(r'^\d+(?:{escaped_comma}\d+)*\Z'.format(escaped_comma=re.escape(','))), code='invalid', message='Enter only digits separated by commas.')]),
),
]

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.21 on 2019-06-19 01:31
@@ -26,14 +25,14 @@ class Migration(migrations.Migration):
('mode_slug', models.CharField(max_length=100, verbose_name='Mode')),
('mode_display_name', models.CharField(max_length=255, verbose_name='Display Name')),
('min_price', models.IntegerField(default=0, verbose_name='Price')),
('currency', models.CharField(default=u'usd', max_length=8)),
('_expiration_datetime', models.DateTimeField(blank=True, db_column=u'expiration_datetime', default=None, help_text='OPTIONAL: After this date/time, users will no longer be able to enroll in this mode. Leave this blank if users can enroll in this mode until enrollment closes for the course.', null=True, verbose_name='Upgrade Deadline')),
('currency', models.CharField(default='usd', max_length=8)),
('_expiration_datetime', models.DateTimeField(blank=True, db_column='expiration_datetime', default=None, help_text='OPTIONAL: After this date/time, users will no longer be able to enroll in this mode. Leave this blank if users can enroll in this mode until enrollment closes for the course.', null=True, verbose_name='Upgrade Deadline')),
('expiration_datetime_is_explicit', models.BooleanField(default=False)),
('expiration_date', models.DateField(blank=True, default=None, null=True)),
('suggested_prices', models.CharField(blank=True, default=u'', max_length=255, validators=[django.core.validators.RegexValidator(re.compile('^\\d+(?:{escaped_comma}\\d+)*\\Z'.format(escaped_comma=re.escape(','))), code='invalid', message='Enter only digits separated by commas.')])),
('suggested_prices', models.CharField(blank=True, default='', max_length=255, validators=[django.core.validators.RegexValidator(re.compile('^\\d+(?:{escaped_comma}\\d+)*\\Z'.format(escaped_comma=re.escape(','))), code='invalid', message='Enter only digits separated by commas.')])),
('description', models.TextField(blank=True, null=True)),
('sku', models.CharField(blank=True, help_text='OPTIONAL: This is the SKU (stock keeping unit) of this mode in the external ecommerce service. Leave this blank if the course has not yet been migrated to the ecommerce service.', max_length=255, null=True, verbose_name=u'SKU')),
('bulk_sku', models.CharField(blank=True, default=None, help_text='This is the bulk SKU (stock keeping unit) of this mode in the external ecommerce service.', max_length=255, null=True, verbose_name=u'Bulk SKU')),
('sku', models.CharField(blank=True, help_text='OPTIONAL: This is the SKU (stock keeping unit) of this mode in the external ecommerce service. Leave this blank if the course has not yet been migrated to the ecommerce service.', max_length=255, null=True, verbose_name='SKU')),
('bulk_sku', models.CharField(blank=True, default=None, help_text='This is the bulk SKU (stock keeping unit) of this mode in the external ecommerce service.', max_length=255, null=True, verbose_name='Bulk SKU')),
('history_id', models.AutoField(primary_key=True, serialize=False)),
('history_date', models.DateTimeField()),
('history_change_reason', models.CharField(max_length=100, null=True)),

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.27 on 2020-01-15 20:22

View File

@@ -69,7 +69,7 @@ class CourseMode(models.Model):
min_price = models.IntegerField(default=0, verbose_name=_("Price"))
# the currency these prices are in, using lower case ISO currency codes
currency = models.CharField(default=u"usd", max_length=8)
currency = models.CharField(default="usd", max_length=8)
# The datetime at which the course mode will expire.
# This is used to implement "upgrade" deadlines.
@@ -78,12 +78,12 @@ class CourseMode(models.Model):
# Once the date passes, users will no longer be able to enroll as verified.
_expiration_datetime = models.DateTimeField(
default=None, null=True, blank=True,
verbose_name=_(u"Upgrade Deadline"),
verbose_name=_("Upgrade Deadline"),
help_text=_(
u"OPTIONAL: After this date/time, users will no longer be able to enroll in this mode. "
u"Leave this blank if users can enroll in this mode until enrollment closes for the course."
"OPTIONAL: After this date/time, users will no longer be able to enroll in this mode. "
"Leave this blank if users can enroll in this mode until enrollment closes for the course."
),
db_column=u'expiration_datetime',
db_column='expiration_datetime',
)
# The system prefers to set this automatically based on default settings. But
@@ -97,7 +97,7 @@ class CourseMode(models.Model):
# DEPRECATED: the suggested prices for this mode
# We used to allow users to choose from a set of prices, but we now allow only
# a single price. This field has been deprecated by `min_price`
suggested_prices = models.CharField(max_length=255, blank=True, default=u'',
suggested_prices = models.CharField(max_length=255, blank=True, default='',
validators=[validate_comma_separated_integer_list])
# optional description override
@@ -109,10 +109,10 @@ class CourseMode(models.Model):
max_length=255,
null=True,
blank=True,
verbose_name=u"SKU",
verbose_name="SKU",
help_text=_(
u"OPTIONAL: This is the SKU (stock keeping unit) of this mode in the external ecommerce service. "
u"Leave this blank if the course has not yet been migrated to the ecommerce service."
"OPTIONAL: This is the SKU (stock keeping unit) of this mode in the external ecommerce service. "
"Leave this blank if the course has not yet been migrated to the ecommerce service."
)
)
@@ -122,9 +122,9 @@ class CourseMode(models.Model):
null=True,
blank=True,
default=None, # Need this in order to set DEFAULT NULL on the database column
verbose_name=u"Bulk SKU",
verbose_name="Bulk SKU",
help_text=_(
u"This is the bulk SKU (stock keeping unit) of this mode in the external ecommerce service."
"This is the bulk SKU (stock keeping unit) of this mode in the external ecommerce service."
)
)
@@ -184,14 +184,14 @@ class CourseMode(models.Model):
# Modes that are allowed to upsell
UPSELL_TO_VERIFIED_MODES = [HONOR, AUDIT]
CACHE_NAMESPACE = u"course_modes.CourseMode.cache."
CACHE_NAMESPACE = "course_modes.CourseMode.cache."
class Meta(object):
class Meta:
app_label = "course_modes"
unique_together = ('course', 'mode_slug', 'currency')
def __init__(self, *args, **kwargs): # lint-amnesty, pylint: disable=useless-super-delegation
super(CourseMode, self).__init__(*args, **kwargs) # lint-amnesty, pylint: disable=super-with-arguments
super().__init__(*args, **kwargs)
def clean(self):
"""
@@ -200,7 +200,7 @@ class CourseMode(models.Model):
"""
if self.is_professional_slug(self.mode_slug) and self.expiration_datetime is not None:
raise ValidationError(
_(u"Professional education modes are not allowed to have expiration_datetime set.")
_("Professional education modes are not allowed to have expiration_datetime set.")
)
mode_config = settings.COURSE_ENROLLMENT_MODES.get(self.mode_slug, {})
@@ -209,7 +209,7 @@ class CourseMode(models.Model):
mode_display_name = mode_config.get('display_name', self.mode_slug)
raise ValidationError(
_( # lint-amnesty, pylint: disable=translation-of-non-string
u"The {course_mode} course mode has a minimum price of {min_price}. You must set a price greater than or equal to {min_price}.".format( # lint-amnesty, pylint: disable=line-too-long
"The {course_mode} course mode has a minimum price of {min_price}. You must set a price greater than or equal to {min_price}.".format( # lint-amnesty, pylint: disable=line-too-long
course_mode=mode_display_name, min_price=min_price_for_mode
)
)
@@ -222,7 +222,7 @@ class CourseMode(models.Model):
if self.id is None:
# If this model has no primary key at save time, it needs to be force-inserted.
force_insert = True
super(CourseMode, self).save(force_insert, force_update, using) # lint-amnesty, pylint: disable=super-with-arguments
super().save(force_insert, force_update, using)
@property
def slug(self):
@@ -304,7 +304,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 six.iteritems(all_modes)
for course_id, modes in all_modes.items()
}
return (all_modes, unexpired_modes)
@@ -807,7 +807,7 @@ class CourseMode(models.Model):
)
def __str__(self):
return u"{} : {}, min={}".format(
return "{} : {}, min={}".format(
self.course_id, self.mode_slug, self.min_price
)
@@ -888,7 +888,7 @@ class CourseModesArchive(models.Model):
.. no_pii:
"""
class Meta(object):
class Meta:
app_label = "course_modes"
# the course that this mode is attached to
@@ -905,11 +905,11 @@ class CourseModesArchive(models.Model):
min_price = models.IntegerField(default=0)
# the suggested prices for this mode
suggested_prices = models.CharField(max_length=255, blank=True, default=u'',
suggested_prices = models.CharField(max_length=255, blank=True, default='',
validators=[validate_comma_separated_integer_list])
# the currency these prices are in, using lower case ISO currency codes
currency = models.CharField(default=u"usd", max_length=8)
currency = models.CharField(default="usd", max_length=8)
# turn this mode off after the given expiration date
expiration_date = models.DateField(default=None, null=True, blank=True)
@@ -924,7 +924,7 @@ class CourseModeExpirationConfig(ConfigurationModel):
.. no_pii:
"""
class Meta(object):
class Meta:
app_label = "course_modes"
verification_window = models.DurationField(
@@ -936,4 +936,4 @@ class CourseModeExpirationConfig(ConfigurationModel):
def __str__(self):
""" Returns the unicode date of the verification window. """
return six.text_type(self.verification_window)
return str(self.verification_window)

View File

@@ -29,7 +29,7 @@ class CourseModeSerializer(serializers.Serializer):
sku = serializers.CharField(required=False)
bulk_sku = serializers.CharField(required=False)
class Meta(object):
class Meta:
# For disambiguating within the drf-yasg swagger schema
ref_name = 'course_modes.CourseMode'

View File

@@ -66,7 +66,7 @@ class CourseModesViewTestBase(AuthAndScopesTestMixin):
cls.other_mode.delete()
def setUp(self):
super(CourseModesViewTestBase, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
# overwrite self.student to be a staff member, since only staff
# should be able to access the course_modes API endpoints.
# This is needed to make a handful of tests inherited from AuthAndScopesTestMixin pass.
@@ -89,7 +89,7 @@ class CourseModesViewTestBase(AuthAndScopesTestMixin):
jwt_token = self._create_jwt_token(self.student, auth_type, include_me_filter=True)
# include_me_filter=True means a JWT filter will require the username
# of the requesting user to be in the requested URL
url = self.get_url(self.student) + '?username={}'.format(self.student.username)
url = self.get_url(self.student) + f'?username={self.student.username}'
resp = self.get_response(AuthType.jwt, token=jwt_token, url=url)
assert status.HTTP_200_OK == resp.status_code
@@ -108,7 +108,7 @@ class TestCourseModesListViews(CourseModesViewTestBase, ModuleStoreTestCase, API
Required method to implement AuthAndScopesTestMixin.
"""
kwargs = {
'course_id': text_type(course_id or self.course_key)
'course_id': str(course_id or self.course_key)
}
return reverse(self.view_name, kwargs=kwargs)
@@ -130,7 +130,7 @@ class TestCourseModesListViews(CourseModesViewTestBase, ModuleStoreTestCase, API
actual_results = self._sorted_results(response)
expected_results = [
{
'course_id': text_type(self.course_key),
'course_id': str(self.course_key),
'mode_slug': 'audit',
'mode_display_name': 'Audit',
'min_price': 0,
@@ -142,7 +142,7 @@ class TestCourseModesListViews(CourseModesViewTestBase, ModuleStoreTestCase, API
'bulk_sku': None,
},
{
'course_id': text_type(self.course_key),
'course_id': str(self.course_key),
'mode_slug': 'verified',
'mode_display_name': 'Verified',
'min_price': 25,
@@ -165,7 +165,7 @@ class TestCourseModesListViews(CourseModesViewTestBase, ModuleStoreTestCase, API
other_actual_results = self._sorted_results(other_response)
other_expected_results = [
{
'course_id': text_type(self.other_course_key),
'course_id': str(self.other_course_key),
'mode_slug': 'other-audit',
'mode_display_name': 'Other Audit',
'min_price': 0,
@@ -199,7 +199,7 @@ class TestCourseModesListViews(CourseModesViewTestBase, ModuleStoreTestCase, API
url = self.get_url(course_id=self.course_key)
request_payload = {
'course_id': text_type(self.course_key),
'course_id': str(self.course_key),
'mode_slug': 'masters',
'mode_display_name': 'Masters',
'currency': 'usd',
@@ -220,7 +220,7 @@ class TestCourseModesListViews(CourseModesViewTestBase, ModuleStoreTestCase, API
url = self.get_url(course_id=self.course_key)
request_payload = {
'course_id': text_type(self.course_key),
'course_id': str(self.course_key),
'mode_slug': 'phd',
}
@@ -252,7 +252,7 @@ class TestCourseModesDetailViews(CourseModesViewTestBase, APITestCase):
Required method to implement AuthAndScopesTestMixin.
"""
kwargs = {
'course_id': text_type(course_id or self.course_key),
'course_id': str(course_id or self.course_key),
'mode_slug': mode_slug or 'audit',
}
return reverse(self.view_name, kwargs=kwargs)
@@ -282,7 +282,7 @@ class TestCourseModesDetailViews(CourseModesViewTestBase, APITestCase):
assert status.HTTP_200_OK == response.status_code
actual_data = dict(response.data)
expected_data = {
'course_id': text_type(self.course_key),
'course_id': str(self.course_key),
'mode_slug': 'audit',
'mode_display_name': 'Audit',
'min_price': 0,

View File

@@ -12,12 +12,12 @@ app_name = 'v1'
urlpatterns = [
url(
r'^courses/{course_id}/$'.format(course_id=settings.COURSE_ID_PATTERN),
fr'^courses/{settings.COURSE_ID_PATTERN}/$',
views.CourseModesView.as_view(),
name='course_modes_list'
),
url(
r'^courses/{course_id}/(?P<mode_slug>.*)$'.format(course_id=settings.COURSE_ID_PATTERN),
fr'^courses/{settings.COURSE_ID_PATTERN}/(?P<mode_slug>.*)$',
views.CourseModesDetailView.as_view(),
name='course_modes_detail'
),

View File

@@ -22,7 +22,7 @@ from openedx.core.lib.api.parsers import MergePatchParser
log = logging.getLogger(__name__)
class CourseModesMixin(object):
class CourseModesMixin:
"""
A base class for course modes views that specifies authentication, permissions,
serialization, pagination, and the base queryset.

View File

@@ -17,7 +17,7 @@ from openedx.core.djangoapps.content.course_overviews.tests.factories import Cou
# Factories are self documenting
class CourseModeFactory(DjangoModelFactory): # lint-amnesty, pylint: disable=missing-class-docstring
class Meta(object):
class Meta:
model = CourseMode
mode_slug = CourseMode.DEFAULT_MODE_SLUG
@@ -38,7 +38,7 @@ class CourseModeFactory(DjangoModelFactory): # lint-amnesty, pylint: disable=mi
course_overview = None
course_kwargs.setdefault('id', course_id)
if course_id is not None:
if isinstance(course_id, six.string_types):
if isinstance(course_id, str):
course_id = CourseKey.from_string(course_id)
course_kwargs['id'] = course_id
try:
@@ -63,4 +63,4 @@ class CourseModeFactory(DjangoModelFactory): # lint-amnesty, pylint: disable=mi
@lazy_attribute
def mode_display_name(self):
return '{0} course'.format(self.mode_slug)
return f'{self.mode_slug} course'

View File

@@ -47,7 +47,7 @@ class AdminCourseModePageTest(ModuleStoreTestCase):
CourseOverview.load_from_module_store(course.id)
data = {
'course': six.text_type(course.id),
'course': str(course.id),
'mode_slug': 'verified',
'mode_display_name': 'verified',
'min_price': 10,
@@ -91,7 +91,7 @@ class AdminCourseModeFormTest(ModuleStoreTestCase):
"""
Create a test course.
"""
super(AdminCourseModeFormTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
self.course = CourseFactory.create()
CourseOverview.load_from_module_store(self.course.id)
@@ -199,7 +199,7 @@ class AdminCourseModeFormTest(ModuleStoreTestCase):
mode_slug=mode,
)
return CourseModeForm({
"course": six.text_type(self.course.id),
"course": str(self.course.id),
"mode_slug": mode,
"mode_display_name": mode,
"_expiration_datetime": upgrade_deadline,

View File

@@ -13,10 +13,9 @@ import ddt
from django.core.exceptions import ValidationError
from django.test import TestCase, override_settings
from django.utils.timezone import now
from mock import patch
from unittest.mock import patch
from opaque_keys.edx.locator import CourseLocator
import six
from six.moves import zip
from common.djangoapps.course_modes.helpers import enrollment_mode_display
from common.djangoapps.course_modes.models import CourseMode, Mode, get_cosmetic_display_price, invalidate_course_mode_cache # lint-amnesty, pylint: disable=line-too-long
@@ -38,12 +37,12 @@ class CourseModeModelTest(TestCase):
}
def setUp(self):
super(CourseModeModelTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
self.course_key = CourseLocator('Test', 'TestCourse', 'TestCourseRun')
CourseMode.objects.all().delete()
def tearDown(self):
super(CourseModeModelTest, self).tearDown() # lint-amnesty, pylint: disable=super-with-arguments
super().tearDown()
invalidate_course_mode_cache(sender=None)
def create_mode(
@@ -92,7 +91,7 @@ class CourseModeModelTest(TestCase):
self.create_mode('verified', 'Verified Certificate', 10)
modes = CourseMode.modes_for_course(self.course_key)
mode = Mode(u'verified', u'Verified Certificate', 10, '', 'usd', None, None, None, None)
mode = Mode('verified', 'Verified Certificate', 10, '', 'usd', None, None, None, None)
assert [mode] == modes
modes_dict = CourseMode.modes_for_course_dict(self.course_key)
@@ -103,16 +102,16 @@ class CourseModeModelTest(TestCase):
"""
Finding the modes when there's multiple modes
"""
mode1 = Mode(u'honor', u'Honor Code Certificate', 0, '', 'usd', None, None, None, None)
mode2 = Mode(u'verified', u'Verified Certificate', 10, '', 'usd', None, None, None, None)
mode1 = Mode('honor', 'Honor Code Certificate', 0, '', 'usd', None, None, None, None)
mode2 = Mode('verified', 'Verified Certificate', 10, '', 'usd', None, None, None, None)
set_modes = [mode1, mode2]
for mode in set_modes:
self.create_mode(mode.slug, mode.name, mode.min_price, mode.suggested_prices)
modes = CourseMode.modes_for_course(self.course_key)
assert modes == set_modes
assert mode1 == CourseMode.mode_for_course(self.course_key, u'honor')
assert mode2 == CourseMode.mode_for_course(self.course_key, u'verified')
assert mode1 == CourseMode.mode_for_course(self.course_key, 'honor')
assert mode2 == CourseMode.mode_for_course(self.course_key, 'verified')
assert CourseMode.mode_for_course(self.course_key, 'DNE') is None
def test_min_course_price_for_currency(self):
@@ -123,9 +122,9 @@ class CourseModeModelTest(TestCase):
assert 0 == CourseMode.min_course_price_for_currency(self.course_key, 'usd')
# create some modes
mode1 = Mode(u'honor', u'Honor Code Certificate', 10, '', 'usd', None, None, None, None)
mode2 = Mode(u'verified', u'Verified Certificate', 20, '', 'usd', None, None, None, None)
mode3 = Mode(u'honor', u'Honor Code Certificate', 80, '', 'cny', None, None, None, None)
mode1 = Mode('honor', 'Honor Code Certificate', 10, '', 'usd', None, None, None, None)
mode2 = Mode('verified', 'Verified Certificate', 20, '', 'usd', None, None, None, None)
mode3 = Mode('honor', 'Honor Code Certificate', 80, '', 'cny', None, None, None, None)
set_modes = [mode1, mode2, mode3]
for mode in set_modes:
self.create_mode(mode.slug, mode.name, mode.min_price, mode.suggested_prices, mode.currency)
@@ -140,7 +139,7 @@ class CourseModeModelTest(TestCase):
modes = CourseMode.modes_for_course(self.course_key)
assert [CourseMode.DEFAULT_MODE] == modes
mode1 = Mode(u'honor', u'Honor Code Certificate', 0, '', 'usd', None, None, None, None)
mode1 = Mode('honor', 'Honor Code Certificate', 0, '', 'usd', None, None, None, None)
self.create_mode(mode1.slug, mode1.name, mode1.min_price, mode1.suggested_prices)
modes = CourseMode.modes_for_course(self.course_key)
assert [mode1] == modes
@@ -149,8 +148,8 @@ class CourseModeModelTest(TestCase):
expired_mode.expiration_datetime = expiration_datetime
expired_mode.save()
expired_mode_value = Mode(
u'verified',
u'Verified Certificate',
'verified',
'Verified Certificate',
10,
'',
'usd',
@@ -335,7 +334,7 @@ class CourseModeModelTest(TestCase):
assert not is_error_expected, 'Expected a ValidationError to be thrown.'
except ValidationError as exc:
assert is_error_expected, 'Did not expect a ValidationError to be thrown.'
assert exc.messages == [u'Professional education modes are not allowed to have expiration_datetime set.']
assert exc.messages == ['Professional education modes are not allowed to have expiration_datetime set.']
@ddt.data(
("verified", "verify_need_to_verify"),
@@ -385,11 +384,11 @@ class CourseModeModelTest(TestCase):
# Check the selectable modes, which should exclude credit
selectable_modes = CourseMode.modes_for_course_dict(self.course_key)
six.assertCountEqual(self, list(selectable_modes.keys()), expected_selectable_modes)
self.assertCountEqual(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)
six.assertCountEqual(self, list(all_modes.keys()), available_modes)
self.assertCountEqual(list(all_modes.keys()), available_modes)
def _enrollment_display_modes_dicts(self, dict_type):
"""

View File

@@ -7,7 +7,7 @@ from datetime import datetime, timedelta
import ddt
from django.conf import settings
from mock import patch
from unittest.mock import patch
from pytz import UTC
from common.djangoapps.course_modes.models import CourseMode
@@ -25,7 +25,7 @@ class CourseModeSignalTest(ModuleStoreTestCase):
"""
def setUp(self):
super(CourseModeSignalTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
self.end = datetime.now(tz=UTC).replace(microsecond=0) + timedelta(days=7)
self.course = CourseFactory.create(end=self.end)
CourseMode.objects.all().delete()

View File

@@ -14,7 +14,7 @@ import pytz
import six
from django.conf import settings
from django.urls import reverse
from mock import patch
from unittest.mock import patch
from common.djangoapps.course_modes.models import CourseMode, Mode
from common.djangoapps.course_modes.tests.factories import CourseModeFactory
@@ -43,7 +43,7 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
@patch.dict(settings.FEATURES, {'MODE_CREATION_FOR_TESTING': True})
def setUp(self):
super(CourseModeViewTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
now = datetime.now(pytz.utc)
day = timedelta(days=1)
tomorrow = now + day
@@ -88,7 +88,7 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
)
# Configure whether we're upgrading or not
url = reverse('course_modes_choose', args=[six.text_type(course.id)])
url = reverse('course_modes_choose', args=[str(course.id)])
response = self.client.get(url)
# Check whether we were correctly redirected
@@ -115,7 +115,7 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
)
# Configure whether we're upgrading or not
url = reverse('course_modes_choose', args=[six.text_type(self.course.id)])
url = reverse('course_modes_choose', args=[str(self.course.id)])
response = self.client.get(url)
start_flow_url = IDVerificationService.get_verify_location(course_id=self.course.id)
@@ -136,7 +136,7 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
user=self.user
)
# Configure whether we're upgrading or not
url = reverse('course_modes_choose', args=[six.text_type(prof_course.id)])
url = reverse('course_modes_choose', args=[str(prof_course.id)])
response = self.client.get(url)
self.assertRedirects(response, '/test_basket/add/?sku=TEST', fetch_redirect_response=False)
ecomm_test_utils.update_commerce_config(enabled=False)
@@ -170,7 +170,7 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
# Verify that the prices render correctly
response = self.client.get(
reverse('course_modes_choose', args=[six.text_type(self.course.id)]),
reverse('course_modes_choose', args=[str(self.course.id)]),
follow=False,
)
@@ -191,7 +191,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=[six.text_type(self.course.id)])
url = reverse('course_modes_choose', args=[str(self.course.id)])
response = self.client.get(url)
if show_upsell:
@@ -247,7 +247,7 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
# Check whether congratulations message is shown on the page
# This should *only* be shown when an enrollment exists
url = reverse('course_modes_choose', args=[six.text_type(self.course.id)])
url = reverse('course_modes_choose', args=[str(self.course.id)])
response = self.client.get(url)
if create_enrollment:
@@ -261,7 +261,7 @@ 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=[six.text_type(self.course.id)])
choose_track_url = reverse('course_modes_choose', args=[str(self.course.id)])
response = self.client.get(choose_track_url)
# Since the only available track is professional ed, expect that
@@ -274,7 +274,7 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
user=self.user,
is_active=True,
mode=mode,
course_id=six.text_type(self.course.id),
course_id=str(self.course.id),
)
# Expect that this time we're redirected to the dashboard (since we're already registered)
@@ -303,7 +303,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=[six.text_type(self.course.id)])
choose_track_url = reverse('course_modes_choose', args=[str(self.course.id)])
response = self.client.post(choose_track_url, self.POST_PARAMS_FOR_COURSE_MODE[course_mode])
# Verify the redirect
@@ -330,7 +330,7 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
assert is_active is None
# Choose the audit mode (POST request)
choose_track_url = reverse('course_modes_choose', args=[six.text_type(self.course.id)])
choose_track_url = reverse('course_modes_choose', args=[str(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
@@ -358,14 +358,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=[six.text_type(self.course.id)])
choose_track_url = reverse('course_modes_choose', args=[str(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
assert 'donation_for_course' in self.client.session
assert six.text_type(self.course.id) in self.client.session['donation_for_course']
assert str(self.course.id) in self.client.session['donation_for_course']
actual_amount = self.client.session['donation_for_course'][six.text_type(self.course.id)]
actual_amount = self.client.session['donation_for_course'][str(self.course.id)]
expected_amount = decimal.Decimal(self.POST_PARAMS_FOR_COURSE_MODE['verified']['contribution'])
assert actual_amount == expected_amount
@@ -378,12 +378,12 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
# automatic enrollment
params = {
'enrollment_action': 'enroll',
'course_id': six.text_type(self.course.id)
'course_id': str(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=[six.text_type(self.course.id)])
choose_track_url = reverse('course_modes_choose', args=[str(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
@@ -397,7 +397,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=[six.text_type(self.course.id)])
choose_track_url = reverse('course_modes_choose', args=[str(self.course.id)])
response = self.client.post(choose_track_url, self.POST_PARAMS_FOR_COURSE_MODE['unsupported'])
assert 400 == response.status_code
@@ -405,20 +405,20 @@ 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=[six.text_type(self.course.id)])
url = reverse('create_mode', args=[str(self.course.id)])
response = self.client.get(url)
assert response.status_code == 200
expected_mode = [Mode(u'honor', u'Honor Code Certificate', 0, '', 'usd', None, None, None, None)]
expected_mode = [Mode('honor', 'Honor Code Certificate', 0, '', 'usd', None, None, None, None)]
course_mode = CourseMode.modes_for_course(self.course.id)
assert course_mode == expected_mode
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
@ddt.data(
(u'verified', u'Verified Certificate', 10, '10,20,30', 'usd'),
(u'professional', u'Professional Education', 100, '100,200', 'usd'),
('verified', 'Verified Certificate', 10, '10,20,30', 'usd'),
('professional', 'Professional Education', 100, '100,200', 'usd'),
)
@ddt.unpack
def test_verified_mode_creation(self, mode_slug, mode_display_name, min_price, suggested_prices, currency):
@@ -429,7 +429,7 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
parameters['suggested_prices'] = suggested_prices
parameters['currency'] = currency
url = reverse('create_mode', args=[six.text_type(self.course.id)])
url = reverse('create_mode', args=[str(self.course.id)])
response = self.client.get(url, parameters)
assert response.status_code == 200
@@ -454,23 +454,23 @@ 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=[six.text_type(self.course.id)])
base_url = reverse('create_mode', args=[str(self.course.id)])
self.client.get(base_url)
# Excluding the currency parameter implicitly tests the mode creation endpoint's ability to
# use default values when parameters are partially missing.
parameters = {}
parameters['mode_slug'] = u'verified'
parameters['mode_display_name'] = u'Verified Certificate'
parameters['mode_slug'] = 'verified'
parameters['mode_display_name'] = 'Verified Certificate'
parameters['min_price'] = 10
parameters['suggested_prices'] = '10,20'
# Create a verified mode
url = reverse('create_mode', args=[six.text_type(self.course.id)])
url = reverse('create_mode', args=[str(self.course.id)])
self.client.get(url, parameters)
honor_mode = Mode(u'honor', u'Honor Code Certificate', 0, '', 'usd', None, None, None, None)
verified_mode = Mode(u'verified', u'Verified Certificate', 10, '10,20', 'usd', None, None, None, None)
honor_mode = Mode('honor', 'Honor Code Certificate', 0, '', 'usd', None, None, None, None)
verified_mode = Mode('verified', 'Verified Certificate', 10, '10,20', 'usd', None, None, None, None)
expected_modes = [honor_mode, verified_mode]
course_modes = CourseMode.modes_for_course(self.course.id)
@@ -485,7 +485,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=[six.text_type(self.course.id)])
url = reverse('course_modes_choose', args=[str(self.course.id)])
response = self.client.get(url)
# Verify that the header navigation links are hidden for the edx.org version
@@ -502,7 +502,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=[six.text_type(self.course.id)])
url = reverse('course_modes_choose', args=[str(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'
@@ -517,7 +517,7 @@ class TrackSelectionEmbargoTest(UrlResetMixin, ModuleStoreTestCase):
@patch.dict(settings.FEATURES, {'EMBARGO': True})
def setUp(self):
super(TrackSelectionEmbargoTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
# Create a course and course modes
self.course = CourseFactory.create()
@@ -529,7 +529,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=[six.text_type(self.course.id)])
self.url = reverse('course_modes_choose', args=[str(self.course.id)])
@patch.dict(settings.FEATURES, {'EMBARGO': True})
def test_embargo_restrict(self):

View File

@@ -7,13 +7,13 @@ from django.conf.urls import url
from common.djangoapps.course_modes import views
urlpatterns = [
url(r'^choose/{}/$'.format(settings.COURSE_ID_PATTERN), views.ChooseModeView.as_view(), name='course_modes_choose'),
url(fr'^choose/{settings.COURSE_ID_PATTERN}/$', views.ChooseModeView.as_view(), name='course_modes_choose'),
]
# Enable verified mode creation
if settings.FEATURES.get('MODE_CREATION_FOR_TESTING'):
urlpatterns.append(
url(r'^create_mode/{}/$'.format(settings.COURSE_ID_PATTERN),
url(fr'^create_mode/{settings.COURSE_ID_PATTERN}/$',
views.create_mode,
name='create_mode'),
)

View File

@@ -64,7 +64,7 @@ class ChooseModeView(View):
atomic() block is active, since that would break atomicity.
"""
return super(ChooseModeView, self).dispatch(*args, **kwargs) # lint-amnesty, pylint: disable=super-with-arguments
return super().dispatch(*args, **kwargs)
@method_decorator(login_required)
@method_decorator(transaction.atomic)
@@ -136,13 +136,13 @@ class ChooseModeView(View):
return redirect(reverse('dashboard'))
donation_for_course = request.session.get("donation_for_course", {})
chosen_price = donation_for_course.get(six.text_type(course_key), None)
chosen_price = donation_for_course.get(str(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 = six.moves.urllib.parse.urlencode({'course_closed': enrollment_end_date})
return redirect('{0}?{1}'.format(reverse('dashboard'), params))
return redirect('{}?{}'.format(reverse('dashboard'), params))
# When a credit mode is available, students will be given the option
# to upgrade from a verified mode to a credit mode at the end of the course.
@@ -156,7 +156,7 @@ class ChooseModeView(View):
CourseMode.is_credit_mode(mode) for mode
in CourseMode.modes_for_course(course_key, only_selectable=False)
)
course_id = text_type(course_key)
course_id = str(course_key)
context = {
"course_modes_choose_url": reverse(
@@ -307,7 +307,7 @@ class ChooseModeView(View):
return self.get(request, course_id, error=error_msg)
donation_for_course = request.session.get("donation_for_course", {})
donation_for_course[six.text_type(course_key)] = amount_value
donation_for_course[str(course_key)] = amount_value
request.session["donation_for_course"] = donation_for_course
verify_url = IDVerificationService.get_verify_location(course_id=course_key)
@@ -358,16 +358,16 @@ def create_mode(request, course_id):
Response
"""
PARAMETERS = {
'mode_slug': u'honor',
'mode_display_name': u'Honor Code Certificate',
'mode_slug': 'honor',
'mode_display_name': 'Honor Code Certificate',
'min_price': 0,
'suggested_prices': u'',
'currency': u'usd',
'suggested_prices': '',
'currency': 'usd',
'sku': None,
}
# Try pulling querystring parameters out of the request
for parameter, default in six.iteritems(PARAMETERS):
for parameter, default in PARAMETERS.items():
PARAMETERS[parameter] = request.GET.get(parameter, default)
# Attempt to create the new mode for the given course