Merge pull request #2302 from edx/db/django-command-migrate-to-split
Django command for migrating courses to split-mongo (and deleting)
This commit is contained in:
@@ -152,6 +152,9 @@ def clear_existing_modulestores():
|
||||
_MODULESTORES.clear()
|
||||
# pylint: disable=W0603
|
||||
global _loc_singleton
|
||||
cache = getattr(_loc_singleton, "cache", None)
|
||||
if cache:
|
||||
cache.clear()
|
||||
_loc_singleton = None
|
||||
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ class LocMapperStore(object):
|
||||
package_id = "{0.org}.{0.course}".format(course_location)
|
||||
# very like _interpret_location_id but w/o the _id
|
||||
location_id = self._construct_location_son(
|
||||
course_location.org, course_location.course,
|
||||
course_location.org, course_location.course,
|
||||
course_location.name if course_location.category == 'course' else None
|
||||
)
|
||||
|
||||
@@ -219,6 +219,11 @@ class LocMapperStore(object):
|
||||
return None
|
||||
result = None
|
||||
for candidate in maps:
|
||||
if get_course and 'name' in candidate['_id']:
|
||||
candidate_id = candidate['_id']
|
||||
return Location(
|
||||
'i4x', candidate_id['org'], candidate_id['course'], 'course', candidate_id['name']
|
||||
)
|
||||
old_course_id = self._generate_location_course_id(candidate['_id'])
|
||||
for old_name, cat_to_usage in candidate['block_map'].iteritems():
|
||||
for category, block_id in cat_to_usage.iteritems():
|
||||
@@ -240,7 +245,7 @@ class LocMapperStore(object):
|
||||
candidate['course_id'], branch=candidate['draft_branch'], block_id=block_id
|
||||
)
|
||||
self._cache_location_map_entry(old_course_id, location, published_locator, draft_locator)
|
||||
|
||||
|
||||
if get_course and category == 'course':
|
||||
result = location
|
||||
elif not get_course and block_id == locator.block_id:
|
||||
@@ -261,8 +266,6 @@ class LocMapperStore(object):
|
||||
return cached
|
||||
|
||||
location_id = self._interpret_location_course_id(old_style_course_id, location)
|
||||
if old_style_course_id is None:
|
||||
old_style_course_id = self._generate_location_course_id(location_id)
|
||||
|
||||
maps = self.location_map.find(location_id)
|
||||
maps = list(maps)
|
||||
@@ -320,10 +323,10 @@ class LocMapperStore(object):
|
||||
return {'_id': self._construct_location_son(location.org, location.course, location.name)}
|
||||
else:
|
||||
return bson.son.SON([('_id.org', location.org), ('_id.course', location.course)])
|
||||
|
||||
|
||||
def _generate_location_course_id(self, entry_id):
|
||||
"""
|
||||
Generate a Location course_id for the given entry's id
|
||||
Generate a Location course_id for the given entry's id.
|
||||
"""
|
||||
# strip id envelope if any
|
||||
entry_id = entry_id.get('_id', entry_id)
|
||||
@@ -334,7 +337,7 @@ class LocMapperStore(object):
|
||||
return '{0[_id.org]}/{0[_id.course]}'.format(entry_id)
|
||||
else:
|
||||
return '{0[org]}/{0[course]}'.format(entry_id)
|
||||
|
||||
|
||||
def _construct_location_son(self, org, course, name=None):
|
||||
"""
|
||||
Construct the SON needed to repr the location for either a query or an insertion
|
||||
@@ -401,6 +404,8 @@ class LocMapperStore(object):
|
||||
"""
|
||||
Get the course Locator for this old course id
|
||||
"""
|
||||
if not old_course_id:
|
||||
return None
|
||||
entry = self.cache.get(old_course_id)
|
||||
if entry is not None:
|
||||
if published:
|
||||
@@ -425,6 +430,8 @@ class LocMapperStore(object):
|
||||
"""
|
||||
For quick lookup of courses
|
||||
"""
|
||||
if not old_course_id:
|
||||
return
|
||||
self.cache.set(old_course_id, (published_course_locator, draft_course_locator))
|
||||
|
||||
def _cache_location_map_entry(self, old_course_id, location, published_usage, draft_usage):
|
||||
|
||||
@@ -23,7 +23,7 @@ class SplitMigrator(object):
|
||||
self.draft_modulestore = draft_modulestore
|
||||
self.loc_mapper = loc_mapper
|
||||
|
||||
def migrate_mongo_course(self, course_location, user_id, new_package_id=None):
|
||||
def migrate_mongo_course(self, course_location, user, new_package_id=None):
|
||||
"""
|
||||
Create a new course in split_mongo representing the published and draft versions of the course from the
|
||||
original mongo store. And return the new_package_id (which the caller can also get by calling
|
||||
@@ -32,7 +32,7 @@ class SplitMigrator(object):
|
||||
If the new course already exists, this raises DuplicateItemError
|
||||
|
||||
:param course_location: a Location whose category is 'course' and points to the course
|
||||
:param user_id: the user whose action is causing this migration
|
||||
:param user: the user whose action is causing this migration
|
||||
:param new_package_id: (optional) the Locator.package_id for the new course. Defaults to
|
||||
whatever translate_location_to_locator returns
|
||||
"""
|
||||
@@ -48,18 +48,18 @@ class SplitMigrator(object):
|
||||
new_course_root_locator = self.loc_mapper.translate_location(old_course_id, course_location)
|
||||
new_course = self.split_modulestore.create_course(
|
||||
course_location.org, original_course.display_name,
|
||||
user_id, id_root=new_package_id,
|
||||
user.id, id_root=new_package_id,
|
||||
fields=self._get_json_fields_translate_children(original_course, old_course_id, True),
|
||||
root_block_id=new_course_root_locator.block_id,
|
||||
master_branch=new_course_root_locator.branch
|
||||
)
|
||||
|
||||
self._copy_published_modules_to_course(new_course, course_location, old_course_id, user_id)
|
||||
self._add_draft_modules_to_course(new_package_id, old_course_id, course_location, user_id)
|
||||
self._copy_published_modules_to_course(new_course, course_location, old_course_id, user)
|
||||
self._add_draft_modules_to_course(new_package_id, old_course_id, course_location, user)
|
||||
|
||||
return new_package_id
|
||||
|
||||
def _copy_published_modules_to_course(self, new_course, old_course_loc, old_course_id, user_id):
|
||||
def _copy_published_modules_to_course(self, new_course, old_course_loc, old_course_id, user):
|
||||
"""
|
||||
Copy all of the modules from the 'direct' version of the course to the new split course.
|
||||
"""
|
||||
@@ -79,7 +79,7 @@ class SplitMigrator(object):
|
||||
old_course_id, module.location, True, add_entry_if_missing=True
|
||||
)
|
||||
_new_module = self.split_modulestore.create_item(
|
||||
course_version_locator, module.category, user_id,
|
||||
course_version_locator, module.category, user.id,
|
||||
block_id=new_locator.block_id,
|
||||
fields=self._get_json_fields_translate_children(module, old_course_id, True),
|
||||
continue_version=True
|
||||
@@ -94,7 +94,7 @@ class SplitMigrator(object):
|
||||
# children which meant some pointers were to non-existent locations in 'direct'
|
||||
self.split_modulestore.internal_clean_children(course_version_locator)
|
||||
|
||||
def _add_draft_modules_to_course(self, new_package_id, old_course_id, old_course_loc, user_id):
|
||||
def _add_draft_modules_to_course(self, new_package_id, old_course_id, old_course_loc, user):
|
||||
"""
|
||||
update each draft. Create any which don't exist in published and attach to their parents.
|
||||
"""
|
||||
@@ -124,12 +124,12 @@ class SplitMigrator(object):
|
||||
if name != 'children' and field.is_set_on(module):
|
||||
field.write_to(split_module, field.read_from(module))
|
||||
|
||||
_new_module = self.split_modulestore.update_item(split_module, user_id)
|
||||
_new_module = self.split_modulestore.update_item(split_module, user.id)
|
||||
else:
|
||||
# only a draft version (aka, 'private'). parent needs updated too.
|
||||
# create a new course version just in case the current head is also the prod head
|
||||
_new_module = self.split_modulestore.create_item(
|
||||
new_draft_course_loc, module.category, user_id,
|
||||
new_draft_course_loc, module.category, user.id,
|
||||
block_id=new_locator.block_id,
|
||||
fields=self._get_json_fields_translate_children(module, old_course_id, True)
|
||||
)
|
||||
@@ -156,7 +156,7 @@ class SplitMigrator(object):
|
||||
new_parent_cursor = idx + 1
|
||||
break
|
||||
new_parent.children.insert(new_parent_cursor, new_block_id)
|
||||
new_parent = self.split_modulestore.update_item(new_parent, user_id)
|
||||
new_parent = self.split_modulestore.update_item(new_parent, user.id)
|
||||
|
||||
def _get_json_fields_translate_children(self, xblock, old_course_id, published):
|
||||
"""
|
||||
|
||||
@@ -1284,6 +1284,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
|
||||
if index is None:
|
||||
raise ItemNotFoundError(package_id)
|
||||
# this is the only real delete in the system. should it do something else?
|
||||
log.info("deleting course from split-mongo: %s", package_id)
|
||||
self.db_connection.delete_course_index(index['_id'])
|
||||
|
||||
def get_errored_courses(self):
|
||||
|
||||
@@ -4,8 +4,8 @@ Modulestore configuration for test cases.
|
||||
|
||||
from uuid import uuid4
|
||||
from django.test import TestCase
|
||||
from xmodule.modulestore.django import editable_modulestore, \
|
||||
clear_existing_modulestores
|
||||
from xmodule.modulestore.django import (
|
||||
editable_modulestore, clear_existing_modulestores, loc_mapper)
|
||||
from xmodule.contentstore.django import contentstore
|
||||
|
||||
|
||||
@@ -225,6 +225,9 @@ class ModuleStoreTestCase(TestCase):
|
||||
if contentstore().fs_files:
|
||||
db = contentstore().fs_files.database
|
||||
db.connection.drop_database(db)
|
||||
location_mapper = loc_mapper()
|
||||
if location_mapper.db:
|
||||
location_mapper.location_map.drop()
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
|
||||
@@ -80,8 +80,8 @@ class TestLocationMapper(unittest.TestCase):
|
||||
Request translation, check package_id, block_id, and branch
|
||||
"""
|
||||
prob_locator = loc_mapper().translate_location(
|
||||
old_style_course_id,
|
||||
location,
|
||||
old_style_course_id,
|
||||
location,
|
||||
published= (branch=='published'),
|
||||
add_entry_if_missing=add_entry
|
||||
)
|
||||
@@ -114,7 +114,7 @@ class TestLocationMapper(unittest.TestCase):
|
||||
|
||||
new_style_package_id = '{}.geek_dept.{}.baz_run'.format(org, course)
|
||||
block_map = {
|
||||
'abc123': {'problem': 'problem2'},
|
||||
'abc123': {'problem': 'problem2'},
|
||||
'def456': {'problem': 'problem4'},
|
||||
'ghi789': {'problem': 'problem7'},
|
||||
}
|
||||
@@ -139,7 +139,7 @@ class TestLocationMapper(unittest.TestCase):
|
||||
|
||||
# add a distractor course (note that abc123 has a different translation in this one)
|
||||
distractor_block_map = {
|
||||
'abc123': {'problem': 'problem3'},
|
||||
'abc123': {'problem': 'problem3'},
|
||||
'def456': {'problem': 'problem4'},
|
||||
'ghi789': {'problem': 'problem7'},
|
||||
}
|
||||
|
||||
@@ -271,7 +271,8 @@ class TestMigration(unittest.TestCase):
|
||||
self.compare_dags(presplit, pre_child, split_child, published)
|
||||
|
||||
def test_migrator(self):
|
||||
self.migrator.migrate_mongo_course(self.course_location, random.getrandbits(32))
|
||||
user = mock.Mock(id=1)
|
||||
self.migrator.migrate_mongo_course(self.course_location, user)
|
||||
# now compare the migrated to the original course
|
||||
self.compare_courses(self.old_mongo, True)
|
||||
self.compare_courses(self.draft_mongo, False)
|
||||
|
||||
Reference in New Issue
Block a user