Commit 1dc6ea39 by Nimisha Asthagiri

Split branch settings LMS-11056

parent 3eecc963
...@@ -100,3 +100,11 @@ class ModuleStoreDraftAndPublished(BranchSettingMixin): ...@@ -100,3 +100,11 @@ class ModuleStoreDraftAndPublished(BranchSettingMixin):
@abstractmethod @abstractmethod
def convert_to_draft(self, location, user_id): def convert_to_draft(self, location, user_id):
raise NotImplementedError raise NotImplementedError
def _unsupported_revision_error(self, allowed_revisions=None):
if not allowed_revisions:
allowed_revisions = [
None, ModuleStoreEnum.RevisionOption.published_only, ModuleStoreEnum.RevisionOption.draft_only
]
return ValueError('revision not one of {}'.format(allowed_revisions))
...@@ -519,7 +519,9 @@ class DraftModuleStore(MongoModuleStore): ...@@ -519,7 +519,9 @@ class DraftModuleStore(MongoModuleStore):
elif revision is None: elif revision is None:
as_functions = [as_draft] as_functions = [as_draft]
else: else:
raise ValueError('revision not one of None, ModuleStoreEnum.RevisionOption.published_only, or ModuleStoreEnum.RevisionOption.all') raise self._unsupported_revision_error(
[None, ModuleStoreEnum.RevisionOption.published_only, ModuleStoreEnum.RevisionOption.all]
)
self._delete_subtree(location, as_functions) self._delete_subtree(location, as_functions)
def _delete_subtree(self, location, as_functions): def _delete_subtree(self, location, as_functions):
......
...@@ -125,7 +125,6 @@ class SplitMongoModuleStore(ModuleStoreWriteBase): ...@@ -125,7 +125,6 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
super(SplitMongoModuleStore, self).__init__(contentstore, **kwargs) super(SplitMongoModuleStore, self).__init__(contentstore, **kwargs)
self.branch_setting_func = kwargs.pop('branch_setting_func', lambda: ModuleStoreEnum.Branch.published_only)
self.db_connection = MongoConnection(**doc_store_config) self.db_connection = MongoConnection(**doc_store_config)
self.db = self.db_connection.database self.db = self.db_connection.database
...@@ -296,14 +295,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase): ...@@ -296,14 +295,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
''' '''
if course_locator.org and course_locator.course and course_locator.run: if course_locator.org and course_locator.course and course_locator.run:
if course_locator.branch is None: if course_locator.branch is None:
# default it based on branch_setting raise InsufficientSpecificationError(course_locator)
# NAATODO move this to your mixin
if self.branch_setting_func() == ModuleStoreEnum.Branch.draft_preferred:
course_locator = course_locator.for_branch(ModuleStoreEnum.BranchName.draft)
elif self.branch_setting_func() == ModuleStoreEnum.Branch.published_only:
course_locator = course_locator.for_branch(ModuleStoreEnum.BranchName.published)
else:
raise InsufficientSpecificationError(course_locator)
# use the course id # use the course id
index = self.db_connection.get_course_index(course_locator) index = self.db_connection.get_course_index(course_locator)
if index is None: if index is None:
...@@ -1806,8 +1798,8 @@ class SplitMongoModuleStore(ModuleStoreWriteBase): ...@@ -1806,8 +1798,8 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
new_block = source_blocks[encoded_block_id] new_block = source_blocks[encoded_block_id]
if destination_block: if destination_block:
if destination_block['edit_info']['update_version'] != new_block['edit_info']['update_version']: if destination_block['edit_info']['update_version'] != new_block['edit_info']['update_version']:
source_children = new_block['fields']['children'] source_children = new_block['fields'].get('children', [])
for child in destination_block['fields']['children']: for child in destination_block['fields'].get('children', []):
try: try:
source_children.index(child) source_children.index(child)
except ValueError: except ValueError:
......
...@@ -7,6 +7,7 @@ from split import SplitMongoModuleStore ...@@ -7,6 +7,7 @@ from split import SplitMongoModuleStore
from xmodule.modulestore import ModuleStoreEnum, PublishState from xmodule.modulestore import ModuleStoreEnum, PublishState
from xmodule.modulestore.draft_and_published import ModuleStoreDraftAndPublished from xmodule.modulestore.draft_and_published import ModuleStoreDraftAndPublished
from xmodule.modulestore.draft import DIRECT_ONLY_CATEGORIES from xmodule.modulestore.draft import DIRECT_ONLY_CATEGORIES
from xmodule.modulestore.exceptions import InsufficientSpecificationError
class DraftVersioningModuleStore(ModuleStoreDraftAndPublished, SplitMongoModuleStore): class DraftVersioningModuleStore(ModuleStoreDraftAndPublished, SplitMongoModuleStore):
...@@ -14,7 +15,36 @@ class DraftVersioningModuleStore(ModuleStoreDraftAndPublished, SplitMongoModuleS ...@@ -14,7 +15,36 @@ class DraftVersioningModuleStore(ModuleStoreDraftAndPublished, SplitMongoModuleS
A subclass of Split that supports a dual-branch fall-back versioning framework A subclass of Split that supports a dual-branch fall-back versioning framework
with a Draft branch that falls back to a Published branch. with a Draft branch that falls back to a Published branch.
""" """
def _lookup_course(self, course_locator):
"""
overrides the implementation of _lookup_course in SplitMongoModuleStore in order to
use the configured branch_setting in the course_locator
"""
if course_locator.org and course_locator.course and course_locator.run:
if course_locator.branch is None:
# default it based on branch_setting
branch_setting = self.get_branch_setting()
if branch_setting == ModuleStoreEnum.Branch.draft_preferred:
course_locator = course_locator.for_branch(ModuleStoreEnum.BranchName.draft)
elif branch_setting == ModuleStoreEnum.Branch.published_only:
course_locator = course_locator.for_branch(ModuleStoreEnum.BranchName.published)
else:
raise InsufficientSpecificationError(course_locator)
return super(DraftVersioningModuleStore, self)._lookup_course(course_locator)
def create_course(self, org, course, run, user_id, **kwargs): def create_course(self, org, course, run, user_id, **kwargs):
"""
Creates and returns the course.
Args:
org (str): the organization that owns the course
course (str): the name of the course
run (str): the name of the run
user_id: id of the user creating the course
kwargs: Any optional arguments understood by a subset of modulestores to customize instantiation
Returns: a CourseDescriptor
"""
master_branch = kwargs.pop('master_branch', ModuleStoreEnum.BranchName.draft) master_branch = kwargs.pop('master_branch', ModuleStoreEnum.BranchName.draft)
return super(DraftVersioningModuleStore, self).create_course( return super(DraftVersioningModuleStore, self).create_course(
org, course, run, user_id, master_branch=master_branch, **kwargs org, course, run, user_id, master_branch=master_branch, **kwargs
...@@ -47,7 +77,10 @@ class DraftVersioningModuleStore(ModuleStoreDraftAndPublished, SplitMongoModuleS ...@@ -47,7 +77,10 @@ class DraftVersioningModuleStore(ModuleStoreDraftAndPublished, SplitMongoModuleS
elif revision is None: elif revision is None:
branches_to_delete = [ModuleStoreEnum.BranchName.draft] branches_to_delete = [ModuleStoreEnum.BranchName.draft]
else: else:
raise ValueError('revision not one of None, ModuleStoreEnum.RevisionOption.published_only, or ModuleStoreEnum.RevisionOption.all') raise self._unsupported_revision_error(
[None, ModuleStoreEnum.RevisionOption.published_only, ModuleStoreEnum.RevisionOption.all]
)
for branch in branches_to_delete: for branch in branches_to_delete:
SplitMongoModuleStore.delete_item(self, location.for_branch(branch), user_id, **kwargs) SplitMongoModuleStore.delete_item(self, location.for_branch(branch), user_id, **kwargs)
...@@ -59,8 +92,10 @@ class DraftVersioningModuleStore(ModuleStoreDraftAndPublished, SplitMongoModuleS ...@@ -59,8 +92,10 @@ class DraftVersioningModuleStore(ModuleStoreDraftAndPublished, SplitMongoModuleS
return key.for_branch(ModuleStoreEnum.BranchName.published) return key.for_branch(ModuleStoreEnum.BranchName.published)
elif revision == ModuleStoreEnum.RevisionOption.draft_only: elif revision == ModuleStoreEnum.RevisionOption.draft_only:
return key.for_branch(ModuleStoreEnum.BranchName.draft) return key.for_branch(ModuleStoreEnum.BranchName.draft)
else: elif revision is None:
return key return key
else:
raise self._unsupported_revision_error()
def has_item(self, usage_key, revision=None): def has_item(self, usage_key, revision=None):
""" """
...@@ -163,30 +198,25 @@ class DraftVersioningModuleStore(ModuleStoreDraftAndPublished, SplitMongoModuleS ...@@ -163,30 +198,25 @@ class DraftVersioningModuleStore(ModuleStoreDraftAndPublished, SplitMongoModuleS
PublishState.public - published exists and is the same as draft PublishState.public - published exists and is the same as draft
PublishState.private - no published version exists PublishState.private - no published version exists
""" """
# TODO figure out what to say if xblock is not from the HEAD of its branch
def get_head(branch): def get_head(branch):
course_structure = self._lookup_course(xblock.location.course_key.for_branch(branch))['structure'] course_structure = self._lookup_course(xblock.location.course_key.for_branch(branch))['structure']
return self._get_block_from_structure(course_structure, xblock.location.block_id) return self._get_block_from_structure(course_structure, xblock.location.block_id)
if xblock.location.branch == ModuleStoreEnum.BranchName.draft: def get_version(block):
try: return block['edit_info']['update_version']
other = get_head(ModuleStoreEnum.BranchName.published)
except ItemNotFoundError: draft_head = get_head(ModuleStoreEnum.BranchName.draft)
return PublishState.private published_head = get_head(ModuleStoreEnum.BranchName.published)
elif xblock.location.branch == ModuleStoreEnum.BranchName.published:
other = get_head(ModuleStoreEnum.BranchName.draft) if not published_head:
# published version does not exist
return PublishState.private
elif get_version(draft_head) == get_version(published_head):
# published and draft versions are equal
return PublishState.public
else: else:
raise ValueError(u'{} is in a branch other than draft or published.'.format(xblock.location)) # published and draft versions differ
if not other:
if xblock.location.branch == ModuleStoreEnum.BranchName.draft:
return PublishState.private
else:
return PublishState.public
elif xblock.update_version != other['edit_info']['update_version']:
return PublishState.draft return PublishState.draft
else:
return PublishState.public
def convert_to_draft(self, location, user_id): def convert_to_draft(self, location, user_id):
""" """
......
...@@ -810,10 +810,22 @@ class TestMixedModuleStore(unittest.TestCase): ...@@ -810,10 +810,22 @@ class TestMixedModuleStore(unittest.TestCase):
self.initdb(default_ms) self.initdb(default_ms)
self._create_block_hierarchy() self._create_block_hierarchy()
problem_location = self.problem_x1a_1 # TODO - Remove these lines once LMS-2869 is implemented
problem_original_name = 'Problem_x1a_1' course_location = self.course_locations[self.MONGO_COURSEID]
problem_new_name = 'New Problem Name' self.store.publish(course_location, self.user_id)
problem_original_name = 'Problem_Original'
problem = self.store.create_child(
self.user_id, self.writable_chapter_location, 'problem', 'prob_block',
fields={'display_name': problem_original_name},
)
problem_location = problem.location.version_agnostic().for_branch(None)
# TODO - Uncomment out these lines once LMS-2869 is implemented
# problem_location = self.problem_x1a_1
# problem_original_name = 'Problem_x1a_1'
course_key = problem_location.course_key course_key = problem_location.course_key
problem_new_name = 'New Problem Name'
def assertNumProblems(display_name, expected_number): def assertNumProblems(display_name, expected_number):
""" """
...@@ -837,10 +849,12 @@ class TestMixedModuleStore(unittest.TestCase): ...@@ -837,10 +849,12 @@ class TestMixedModuleStore(unittest.TestCase):
# verify Draft problem # verify Draft problem
with self.store.branch_setting(ModuleStoreEnum.Branch.draft_preferred, course_key): with self.store.branch_setting(ModuleStoreEnum.Branch.draft_preferred, course_key):
self.assertTrue(self.store.has_item(problem_location))
assertProblemNameEquals(problem_original_name) assertProblemNameEquals(problem_original_name)
# verify Published problem doesn't exist # verify Published problem doesn't exist
with self.store.branch_setting(ModuleStoreEnum.Branch.published_only, course_key): with self.store.branch_setting(ModuleStoreEnum.Branch.published_only, course_key):
self.assertFalse(self.store.has_item(problem_location))
with self.assertRaises(ItemNotFoundError): with self.assertRaises(ItemNotFoundError):
self.store.get_item(problem_location) self.store.get_item(problem_location)
...@@ -849,6 +863,7 @@ class TestMixedModuleStore(unittest.TestCase): ...@@ -849,6 +863,7 @@ class TestMixedModuleStore(unittest.TestCase):
# verify Published problem # verify Published problem
with self.store.branch_setting(ModuleStoreEnum.Branch.published_only, course_key): with self.store.branch_setting(ModuleStoreEnum.Branch.published_only, course_key):
self.assertTrue(self.store.has_item(problem_location))
assertProblemNameEquals(problem_original_name) assertProblemNameEquals(problem_original_name)
# verify Draft-preferred # verify Draft-preferred
......
...@@ -13,7 +13,7 @@ from xblock.fields import Scope ...@@ -13,7 +13,7 @@ from xblock.fields import Scope
from xmodule.course_module import CourseDescriptor from xmodule.course_module import CourseDescriptor
from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.exceptions import (ItemNotFoundError, VersionConflictError, from xmodule.modulestore.exceptions import (ItemNotFoundError, VersionConflictError,
DuplicateItemError, DuplicateCourseError) DuplicateItemError, DuplicateCourseError, InsufficientSpecificationError)
from opaque_keys.edx.locator import CourseLocator, BlockUsageLocator, VersionTree, LocalId from opaque_keys.edx.locator import CourseLocator, BlockUsageLocator, VersionTree, LocalId
from xmodule.modulestore.inheritance import InheritanceMixin from xmodule.modulestore.inheritance import InheritanceMixin
from xmodule.x_module import XModuleMixin from xmodule.x_module import XModuleMixin
...@@ -667,7 +667,7 @@ class SplitModuleCourseTests(SplitModuleTest): ...@@ -667,7 +667,7 @@ class SplitModuleCourseTests(SplitModuleTest):
def test_get_course_negative(self): def test_get_course_negative(self):
# Now negative testing # Now negative testing
with self.assertRaises(ItemNotFoundError): with self.assertRaises(InsufficientSpecificationError):
modulestore().get_course(CourseLocator(org='edu', course='meh', run='blah')) modulestore().get_course(CourseLocator(org='edu', course='meh', run='blah'))
with self.assertRaises(ItemNotFoundError): with self.assertRaises(ItemNotFoundError):
modulestore().get_course(CourseLocator(org='edu', course='nosuchthing', run="run", branch=BRANCH_NAME_DRAFT)) modulestore().get_course(CourseLocator(org='edu', course='nosuchthing', run="run", branch=BRANCH_NAME_DRAFT))
......
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