diff --git a/cms/djangoapps/contentstore/management/commands/import.py b/cms/djangoapps/contentstore/management/commands/import.py index 1d15f1e7df..a8ec2c2685 100644 --- a/cms/djangoapps/contentstore/management/commands/import.py +++ b/cms/djangoapps/contentstore/management/commands/import.py @@ -26,4 +26,4 @@ class Command(BaseCommand): print "Importing. Data_dir={data}, course_dirs={courses}".format( data=data_dir, courses=course_dirs) - import_from_xml(modulestore(), data_dir, course_dirs, load_error_modules=False) + import_from_xml(modulestore('direct'), data_dir, course_dirs, load_error_modules=False) diff --git a/cms/envs/dev.py b/cms/envs/dev.py index 8401fa5c15..e5548df2d4 100644 --- a/cms/envs/dev.py +++ b/cms/envs/dev.py @@ -14,17 +14,23 @@ LOGGING = get_logger_config(ENV_ROOT / "log", tracking_filename="tracking.log", debug=True) +modulestore_options = { + 'default_class': 'xmodule.raw_module.RawDescriptor', + 'host': 'localhost', + 'db': 'xmodule', + 'collection': 'modulestore', + 'fs_root': GITHUB_REPO_ROOT, + 'render_template': 'mitxmako.shortcuts.render_to_string', +} + MODULESTORE = { 'default': { + 'ENGINE': 'xmodule.modulestore.mongo.DraftMongoModuleStore', + 'OPTIONS': modulestore_options + }, + 'direct': { 'ENGINE': 'xmodule.modulestore.mongo.MongoModuleStore', - 'OPTIONS': { - 'default_class': 'xmodule.raw_module.RawDescriptor', - 'host': 'localhost', - 'db': 'xmodule', - 'collection': 'modulestore', - 'fs_root': GITHUB_REPO_ROOT, - 'render_template': 'mitxmako.shortcuts.render_to_string', - } + 'OPTIONS': modulestore_options } } diff --git a/common/lib/xmodule/xmodule/modulestore/__init__.py b/common/lib/xmodule/xmodule/modulestore/__init__.py index 880159d8ed..3ee83449f9 100644 --- a/common/lib/xmodule/xmodule/modulestore/__init__.py +++ b/common/lib/xmodule/xmodule/modulestore/__init__.py @@ -345,7 +345,9 @@ class ModuleStore(object): Returns a list containing the top level XModuleDescriptors of the courses in this modulestore. ''' - raise NotImplementedError + # TODO (vshnayder): Why do I have to specify i4x here? + course_filter = Location("i4x", category="course") + return self.get_items(course_filter) def get_parent_locations(self, location): '''Find all locations that are the parents of this location. Needed diff --git a/common/lib/xmodule/xmodule/modulestore/draft.py b/common/lib/xmodule/xmodule/modulestore/draft.py new file mode 100644 index 0000000000..6293063ffa --- /dev/null +++ b/common/lib/xmodule/xmodule/modulestore/draft.py @@ -0,0 +1,128 @@ + +from . import ModuleStoreBase, Location +from .exceptions import ItemNotFoundError + + +class DraftModuleStore(ModuleStoreBase): + """ + This mixin modifies a modulestore to give it draft semantics. + That is, edits made to units are stored to locations that have the revision 'draft', + and when reads are made, they first read with revision 'draft', and then fall back + to the baseline revision only if 'draft' doesn't exist. + + This module also includes functionality to promote 'draft' modules (and optionally + their children) to published modules. + """ + + def get_item(self, location, depth=0): + """ + Returns an XModuleDescriptor instance for the item at location. + If location.revision is None, returns the item with the most + recent revision + + If any segment of the location is None except revision, raises + xmodule.modulestore.exceptions.InsufficientSpecificationError + + If no object is found at that location, raises + xmodule.modulestore.exceptions.ItemNotFoundError + + location: Something that can be passed to Location + + depth (int): An argument that some module stores may use to prefetch + descendents of the queried modules for more efficient results later + in the request. The depth is counted in the number of calls to + get_children() to cache. None indicates to cache all descendents + """ + try: + return super(DraftModuleStore, self).get_item(Location(location)._replace(revision='draft'), depth) + except ItemNotFoundError: + return super(DraftModuleStore, self).get_item(location, depth) + + def get_instance(self, course_id, location): + """ + Get an instance of this location, with policy for course_id applied. + TODO (vshnayder): this may want to live outside the modulestore eventually + """ + try: + return super(DraftModuleStore, self).get_instance(course_id, Location(location)._replace(revision='draft')) + except ItemNotFoundError: + return super(DraftModuleStore, self).get_instance(course_id, location) + + def get_items(self, location, depth=0): + """ + Returns a list of XModuleDescriptor instances for the items + that match location. Any element of location that is None is treated + as a wildcard that matches any value + + location: Something that can be passed to Location + + depth: An argument that some module stores may use to prefetch + descendents of the queried modules for more efficient results later + in the request. The depth is counted in the number of calls to + get_children() to cache. None indicates to cache all descendents + """ + draft_loc = Location(location)._replace(revision='draft') + draft_items = super(DraftModuleStore, self).get_items(draft_loc, depth) + items = super(DraftModuleStore, self).get_items(location, depth) + + draft_locs_found = set(item.location._replace(revision=None) for item in draft_items) + non_draft_items = [ + item + for item in items + if (item.location.revision != 'draft' + and item.location._replace(revision=None) not in draft_locs_found) + ] + return draft_items + non_draft_items + + def clone_item(self, source, location): + """ + Clone a new item that is a copy of the item at the location `source` + and writes it to `location` + """ + return super(DraftModuleStore, self).clone_item(source, Location(location)._replace(revision='draft')) + + def update_item(self, location, data): + """ + Set the data in the item specified by the location to + data + + location: Something that can be passed to Location + data: A nested dictionary of problem data + """ + return super(DraftModuleStore, self).update_item(Location(location)._replace(revision='draft'), data) + + def update_children(self, location, children): + """ + Set the children for the item specified by the location to + children + + location: Something that can be passed to Location + children: A list of child item identifiers + """ + return super(DraftModuleStore, self).update_children(Location(location)._replace(revision='draft'), children) + + def update_metadata(self, location, metadata): + """ + Set the metadata for the item specified by the location to + metadata + + location: Something that can be passed to Location + metadata: A nested dictionary of module metadata + """ + return super(DraftModuleStore, self).update_metadata(Location(location)._replace(revision='draft'), metadata) + + def delete_item(self, location): + """ + Delete an item from this modulestore + + location: Something that can be passed to Location + """ + return super(DraftModuleStore, self).delete_item(Location(location)._replace(revision='draft')) + + def get_parent_locations(self, location): + '''Find all locations that are the parents of this location. Needed + for path_to_location(). + + returns an iterable of things that can be passed to Location. + ''' + return super(DraftModuleStore, self).get_parent_locations(Location(location)._replace(revision='draft')) diff --git a/common/lib/xmodule/xmodule/modulestore/mongo.py b/common/lib/xmodule/xmodule/modulestore/mongo.py index 21e28e9d67..1e203c6a78 100644 --- a/common/lib/xmodule/xmodule/modulestore/mongo.py +++ b/common/lib/xmodule/xmodule/modulestore/mongo.py @@ -13,6 +13,7 @@ from xmodule.mako_module import MakoDescriptorSystem from xmodule.error_module import ErrorDescriptor from . import ModuleStoreBase, Location +from .draft import DraftModuleStore from .exceptions import (ItemNotFoundError, DuplicateItemError) @@ -341,3 +342,8 @@ class MongoModuleStore(ModuleStoreBase): are loaded on demand, rather than up front """ return {} + + +# DraftModuleStore is first, because it needs to intercept calls to MongoModuleStore +class DraftMongoModuleStore(DraftModuleStore, MongoModuleStore): + pass diff --git a/common/lib/xmodule/xmodule/templates.py b/common/lib/xmodule/xmodule/templates.py index 2937cddea6..41b1523709 100644 --- a/common/lib/xmodule/xmodule/templates.py +++ b/common/lib/xmodule/xmodule/templates.py @@ -75,6 +75,6 @@ def update_templates(): ), exc_info=True) continue - modulestore().update_item(template_location, template.data) - modulestore().update_children(template_location, template.children) - modulestore().update_metadata(template_location, template.metadata) + modulestore('direct').update_item(template_location, template.data) + modulestore('direct').update_children(template_location, template.children) + modulestore('direct').update_metadata(template_location, template.metadata)