diff --git a/cms/djangoapps/contentstore/management/commands/create_course.py b/cms/djangoapps/contentstore/management/commands/create_course.py new file mode 100644 index 0000000000..12c7054d2a --- /dev/null +++ b/cms/djangoapps/contentstore/management/commands/create_course.py @@ -0,0 +1,54 @@ +""" +Django management command to create a course in a specific modulestore +""" +from django.core.management.base import BaseCommand, CommandError +from django.contrib.auth.models import User +from xmodule.modulestore import ModuleStoreEnum +from contentstore.views.course import create_new_course_in_store +from contentstore.management.commands.utils import user_from_str + + +class Command(BaseCommand): + """ + Create a course in a specific modulestore. + """ + + # can this query modulestore for the list of write accessible stores or does that violate command pattern? + help = "Create a course in one of {}".format([ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split]) + args = "modulestore user org course run" + + def parse_args(self, *args): + """ + Return a tuple of passed in values for (modulestore, user, org, course, run). + """ + if len(args) != 5: + raise CommandError( + "create_course requires 5 arguments: " + "a modulestore, user, org, course, run. Modulestore is one of {}".format( + [ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split] + ) + ) + + if args[0] not in [ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split]: + raise CommandError( + "Modulestore (first arg) must be one of {}".format( + [ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split] + ) + ) + storetype = args[0] + + try: + user = user_from_str(args[1]) + except User.DoesNotExist: + raise CommandError("No user {} found: expected args are ".format(args[1], self.args)) + + org = args[2] + course = args[3] + run = args[4] + + return storetype, user, org, course, run + + def handle(self, *args, **options): + storetype, user, org, course, run = self.parse_args(*args) + new_course = create_new_course_in_store(storetype, user, org, course, run, {}) + self.stdout.write(u"Created {}".format(unicode(new_course.id))) diff --git a/cms/djangoapps/contentstore/management/commands/migrate_to_split.py b/cms/djangoapps/contentstore/management/commands/migrate_to_split.py index bb164fc6d8..7143519194 100644 --- a/cms/djangoapps/contentstore/management/commands/migrate_to_split.py +++ b/cms/djangoapps/contentstore/management/commands/migrate_to_split.py @@ -10,21 +10,7 @@ from opaque_keys.edx.keys import CourseKey from opaque_keys import InvalidKeyError from opaque_keys.edx.locations import SlashSeparatedCourseKey from xmodule.modulestore import ModuleStoreEnum - - -def user_from_str(identifier): - """ - Return a user identified by the given string. The string could be an email - address, or a stringified integer corresponding to the ID of the user in - the database. If no user could be found, a User.DoesNotExist exception - will be raised. - """ - try: - user_id = int(identifier) - except ValueError: - return User.objects.get(email=identifier) - - return User.objects.get(id=user_id) +from contentstore.management.commands.utils import user_from_str class Command(BaseCommand): diff --git a/cms/djangoapps/contentstore/management/commands/tests/test_create_course.py b/cms/djangoapps/contentstore/management/commands/tests/test_create_course.py new file mode 100644 index 0000000000..bd7098e780 --- /dev/null +++ b/cms/djangoapps/contentstore/management/commands/tests/test_create_course.py @@ -0,0 +1,68 @@ +""" +Unittests for creating a course in an chosen modulestore +""" +import unittest +import ddt +from django.core.management import CommandError, call_command + +from contentstore.management.commands.create_course import Command +from xmodule.modulestore import ModuleStoreEnum +from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase +from xmodule.modulestore.django import modulestore + + +class TestArgParsing(unittest.TestCase): + """ + Tests for parsing arguments for the `create_course` management command + """ + def setUp(self): + self.command = Command() + + def test_no_args(self): + errstring = "create_course requires 5 arguments" + with self.assertRaisesRegexp(CommandError, errstring): + self.command.handle('create_course') + + def test_invalid_store(self): + with self.assertRaises(CommandError): + self.command.handle("foo", "user@foo.org", "org", "course", "run") + + def test_xml_store(self): + with self.assertRaises(CommandError): + self.command.handle(ModuleStoreEnum.Type.xml, "user@foo.org", "org", "course", "run") + + def test_nonexistent_user_id(self): + errstring = "No user 99 found" + with self.assertRaisesRegexp(CommandError, errstring): + self.command.handle("split", "99", "org", "course", "run") + + def test_nonexistent_user_email(self): + errstring = "No user fake@example.com found" + with self.assertRaisesRegexp(CommandError, errstring): + self.command.handle("mongo", "fake@example.com", "org", "course", "run") + + +@ddt.ddt +class TestCreateCourse(ModuleStoreTestCase): + """ + Unit tests for creating a course in either old mongo or split mongo via command line + """ + + def setUp(self): + super(TestCreateCourse, self).setUp(create_user=True) + + @ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split) + def test_all_stores_user_email(self, store): + call_command( + "create_course", + store, + str(self.user.email), + "org", "course", "run" + ) + new_key = modulestore().make_course_key("org", "course", "run") + self.assertTrue( + modulestore().has_course(new_key), + "Could not find course in {}".format(store) + ) + # pylint: disable=protected-access + self.assertEqual(store, modulestore()._get_modulestore_for_courseid(new_key).get_modulestore_type()) diff --git a/cms/djangoapps/contentstore/management/commands/utils.py b/cms/djangoapps/contentstore/management/commands/utils.py new file mode 100644 index 0000000000..e2e13a3263 --- /dev/null +++ b/cms/djangoapps/contentstore/management/commands/utils.py @@ -0,0 +1,19 @@ +""" +Common methods for cms commands to use +""" +from django.contrib.auth.models import User + + +def user_from_str(identifier): + """ + Return a user identified by the given string. The string could be an email + address, or a stringified integer corresponding to the ID of the user in + the database. If no user could be found, a User.DoesNotExist exception + will be raised. + """ + try: + user_id = int(identifier) + except ValueError: + return User.objects.get(email=identifier) + + return User.objects.get(id=user_id) diff --git a/cms/djangoapps/contentstore/views/course.py b/cms/djangoapps/contentstore/views/course.py index 468d0efb5b..79056f749d 100644 --- a/cms/djangoapps/contentstore/views/course.py +++ b/cms/djangoapps/contentstore/views/course.py @@ -564,6 +564,22 @@ def _create_new_course(request, org, number, run, fields): Returns the URL for the course overview page. Raises DuplicateCourseError if the course already exists """ + store_for_new_course = ( + settings.FEATURES.get('DEFAULT_STORE_FOR_NEW_COURSE') or + modulestore().default_modulestore.get_modulestore_type() + ) + new_course = create_new_course_in_store(store_for_new_course, request.user, org, number, run, fields) + return JsonResponse({ + 'url': reverse_course_url('course_handler', new_course.id), + 'course_key': unicode(new_course.id), + }) + + +def create_new_course_in_store(store, user, org, number, run, fields): + """ + Create course in store w/ handling instructor enrollment, permissions, and defaulting the wiki slug. + Separated out b/c command line course creation uses this as well as the web interface. + """ # Set a unique wiki_slug for newly created courses. To maintain active wiki_slugs for # existing xml courses this cannot be changed in CourseDescriptor. # # TODO get rid of defining wiki slug in this org/course/run specific way and reconcile @@ -572,31 +588,22 @@ def _create_new_course(request, org, number, run, fields): definition_data = {'wiki_slug': wiki_slug} fields.update(definition_data) - store = modulestore() - store_for_new_course = ( - settings.FEATURES.get('DEFAULT_STORE_FOR_NEW_COURSE') or - store.default_modulestore.get_modulestore_type() - ) - with store.default_store(store_for_new_course): + with modulestore().default_store(store): # Creating the course raises DuplicateCourseError if an existing course with this org/name is found - new_course = store.create_course( + new_course = modulestore().create_course( org, number, run, - request.user.id, + user.id, fields=fields, ) # Make sure user has instructor and staff access to the new course - add_instructor(new_course.id, request.user, request.user) + add_instructor(new_course.id, user, user) # Initialize permissions for user in the new course - initialize_permissions(new_course.id, request.user) - - return JsonResponse({ - 'url': reverse_course_url('course_handler', new_course.id), - 'course_key': unicode(new_course.id), - }) + initialize_permissions(new_course.id, user) + return new_course def _rerun_course(request, org, number, run, fields):