diff --git a/lms/djangoapps/verified_track_content/__init__.py b/lms/djangoapps/verified_track_content/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lms/djangoapps/verified_track_content/admin.py b/lms/djangoapps/verified_track_content/admin.py new file mode 100644 index 0000000000..71ae02219e --- /dev/null +++ b/lms/djangoapps/verified_track_content/admin.py @@ -0,0 +1,14 @@ +""" +Django admin page for verified track configuration +""" + +from django.contrib import admin + +from verified_track_content.forms import VerifiedTrackCourseForm +from verified_track_content.models import VerifiedTrackCohortedCourse + + +@admin.register(VerifiedTrackCohortedCourse) +class VerifiedTrackCohortedCourseAdmin(admin.ModelAdmin): + """Admin for enabling verified track cohorting. """ + form = VerifiedTrackCourseForm diff --git a/lms/djangoapps/verified_track_content/forms.py b/lms/djangoapps/verified_track_content/forms.py new file mode 100644 index 0000000000..2de81ee8c6 --- /dev/null +++ b/lms/djangoapps/verified_track_content/forms.py @@ -0,0 +1,54 @@ +""" +Forms for configuring courses for verified track cohorting +""" +from django import forms + +from django.utils.translation import ugettext as _ + +from xmodule.modulestore.django import modulestore +from opaque_keys import InvalidKeyError +from opaque_keys.edx.keys import CourseKey + +from verified_track_content.models import VerifiedTrackCohortedCourse + + +class VerifiedTrackCourseForm(forms.ModelForm): + """Validate course keys for the VerifiedTrackCohortedCourse model + + The default behavior in Django admin is to: + * Save course keys for courses that do not exist. + * Return a 500 response if the course key format is invalid. + + Using this form ensures that we display a user-friendly + error message instead. + + """ + class Meta(object): # pylint:disable=missing-docstring + model = VerifiedTrackCohortedCourse + fields = '__all__' + + def clean_course_key(self): + """Validate the course key. + + Checks that the key format is valid and that + the course exists. If not, displays an error message. + + Arguments: + field_name (str): The name of the field to validate. + + Returns: + CourseKey + + """ + cleaned_id = self.cleaned_data['course_key'] + error_msg = _('COURSE NOT FOUND. Please check that the course ID is valid.') + + try: + course_key = CourseKey.from_string(cleaned_id) + except InvalidKeyError: + raise forms.ValidationError(error_msg) + + if not modulestore().has_course(course_key): + raise forms.ValidationError(error_msg) + + return course_key diff --git a/lms/djangoapps/verified_track_content/migrations/0001_initial.py b/lms/djangoapps/verified_track_content/migrations/0001_initial.py new file mode 100644 index 0000000000..0f1df04262 --- /dev/null +++ b/lms/djangoapps/verified_track_content/migrations/0001_initial.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models +import xmodule_django.models + + +class Migration(migrations.Migration): + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='VerifiedTrackCohortedCourse', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('course_key', xmodule_django.models.CourseKeyField(help_text='The course key for the course we would like to be auto-cohorted.', unique=True, max_length=255, db_index=True)), + ('enabled', models.BooleanField()), + ], + ), + ] diff --git a/lms/djangoapps/verified_track_content/migrations/__init__.py b/lms/djangoapps/verified_track_content/migrations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lms/djangoapps/verified_track_content/models.py b/lms/djangoapps/verified_track_content/models.py new file mode 100644 index 0000000000..a6c54b2105 --- /dev/null +++ b/lms/djangoapps/verified_track_content/models.py @@ -0,0 +1,39 @@ +""" +Models for verified track selections. +""" +from django.db import models +from django.utils.translation import ugettext_lazy + +from xmodule_django.models import CourseKeyField + + +class VerifiedTrackCohortedCourse(models.Model): + """ + Tracks which courses have verified track auto-cohorting enabled. + """ + course_key = CourseKeyField( + max_length=255, db_index=True, unique=True, + help_text=ugettext_lazy(u"The course key for the course we would like to be auto-cohorted.") + ) + + enabled = models.BooleanField() + + def __unicode__(self): + return u"Course: {}, enabled: {}".format(unicode(self.course_key), self.enabled) + + @classmethod + def is_verified_track_cohort_enabled(cls, course_key): + """ + Checks whether or not verified track cohort is enabled for the given course. + + Args: + course_key (CourseKey): a course key representing the course we want to check + + Returns: + True if the course has verified track cohorts is enabled + False if not + """ + try: + return cls.objects.get(course_key=course_key).enabled + except cls.DoesNotExist: + return False diff --git a/lms/djangoapps/verified_track_content/tests/test_forms.py b/lms/djangoapps/verified_track_content/tests/test_forms.py new file mode 100644 index 0000000000..c1e7835faf --- /dev/null +++ b/lms/djangoapps/verified_track_content/tests/test_forms.py @@ -0,0 +1,45 @@ +""" +Test for forms helpers. +""" +from opaque_keys.edx.keys import CourseKey + +from xmodule.modulestore.tests.factories import CourseFactory +from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase + +from verified_track_content.forms import VerifiedTrackCourseForm + + +class TestVerifiedTrackCourseForm(SharedModuleStoreTestCase): + """ + Test form validation. + """ + + FAKE_COURSE = 'edX/Test_Course/Run' + BAD_COURSE_KEY = 'bad_course_key' + + @classmethod + def setUpClass(cls): + super(TestVerifiedTrackCourseForm, cls).setUpClass() + cls.course = CourseFactory.create() + + def test_form_validation_success(self): + form_data = {'course_key': unicode(self.course.id), 'enabled': True} + form = VerifiedTrackCourseForm(data=form_data) + self.assertTrue(form.is_valid()) + + def test_form_validation_failure(self): + form_data = {'course_key': self.FAKE_COURSE, 'enabled': True} + form = VerifiedTrackCourseForm(data=form_data) + self.assertFalse(form.is_valid()) + self.assertEqual( + form.errors['course_key'], + ['COURSE NOT FOUND. Please check that the course ID is valid.'] + ) + + form_data = {'course_key': self.BAD_COURSE_KEY, 'enabled': True} + form = VerifiedTrackCourseForm(data=form_data) + self.assertFalse(form.is_valid()) + self.assertEqual( + form.errors['course_key'], + ['COURSE NOT FOUND. Please check that the course ID is valid.'] + ) diff --git a/lms/djangoapps/verified_track_content/tests/test_models.py b/lms/djangoapps/verified_track_content/tests/test_models.py new file mode 100644 index 0000000000..37479f85c9 --- /dev/null +++ b/lms/djangoapps/verified_track_content/tests/test_models.py @@ -0,0 +1,37 @@ +""" +Tests for Verified Track Cohorting models +""" +from django.test import TestCase + +from opaque_keys.edx.keys import CourseKey + +from verified_track_content.models import VerifiedTrackCohortedCourse + + +class TestVerifiedTrackCohortedCourse(TestCase): + """ + Tests that the configuration works as expected. + """ + SAMPLE_COURSE = 'edX/Test_Course/Run' + + def test_course_enabled(self): + course_key = CourseKey.from_string(self.SAMPLE_COURSE) + # Test when no configuration exists + self.assertFalse(VerifiedTrackCohortedCourse.is_verified_track_cohort_enabled(course_key)) + + # Enable for a course + config = VerifiedTrackCohortedCourse.objects.create(course_key=course_key, enabled=True) + config.save() + self.assertTrue(VerifiedTrackCohortedCourse.is_verified_track_cohort_enabled(course_key)) + + # Disable for the course + config.enabled = False + config.save() + self.assertFalse(VerifiedTrackCohortedCourse.is_verified_track_cohort_enabled(course_key)) + + def test_unicode(self): + course_key = CourseKey.from_string(self.SAMPLE_COURSE) + # Enable for a course + config = VerifiedTrackCohortedCourse.objects.create(course_key=course_key, enabled=True) + config.save() + self.assertEqual(unicode(config), "Course: {}, enabled: True".format(self.SAMPLE_COURSE)) diff --git a/lms/envs/common.py b/lms/envs/common.py index a43e93ff3f..288f78411c 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -2016,6 +2016,9 @@ INSTALLED_APPS = ( # Management commands used for configuration automation 'edx_management_commands.management_commands', + + # Verified Track Content Cohorting + 'verified_track_content', ) # Migrations which are not in the standard module "migrations"