Commit 42c02c80 by Mushtaq Ali

Merge pull request #9809 from edx/mushtaq/force-publish-command

'force_publish' command for publishing a course forcefully by making …
parents c67bcaef 11741f11
"""
Script for force publishing a course
"""
from django.core.management.base import BaseCommand, CommandError
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.django import modulestore
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
from .prompt import query_yes_no
from .utils import get_course_versions
# To run from command line: ./manage.py cms force_publish course-v1:org+course+run
class Command(BaseCommand):
"""Force publish a course"""
help = '''
Force publish a course. Takes two arguments:
<course_id>: the course id of the course you want to publish forcefully
commit: do the force publish
If you do not specify 'commit', the command will print out what changes would be made.
'''
def handle(self, *args, **options):
"""Execute the command"""
if len(args) not in {1, 2}:
raise CommandError("force_publish requires 1 or more argument: <course_id> |commit|")
try:
course_key = CourseKey.from_string(args[0])
except InvalidKeyError:
raise CommandError("Invalid course key.")
if not modulestore().get_course(course_key):
raise CommandError("Course not found.")
commit = False
if len(args) == 2:
commit = args[1] == 'commit'
# for now only support on split mongo
owning_store = modulestore()._get_modulestore_for_courselike(course_key) # pylint: disable=protected-access
if hasattr(owning_store, 'force_publish_course'):
versions = get_course_versions(args[0])
print "Course versions : {0}".format(versions)
if commit:
if query_yes_no("Are you sure to publish the {0} course forcefully?".format(course_key), default="no"):
# publish course forcefully
updated_versions = owning_store.force_publish_course(
course_key, ModuleStoreEnum.UserID.mgmt_command, commit
)
if updated_versions:
# if publish and draft were different
if versions['published-branch'] != versions['draft-branch']:
print "Success! Published the course '{0}' forcefully.".format(course_key)
print "Updated course versions : \n{0}".format(updated_versions)
else:
print "Course '{0}' is already in published state.".format(course_key)
else:
print "Error! Could not publish course {0}.".format(course_key)
else:
# if publish and draft were different
if versions['published-branch'] != versions['draft-branch']:
print "Dry run. Following would have been changed : "
print "Published branch version {0} changed to draft branch version {1}".format(
versions['published-branch'], versions['draft-branch']
)
else:
print "Dry run. Course '{0}' is already in published state.".format(course_key)
else:
raise CommandError("The owning modulestore does not support this command.")
"""
Tests for the force_publish management command
"""
import mock
from django.core.management.base import CommandError
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
from contentstore.management.commands.force_publish import Command
from contentstore.management.commands.utils import get_course_versions
class TestForcePublish(SharedModuleStoreTestCase):
"""
Tests for the force_publish management command
"""
@classmethod
def setUpClass(cls):
super(TestForcePublish, cls).setUpClass()
cls.course = CourseFactory.create(default_store=ModuleStoreEnum.Type.split)
cls.test_user_id = ModuleStoreEnum.UserID.test
cls.command = Command()
def test_no_args(self):
"""
Test 'force_publish' command with no arguments
"""
errstring = "force_publish requires 1 or more argument: <course_id> |commit"
with self.assertRaisesRegexp(CommandError, errstring):
self.command.handle()
def test_invalid_course_key(self):
"""
Test 'force_publish' command with invalid course key
"""
errstring = "Invalid course key."
with self.assertRaisesRegexp(CommandError, errstring):
self.command.handle('TestX/TS01')
def test_too_many_arguments(self):
"""
Test 'force_publish' command with more than 2 arguments
"""
errstring = "force_publish requires 1 or more argument: <course_id> |commit"
with self.assertRaisesRegexp(CommandError, errstring):
self.command.handle(unicode(self.course.id), 'commit', 'invalid-arg')
def test_course_key_not_found(self):
"""
Test 'force_publish' command with non-existing course key
"""
errstring = "Course not found."
with self.assertRaisesRegexp(CommandError, errstring):
self.command.handle(unicode('course-v1:org+course+run'))
def test_force_publish_non_split(self):
"""
Test 'force_publish' command doesn't work on non split courses
"""
course = CourseFactory.create(default_store=ModuleStoreEnum.Type.mongo)
errstring = 'The owning modulestore does not support this command.'
with self.assertRaisesRegexp(CommandError, errstring):
self.command.handle(unicode(course.id))
@SharedModuleStoreTestCase.modifies_courseware
def test_force_publish(self):
"""
Test 'force_publish' command
"""
# Add some changes to course
chapter = ItemFactory.create(category='chapter', parent_location=self.course.location)
self.store.create_child(
self.test_user_id,
chapter.location,
'html',
block_id='html_component'
)
# verify that course has changes.
self.assertTrue(self.store.has_changes(self.store.get_item(self.course.location)))
# get draft and publish branch versions
versions = get_course_versions(unicode(self.course.id))
draft_version = versions['draft-branch']
published_version = versions['published-branch']
# verify that draft and publish point to different versions
self.assertNotEqual(draft_version, published_version)
with mock.patch('contentstore.management.commands.force_publish.query_yes_no') as patched_yes_no:
patched_yes_no.return_value = True
# force publish course
self.command.handle(unicode(self.course.id), 'commit')
# verify that course has no changes
self.assertFalse(self.store.has_changes(self.store.get_item(self.course.location)))
# get new draft and publish branch versions
versions = get_course_versions(unicode(self.course.id))
new_draft_version = versions['draft-branch']
new_published_version = versions['published-branch']
# verify that the draft branch didn't change while the published branch did
self.assertEqual(draft_version, new_draft_version)
self.assertNotEqual(published_version, new_published_version)
# verify that draft and publish point to same versions now
self.assertEqual(new_draft_version, new_published_version)
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
Common methods for cms commands to use Common methods for cms commands to use
""" """
from django.contrib.auth.models import User from django.contrib.auth.models import User
from opaque_keys.edx.keys import CourseKey
from xmodule.modulestore.django import modulestore
def user_from_str(identifier): def user_from_str(identifier):
...@@ -17,3 +19,21 @@ def user_from_str(identifier): ...@@ -17,3 +19,21 @@ def user_from_str(identifier):
return User.objects.get(email=identifier) return User.objects.get(email=identifier)
return User.objects.get(id=user_id) return User.objects.get(id=user_id)
def get_course_versions(course_key):
"""
Fetches the latest course versions
:param course_key:
:return: { 'draft-branch' : value1, 'published-branch' : value2}
"""
course_locator = CourseKey.from_string(course_key)
store = modulestore()._get_modulestore_for_courselike(course_locator) # pylint: disable=protected-access
index_entry = store.get_course_index(course_locator)
if index_entry is not None:
return {
'draft-branch': index_entry['versions']['draft-branch'],
'published-branch': index_entry['versions']['published-branch']
}
return None
...@@ -445,6 +445,28 @@ class DraftVersioningModuleStore(SplitMongoModuleStore, ModuleStoreDraftAndPubli ...@@ -445,6 +445,28 @@ class DraftVersioningModuleStore(SplitMongoModuleStore, ModuleStoreDraftAndPubli
if index_entry is not None: if index_entry is not None:
self._update_head(draft_course_key, index_entry, ModuleStoreEnum.BranchName.draft, new_structure['_id']) self._update_head(draft_course_key, index_entry, ModuleStoreEnum.BranchName.draft, new_structure['_id'])
def force_publish_course(self, course_locator, user_id, commit=False):
"""
Helper method to forcefully publish a course,
making the published branch point to the same structure as the draft branch.
"""
versions = None
index_entry = self.get_course_index(course_locator)
if index_entry is not None:
versions = index_entry['versions']
if commit:
# update published branch version only if publish and draft point to different versions
if versions['published-branch'] != versions['draft-branch']:
self._update_head(
course_locator,
index_entry,
'published-branch',
index_entry['versions']['draft-branch']
)
self._flag_publish_event(course_locator)
return self.get_course_index(course_locator)['versions']
return versions
def get_course_history_info(self, course_locator): def get_course_history_info(self, course_locator):
""" """
See :py:meth `xmodule.modulestore.split_mongo.split.SplitMongoModuleStore.get_course_history_info` See :py:meth `xmodule.modulestore.split_mongo.split.SplitMongoModuleStore.get_course_history_info`
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment