Commit 53b73ca3 by Don Mitchell

Merge pull request #1208 from MITx/feature/cdodge/clone-course

fix up some thumbnail issues. Also we need to manage the user permission...
parents a2d283d9 69518652
......@@ -71,6 +71,23 @@ def _delete_course_group(location):
user.groups.remove(staff)
user.save()
'''
This is to be called only by either a command line code path or through an app which has already
asserted permissions to do this action
'''
def _copy_course_group(source, dest):
instructors = Group.objects.get(name=get_course_groupname_for_role(source, INSTRUCTOR_ROLE_NAME))
new_instructors_group = Group.objects.get(name=get_course_groupname_for_role(dest, INSTRUCTOR_ROLE_NAME))
for user in instructors.user_set.all():
user.groups.add(new_instructors_group)
user.save()
staff = Group.objects.get(name=get_course_groupname_for_role(source, STAFF_ROLE_NAME))
new_staff_group = Group.objects.get(name=get_course_groupname_for_role(dest, STAFF_ROLE_NAME))
for user in staff.user_set.all():
user.groups.add(new_staff_group)
user.save()
def add_user_to_course_group(caller, user, location, role):
# only admins can add/remove other users
......
......@@ -8,6 +8,8 @@ from xmodule.contentstore.django import contentstore
from xmodule.modulestore import Location
from xmodule.course_module import CourseDescriptor
from auth.authz import _copy_course_group
#
# To run from command line: rake cms:clone SOURCE_LOC=MITx/111/Foo1 DEST_LOC=MITx/135/Foo3
#
......@@ -23,9 +25,14 @@ class Command(BaseCommand):
source_location_str = args[0]
dest_location_str = args[1]
ms = modulestore('direct')
cs = contentstore()
print "Cloning course {0} to {1}".format(source_location_str, dest_location_str)
source_location = CourseDescriptor.id_to_location(source_location_str)
dest_location = CourseDescriptor.id_to_location(dest_location_str)
clone_course(modulestore('direct'), contentstore(), source_location, dest_location)
if clone_course(ms, cs, source_location, dest_location):
print "copying User permissions..."
_copy_course_group(source_location, dest_location)
......@@ -21,14 +21,18 @@ class Command(BaseCommand):
def handle(self, *args, **options):
if len(args) != 1:
raise CommandError("delete_course requires one arguments: <location>")
raise CommandError("delete_course requires one argument: <location>")
loc_str = args[0]
ms = modulestore('direct')
cs = contentstore()
if query_yes_no("Deleting course {0}. Confirm?".format(loc_str), default="no"):
if query_yes_no("Are you sure. This action cannot be undone!", default="no"):
loc = CourseDescriptor.id_to_location(loc_str)
if delete_course(modulestore('direct'), contentstore(), loc) == True:
if delete_course(ms, cs, loc) == True:
print 'removing User permissions from course....'
# in the django layer, we need to remove all the user permissions groups associated with this course
_delete_course_group(loc)
......
......@@ -13,6 +13,11 @@ from xmodule.modulestore.xml_importer import import_from_xml
import copy
from factories import *
from xmodule.modulestore.store_utilities import clone_course
from xmodule.modulestore.store_utilities import delete_course
from xmodule.modulestore.django import modulestore
from xmodule.contentstore.django import contentstore
from xmodule.course_module import CourseDescriptor
def parse_json(response):
"""Parse response, which is assumed to be json"""
......@@ -339,4 +344,45 @@ class ContentStoreTest(TestCase):
def test_edit_unit_full(self):
self.check_edit_unit('full')
def test_clone_course(self):
import_from_xml(modulestore(), 'common/test/data/', ['full'])
resp = self.client.post(reverse('create_new_course'), self.course_data)
self.assertEqual(resp.status_code, 200)
data = parse_json(resp)
self.assertEqual(data['id'], 'i4x://MITx/999/course/Robot_Super_Course')
ms = modulestore('direct')
cs = contentstore()
source_location = CourseDescriptor.id_to_location('edX/full/6.002_Spring_2012')
dest_location = CourseDescriptor.id_to_location('MITx/999/Robot_Super_Course')
clone_course(ms, cs, source_location, dest_location)
# now loop through all the units in the course and verify that the clone can render them, which
# means the objects are at least present
items = ms.get_items(Location(['i4x','edX', 'full', 'vertical', None]))
self.assertGreater(len(items), 0)
clone_items = ms.get_items(Location(['i4x', 'MITx','999','vertical', None]))
self.assertGreater(len(clone_items), 0)
for descriptor in items:
new_loc = descriptor.location._replace(org = 'MITx', course='999')
print "Checking {0} should now also be at {1}".format(descriptor.location.url(), new_loc.url())
resp = self.client.get(reverse('edit_unit', kwargs={'location': new_loc.url()}))
self.assertEqual(resp.status_code, 200)
def test_delete_course(self):
import_from_xml(modulestore(), 'common/test/data/', ['full'])
ms = modulestore('direct')
cs = contentstore()
location = CourseDescriptor.id_to_location('edX/full/6.002_Spring_2012')
delete_course(ms, cs, location)
items = ms.get_items(Location(['i4x','edX', 'full', 'vertical', None]))
self.assertEqual(len(items), 0)
......@@ -59,6 +59,14 @@ MODULESTORE = {
}
}
CONTENTSTORE = {
'ENGINE': 'xmodule.contentstore.mongo.MongoContentStore',
'OPTIONS': {
'host': 'localhost',
'db' : 'xcontent',
}
}
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
......
......@@ -18,7 +18,7 @@ class StaticContent(object):
self.content_type = content_type
self.data = data
self.last_modified_at = last_modified_at
self.thumbnail_location = thumbnail_location
self.thumbnail_location = Location(thumbnail_location)
@property
def is_thumbnail(self):
......
......@@ -47,7 +47,7 @@ class MongoContentStore(ContentStore):
try:
with self.fs.get(id) as fp:
return StaticContent(location, fp.displayname, fp.content_type, fp.read(),
fp.uploadDate, thumbnail_location = fp.thumbnail_location if 'thumbnail_location' in fp else None)
fp.uploadDate, thumbnail_location = fp.thumbnail_location if hasattr(fp, 'thumbnail_location') else None)
except NoFile:
raise NotFoundError()
......
......@@ -81,14 +81,15 @@ def clone_course(modulestore, contentstore, source_location, dest_location, dele
# be sure to update the pointer to the thumbnail
if content.thumbnail_location is not None:
content.thumbnail_location._replace(tag = dest_location.tag, org = dest_location.org,
content.thumbnail_location = content.thumbnail_location._replace(org = dest_location.org,
course = dest_location.course)
print "Cloning asset {0} to {1}".format(asset_loc, content.location)
contentstore.save(content)
return True
def delete_course(modulestore, contentstore, source_location):
# first check to see if the modulestore is Mongo backed
if not isinstance(modulestore, MongoModuleStore):
......
......@@ -399,6 +399,7 @@ end
namespace :cms do
desc "Clone existing MongoDB based course"
task :clone do
if ENV['SOURCE_LOC'] and ENV['DEST_LOC']
sh(django_admin(:cms, :dev, :clone, ENV['SOURCE_LOC'], ENV['DEST_LOC']))
else
......
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