Commit ded28af8 by Don Mitchell

Make Locators comply with UsageKey accessors

parent 6d7fe561
...@@ -12,7 +12,6 @@ from xmodule.html_module import HtmlDescriptor ...@@ -12,7 +12,6 @@ from xmodule.html_module import HtmlDescriptor
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
@unittest.skip("Not fixing split until we land opaque-keys 0.9")
class TemplateTests(unittest.TestCase): class TemplateTests(unittest.TestCase):
""" """
Test finding and using the templates (boilerplates) for xblocks. Test finding and using the templates (boilerplates) for xblocks.
...@@ -55,25 +54,25 @@ class TemplateTests(unittest.TestCase): ...@@ -55,25 +54,25 @@ class TemplateTests(unittest.TestCase):
def test_factories(self): def test_factories(self):
test_course = persistent_factories.PersistentCourseFactory.create( test_course = persistent_factories.PersistentCourseFactory.create(
course_id='testx.tempcourse', org='testx', offering='tempcourse', org='testx',
display_name='fun test course', user_id='testbot' display_name='fun test course', user_id='testbot'
) )
self.assertIsInstance(test_course, CourseDescriptor) self.assertIsInstance(test_course, CourseDescriptor)
self.assertEqual(test_course.display_name, 'fun test course') self.assertEqual(test_course.display_name, 'fun test course')
index_info = modulestore('split').get_course_index_info(test_course.location) index_info = modulestore('split').get_course_index_info(test_course.id)
self.assertEqual(index_info['org'], 'testx') self.assertEqual(index_info['org'], 'testx')
self.assertEqual(index_info['_id'], 'testx.tempcourse') self.assertEqual(index_info['offering'], 'tempcourse')
test_chapter = persistent_factories.ItemFactory.create(display_name='chapter 1', test_chapter = persistent_factories.ItemFactory.create(display_name='chapter 1',
parent_location=test_course.location) parent_location=test_course.location)
self.assertIsInstance(test_chapter, SequenceDescriptor) self.assertIsInstance(test_chapter, SequenceDescriptor)
# refetch parent which should now point to child # refetch parent which should now point to child
test_course = modulestore('split').get_course(test_course.id) test_course = modulestore('split').get_course(test_course.id.version_agnostic())
self.assertIn(test_chapter.location.block_id, test_course.children) self.assertIn(test_chapter.location.block_id, test_course.children)
with self.assertRaises(DuplicateCourseError): with self.assertRaises(DuplicateCourseError):
persistent_factories.PersistentCourseFactory.create( persistent_factories.PersistentCourseFactory.create(
course_id='testx.tempcourse', org='testx', offering='tempcourse', org='testx',
display_name='fun test course', user_id='testbot' display_name='fun test course', user_id='testbot'
) )
...@@ -82,7 +81,7 @@ class TemplateTests(unittest.TestCase): ...@@ -82,7 +81,7 @@ class TemplateTests(unittest.TestCase):
Test create_xblock to create non persisted xblocks Test create_xblock to create non persisted xblocks
""" """
test_course = persistent_factories.PersistentCourseFactory.create( test_course = persistent_factories.PersistentCourseFactory.create(
course_id='testx.tempcourse', org='testx', offering='tempcourse', org='testx',
display_name='fun test course', user_id='testbot' display_name='fun test course', user_id='testbot'
) )
...@@ -109,7 +108,7 @@ class TemplateTests(unittest.TestCase): ...@@ -109,7 +108,7 @@ class TemplateTests(unittest.TestCase):
try saving temporary xblocks try saving temporary xblocks
""" """
test_course = persistent_factories.PersistentCourseFactory.create( test_course = persistent_factories.PersistentCourseFactory.create(
course_id='testx.tempcourse', org='testx', offering='tempcourse', org='testx',
display_name='fun test course', user_id='testbot' display_name='fun test course', user_id='testbot'
) )
test_chapter = modulestore('split').create_xblock( test_chapter = modulestore('split').create_xblock(
...@@ -148,7 +147,7 @@ class TemplateTests(unittest.TestCase): ...@@ -148,7 +147,7 @@ class TemplateTests(unittest.TestCase):
def test_delete_course(self): def test_delete_course(self):
test_course = persistent_factories.PersistentCourseFactory.create( test_course = persistent_factories.PersistentCourseFactory.create(
course_id='edu.harvard.history.doomed', org='testx', offering='history.doomed', org='edu.harvard',
display_name='doomed test course', display_name='doomed test course',
user_id='testbot') user_id='testbot')
persistent_factories.ItemFactory.create(display_name='chapter 1', persistent_factories.ItemFactory.create(display_name='chapter 1',
...@@ -171,7 +170,7 @@ class TemplateTests(unittest.TestCase): ...@@ -171,7 +170,7 @@ class TemplateTests(unittest.TestCase):
Test get_block_generations Test get_block_generations
""" """
test_course = persistent_factories.PersistentCourseFactory.create( test_course = persistent_factories.PersistentCourseFactory.create(
course_id='edu.harvard.history.hist101', org='testx', offering='history.hist101', org='edu.harvard',
display_name='history test course', display_name='history test course',
user_id='testbot' user_id='testbot'
) )
...@@ -193,7 +192,9 @@ class TemplateTests(unittest.TestCase): ...@@ -193,7 +192,9 @@ class TemplateTests(unittest.TestCase):
second_problem = persistent_factories.ItemFactory.create( second_problem = persistent_factories.ItemFactory.create(
display_name='problem 2', display_name='problem 2',
parent_location=BlockUsageLocator.make_relative(updated_loc, block_id=sub.location.block_id), parent_location=BlockUsageLocator.make_relative(
updated_loc, block_type='problem', block_id=sub.location.block_id
),
user_id='testbot', category='problem', user_id='testbot', category='problem',
data="<problem></problem>" data="<problem></problem>"
) )
......
...@@ -7,7 +7,7 @@ import lxml ...@@ -7,7 +7,7 @@ import lxml
from contentstore.tests.utils import CourseTestCase from contentstore.tests.utils import CourseTestCase
from contentstore.utils import reverse_course_url from contentstore.utils import reverse_course_url
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
from xmodule.modulestore import parsers from xmodule.modulestore.locator import Locator
class TestCourseIndex(CourseTestCase): class TestCourseIndex(CourseTestCase):
...@@ -38,7 +38,7 @@ class TestCourseIndex(CourseTestCase): ...@@ -38,7 +38,7 @@ class TestCourseIndex(CourseTestCase):
for link in course_link_eles: for link in course_link_eles:
self.assertRegexpMatches( self.assertRegexpMatches(
link.get("href"), link.get("href"),
'course/slashes:{0}'.format(parsers.ALLOWED_ID_CHARS) 'course/slashes:{0}'.format(Locator.ALLOWED_ID_CHARS)
) )
# now test that url # now test that url
outline_response = authed_client.get(link.get("href"), {}, HTTP_ACCEPT='text/html') outline_response = authed_client.get(link.get("href"), {}, HTTP_ACCEPT='text/html')
......
...@@ -79,5 +79,8 @@ setup( ...@@ -79,5 +79,8 @@ setup(
'asset-location = xmodule.modulestore.locations:AssetLocation', 'asset-location = xmodule.modulestore.locations:AssetLocation',
'edx = xmodule.modulestore.locator:BlockUsageLocator', 'edx = xmodule.modulestore.locator:BlockUsageLocator',
], ],
'definition_key': [
'defx = xmodule.modulestore.locator:DefinitionLocator',
],
}, },
) )
...@@ -58,7 +58,7 @@ class DefinitionKey(OpaqueKey): ...@@ -58,7 +58,7 @@ class DefinitionKey(OpaqueKey):
KEY_TYPE = 'definition_key' KEY_TYPE = 'definition_key'
__slots__ = () __slots__ = ()
@abstractmethod @abstractproperty
def block_type(self): def block_type(self):
""" """
The XBlock type of this definition. The XBlock type of this definition.
...@@ -125,6 +125,10 @@ class UsageKey(CourseObjectMixin, OpaqueKey): ...@@ -125,6 +125,10 @@ class UsageKey(CourseObjectMixin, OpaqueKey):
""" """
raise NotImplementedError() raise NotImplementedError()
@property
def block_type(self):
return self.category
class OpaqueKeyReader(IdReader): class OpaqueKeyReader(IdReader):
""" """
......
...@@ -150,6 +150,7 @@ class LocMapperStore(object): ...@@ -150,6 +150,7 @@ class LocMapperStore(object):
entry = self._migrate_if_necessary([entry])[0] entry = self._migrate_if_necessary([entry])[0]
block_id = entry['block_map'].get(self.encode_key_for_mongo(location.name)) block_id = entry['block_map'].get(self.encode_key_for_mongo(location.name))
category = location.category
if block_id is None: if block_id is None:
if add_entry_if_missing: if add_entry_if_missing:
block_id = self._add_to_block_map( block_id = self._add_to_block_map(
...@@ -159,14 +160,15 @@ class LocMapperStore(object): ...@@ -159,14 +160,15 @@ class LocMapperStore(object):
raise ItemNotFoundError(location) raise ItemNotFoundError(location)
else: else:
# jump_to_id uses a None category. # jump_to_id uses a None category.
if location.category is None: if category is None:
if len(block_id) == 1: if len(block_id) == 1:
# unique match (most common case) # unique match (most common case)
category = block_id.keys()[0]
block_id = block_id.values()[0] block_id = block_id.values()[0]
else: else:
raise InvalidLocationError() raise InvalidLocationError()
elif location.category in block_id: elif category in block_id:
block_id = block_id[location.category] block_id = block_id[category]
elif add_entry_if_missing: elif add_entry_if_missing:
block_id = self._add_to_block_map(location, course_son, entry['block_map']) block_id = self._add_to_block_map(location, course_son, entry['block_map'])
else: else:
...@@ -179,10 +181,12 @@ class LocMapperStore(object): ...@@ -179,10 +181,12 @@ class LocMapperStore(object):
) )
published_usage = BlockUsageLocator( published_usage = BlockUsageLocator(
prod_course_locator, prod_course_locator,
block_type=category,
block_id=block_id block_id=block_id
) )
draft_usage = BlockUsageLocator( draft_usage = BlockUsageLocator(
prod_course_locator.for_branch(entry['draft_branch']), prod_course_locator.for_branch(entry['draft_branch']),
block_type=category,
block_id=block_id block_id=block_id
) )
if published: if published:
...@@ -285,6 +289,7 @@ class LocMapperStore(object): ...@@ -285,6 +289,7 @@ class LocMapperStore(object):
org=entry[entry_org], offering=entry[entry_offering], org=entry[entry_org], offering=entry[entry_offering],
branch=entry['prod_branch'] branch=entry['prod_branch']
), ),
block_type=category,
block_id=block_id block_id=block_id
) )
draft_locator = BlockUsageLocator( draft_locator = BlockUsageLocator(
...@@ -292,6 +297,7 @@ class LocMapperStore(object): ...@@ -292,6 +297,7 @@ class LocMapperStore(object):
org=entry[entry_org], offering=entry[entry_offering], org=entry[entry_org], offering=entry[entry_offering],
branch=entry['draft_branch'] branch=entry['draft_branch']
), ),
block_type=category,
block_id=block_id block_id=block_id
) )
self._cache_location_map_entry(location, published_locator, draft_locator) self._cache_location_map_entry(location, published_locator, draft_locator)
......
...@@ -13,15 +13,7 @@ from bson.errors import InvalidId ...@@ -13,15 +13,7 @@ from bson.errors import InvalidId
from opaque_keys import OpaqueKey, InvalidKeyError from opaque_keys import OpaqueKey, InvalidKeyError
from xmodule.modulestore.keys import CourseKey, UsageKey from xmodule.modulestore.keys import CourseKey, UsageKey, DefinitionKey
from xmodule.modulestore.parsers import (
parse_url,
parse_block_ref,
BRANCH_PREFIX,
BLOCK_PREFIX,
VERSION_PREFIX,
ALLOWED_ID_RE)
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -45,14 +37,10 @@ class Locator(OpaqueKey): ...@@ -45,14 +37,10 @@ class Locator(OpaqueKey):
Locator is an abstract base class: do not instantiate Locator is an abstract base class: do not instantiate
""" """
@abstractmethod BLOCK_TYPE_PREFIX = r"type"
def url(self): # Prefix for the version portion of a locator URL, when it is preceded by a course ID
""" VERSION_PREFIX = r"version"
Return a string containing the URL for this location. Raises ALLOWED_ID_CHARS = r'[\w\-~.:]'
InvalidKeyError if the instance doesn't have a
complete enough specification to generate a url
"""
raise NotImplementedError()
def __str__(self): def __str__(self):
''' '''
...@@ -86,35 +74,42 @@ class BlockLocatorBase(Locator): ...@@ -86,35 +74,42 @@ class BlockLocatorBase(Locator):
# Token separating org from offering # Token separating org from offering
ORG_SEPARATOR = '+' ORG_SEPARATOR = '+'
def version(self): # Prefix for the branch portion of a locator URL
""" BRANCH_PREFIX = r"branch"
Returns the ObjectId referencing this specific location. # Prefix for the block portion of a locator URL
""" BLOCK_PREFIX = r"block"
return self.version_guid
ALLOWED_ID_RE = re.compile(r'^' + Locator.ALLOWED_ID_CHARS + '+$', re.UNICODE)
URL_RE_SOURCE = r"""
((?P<org>{ALLOWED_ID_CHARS}+)\+(?P<offering>{ALLOWED_ID_CHARS}+)\+?)??
({BRANCH_PREFIX}\+(?P<branch>{ALLOWED_ID_CHARS}+)\+?)?
({VERSION_PREFIX}\+(?P<version_guid>[A-F0-9]+)\+?)?
({BLOCK_TYPE_PREFIX}\+(?P<block_type>{ALLOWED_ID_CHARS}+)\+?)?
({BLOCK_PREFIX}\+(?P<block_id>{ALLOWED_ID_CHARS}+))?
""".format(
ALLOWED_ID_CHARS=Locator.ALLOWED_ID_CHARS, BRANCH_PREFIX=BRANCH_PREFIX,
VERSION_PREFIX=Locator.VERSION_PREFIX, BLOCK_TYPE_PREFIX=Locator.BLOCK_TYPE_PREFIX, BLOCK_PREFIX=BLOCK_PREFIX
)
URL_RE = re.compile('^' + URL_RE_SOURCE + '$', re.IGNORECASE | re.VERBOSE | re.UNICODE)
def url(self):
"""
Return a string containing the URL for this location.
"""
return self.NAMESPACE_SEPARATOR.join([self.CANONICAL_NAMESPACE, self._to_string()])
@classmethod @classmethod
def _parse_url(cls, url): def parse_url(cls, string):
"""
url must be a string beginning with 'edx:' and containing
either a valid version_guid or org & offering (with optional branch), or both.
""" """
if not isinstance(url, basestring): Raises InvalidKeyError if string cannot be parsed.
raise TypeError('%s is not an instance of basestring' % url)
parse = parse_url(url) If it can be parsed as a version_guid with no preceding org + offering, returns a dict
if not parse: with key 'version_guid' and the value,
raise InvalidKeyError(cls, url)
if parse['version_guid']:
parse['version_guid'] = cls.as_object_id(parse['version_guid'])
return parse If it can be parsed as a org + offering, returns a dict
with key 'id' and optional keys 'branch' and 'version_guid'.
"""
match = cls.URL_RE.match(string)
if not match:
raise InvalidKeyError(cls, string)
return match.groupdict()
@property @property
def package_id(self): def package_id(self):
...@@ -130,13 +125,10 @@ class CourseLocator(BlockLocatorBase, CourseKey): ...@@ -130,13 +125,10 @@ class CourseLocator(BlockLocatorBase, CourseKey):
CourseLocator(version_guid=ObjectId('519665f6223ebd6980884f2b')) CourseLocator(version_guid=ObjectId('519665f6223ebd6980884f2b'))
CourseLocator(org='mit.eecs', offering='6.002x') CourseLocator(org='mit.eecs', offering='6.002x')
CourseLocator(org='mit.eecs', offering='6002x', branch = 'published') CourseLocator(org='mit.eecs', offering='6002x', branch = 'published')
CourseLocator.from_string('edx:version/519665f6223ebd6980884f2b') CourseLocator.from_string('course-locator:version+519665f6223ebd6980884f2b')
CourseLocator.from_string('version/519665f6223ebd6980884f2b') CourseLocator.from_string('course-locator:mit.eecs+6002x')
CourseLocator.from_string('edx:mit.eecs+6002x') CourseLocator.from_string('course-locator:mit.eecs+6002x+branch+published')
CourseLocator.from_string('mit.eecs+6002x') CourseLocator.from_string('course-locator:mit.eecs+6002x+branch+published+version+519665f6223ebd6980884f2b')
CourseLocator.from_string('edx:mit.eecs+6002x/branch/published')
CourseLocator.from_string('edx:mit.eecs+6002x/branch/published/version/519665f6223ebd6980884f2b')
CourseLocator.from_string('mit.eecs+6002x/branch/published/version/519665f6223ebd6980884f2b')
Should have at least a specific org & offering (id for the course as if it were a project w/ Should have at least a specific org & offering (id for the course as if it were a project w/
versions) with optional 'branch', versions) with optional 'branch',
...@@ -163,7 +155,7 @@ class CourseLocator(BlockLocatorBase, CourseKey): ...@@ -163,7 +155,7 @@ class CourseLocator(BlockLocatorBase, CourseKey):
if version_guid: if version_guid:
version_guid = self.as_object_id(version_guid) version_guid = self.as_object_id(version_guid)
if not all(field is None or ALLOWED_ID_RE.match(field) for field in [org, offering, branch]): if not all(field is None or self.ALLOWED_ID_RE.match(field) for field in [org, offering, branch]):
raise InvalidKeyError(self.__class__, [org, offering, branch]) raise InvalidKeyError(self.__class__, [org, offering, branch])
super(CourseLocator, self).__init__( super(CourseLocator, self).__init__(
...@@ -173,31 +165,27 @@ class CourseLocator(BlockLocatorBase, CourseKey): ...@@ -173,31 +165,27 @@ class CourseLocator(BlockLocatorBase, CourseKey):
version_guid=version_guid version_guid=version_guid
) )
if self.version_guid is None and self.org is None and self.offering is None: if self.version_guid is None and (self.org is None or self.offering is None):
raise InvalidKeyError(self.__class__, "Either version_guid or org and offering should be set") raise InvalidKeyError(self.__class__, "Either version_guid or org and offering should be set")
def version(self):
"""
Returns the ObjectId referencing this specific location.
"""
return self.version_guid
@classmethod @classmethod
def _from_string(cls, serialized): def _from_string(cls, serialized):
""" """
Return a CourseLocator parsing the given serialized string Return a CourseLocator parsing the given serialized string
:param serialized: matches the string to a CourseLocator :param serialized: matches the string to a CourseLocator
""" """
kwargs = cls._parse_url(serialized) parse = cls.parse_url(serialized)
try:
return cls(**{key: kwargs.get(key) for key in cls.KEY_FIELDS})
except ValueError:
raise InvalidKeyError(cls, "Either version_guid or org and offering should be set: {}".format(serialized))
def is_fully_specified(self): if parse['version_guid']:
""" parse['version_guid'] = cls.as_object_id(parse['version_guid'])
Returns True if either version_guid is specified, or org+offering+branch
are specified. return cls(**{key: parse.get(key) for key in cls.KEY_FIELDS})
This should always return True, since this should be validated in the constructor.
"""
return (
self.version_guid is not None or
(self.org is not None and self.offering is not None and self.branch is not None)
)
def html_id(self): def html_id(self):
""" """
...@@ -212,6 +200,7 @@ class CourseLocator(BlockLocatorBase, CourseKey): ...@@ -212,6 +200,7 @@ class CourseLocator(BlockLocatorBase, CourseKey):
def make_usage_key(self, block_type, block_id): def make_usage_key(self, block_type, block_id):
return BlockUsageLocator( return BlockUsageLocator(
course_key=self, course_key=self,
block_type=block_type,
block_id=block_id block_id=block_id
) )
...@@ -280,13 +269,13 @@ class CourseLocator(BlockLocatorBase, CourseKey): ...@@ -280,13 +269,13 @@ class CourseLocator(BlockLocatorBase, CourseKey):
if self.offering: if self.offering:
parts.append(unicode(self.package_id)) parts.append(unicode(self.package_id))
if self.branch: if self.branch:
parts.append(u"{prefix}{branch}".format(prefix=BRANCH_PREFIX, branch=self.branch)) parts.append(u"{prefix}+{branch}".format(prefix=self.BRANCH_PREFIX, branch=self.branch))
if self.version_guid: if self.version_guid:
parts.append(u"{prefix}{guid}".format(prefix=VERSION_PREFIX, guid=self.version_guid)) parts.append(u"{prefix}+{guid}".format(prefix=self.VERSION_PREFIX, guid=self.version_guid))
return u"/".join(parts) return u"+".join(parts)
class BlockUsageLocator(BlockLocatorBase, UsageKey): # TODO implement UsageKey methods class BlockUsageLocator(BlockLocatorBase, UsageKey):
""" """
Encodes a location. Encodes a location.
...@@ -305,12 +294,13 @@ class BlockUsageLocator(BlockLocatorBase, UsageKey): # TODO implement UsageKey ...@@ -305,12 +294,13 @@ class BlockUsageLocator(BlockLocatorBase, UsageKey): # TODO implement UsageKey
branch : string branch : string
""" """
CANONICAL_NAMESPACE = 'edx' CANONICAL_NAMESPACE = 'edx'
KEY_FIELDS = ('course_key', 'block_id') KEY_FIELDS = ('course_key', 'block_type', 'block_id')
# fake out class instrospection as this is an attr in this class's instances # fake out class instrospection as this is an attr in this class's instances
course_key = None course_key = None
block_type = None
def __init__(self, course_key, block_id): def __init__(self, course_key, block_type, block_id):
""" """
Construct a BlockUsageLocator Construct a BlockUsageLocator
""" """
...@@ -318,7 +308,7 @@ class BlockUsageLocator(BlockLocatorBase, UsageKey): # TODO implement UsageKey ...@@ -318,7 +308,7 @@ class BlockUsageLocator(BlockLocatorBase, UsageKey): # TODO implement UsageKey
if block_id is None: if block_id is None:
raise InvalidKeyError(self.__class__, "Missing block id") raise InvalidKeyError(self.__class__, "Missing block id")
super(BlockUsageLocator, self).__init__(course_key=course_key, block_id=block_id) super(BlockUsageLocator, self).__init__(course_key=course_key, block_type=block_type, block_id=block_id)
@classmethod @classmethod
def _from_string(cls, serialized): def _from_string(cls, serialized):
...@@ -326,11 +316,11 @@ class BlockUsageLocator(BlockLocatorBase, UsageKey): # TODO implement UsageKey ...@@ -326,11 +316,11 @@ class BlockUsageLocator(BlockLocatorBase, UsageKey): # TODO implement UsageKey
Requests CourseLocator to deserialize its part and then adds the local deserialization of block Requests CourseLocator to deserialize its part and then adds the local deserialization of block
""" """
course_key = CourseLocator._from_string(serialized) course_key = CourseLocator._from_string(serialized)
parsed_parts = parse_url(serialized) parsed_parts = cls.parse_url(serialized)
block_id = parsed_parts.get('block_id') block_id = parsed_parts.get('block_id', None)
if block_id is None: if block_id is None:
raise InvalidKeyError(cls, serialized) raise InvalidKeyError(cls, serialized)
return cls(course_key, block_id) return cls(course_key, parsed_parts.get('block_type'), block_id)
def version_agnostic(self): def version_agnostic(self):
""" """
...@@ -342,7 +332,8 @@ class BlockUsageLocator(BlockLocatorBase, UsageKey): # TODO implement UsageKey ...@@ -342,7 +332,8 @@ class BlockUsageLocator(BlockLocatorBase, UsageKey): # TODO implement UsageKey
""" """
return BlockUsageLocator( return BlockUsageLocator(
course_key=self.course_key.version_agnostic(), course_key=self.course_key.version_agnostic(),
block_id=self.block_id block_type=self.block_type,
block_id=self.block_id,
) )
def course_agnostic(self): def course_agnostic(self):
...@@ -354,6 +345,7 @@ class BlockUsageLocator(BlockLocatorBase, UsageKey): # TODO implement UsageKey ...@@ -354,6 +345,7 @@ class BlockUsageLocator(BlockLocatorBase, UsageKey): # TODO implement UsageKey
""" """
return BlockUsageLocator( return BlockUsageLocator(
course_key=self.course_key.course_agnostic(), course_key=self.course_key.course_agnostic(),
block_type=self.block_type,
block_id=self.block_id block_id=self.block_id
) )
...@@ -363,6 +355,17 @@ class BlockUsageLocator(BlockLocatorBase, UsageKey): # TODO implement UsageKey ...@@ -363,6 +355,17 @@ class BlockUsageLocator(BlockLocatorBase, UsageKey): # TODO implement UsageKey
""" """
return BlockUsageLocator( return BlockUsageLocator(
self.course_key.for_branch(branch), self.course_key.for_branch(branch),
block_type=self.block_type,
block_id=self.block_id
)
def for_version(self, version_guid):
"""
Return a UsageLocator for the same block in a different branch of the course.
"""
return BlockUsageLocator(
self.course_key.for_version(version_guid),
block_type=self.block_type,
block_id=self.block_id block_id=self.block_id
) )
...@@ -370,11 +373,10 @@ class BlockUsageLocator(BlockLocatorBase, UsageKey): # TODO implement UsageKey ...@@ -370,11 +373,10 @@ class BlockUsageLocator(BlockLocatorBase, UsageKey): # TODO implement UsageKey
def _parse_block_ref(cls, block_ref): def _parse_block_ref(cls, block_ref):
if isinstance(block_ref, LocalId): if isinstance(block_ref, LocalId):
return block_ref return block_ref
elif len(block_ref) > 0 and cls.ALLOWED_ID_RE.match(block_ref):
return block_ref
else: else:
parse = parse_block_ref(block_ref) raise InvalidKeyError(cls, block_ref)
if not parse:
raise InvalidKeyError(cls, block_ref)
return parse.get('block_id')
@property @property
def definition_key(self): def definition_key(self):
...@@ -400,6 +402,9 @@ class BlockUsageLocator(BlockLocatorBase, UsageKey): # TODO implement UsageKey ...@@ -400,6 +402,9 @@ class BlockUsageLocator(BlockLocatorBase, UsageKey): # TODO implement UsageKey
def version_guid(self): def version_guid(self):
return self.course_key.version_guid return self.course_key.version_guid
def version(self):
return self.course_key.version_guid
@property @property
def name(self): def name(self):
""" """
...@@ -411,7 +416,7 @@ class BlockUsageLocator(BlockLocatorBase, UsageKey): # TODO implement UsageKey ...@@ -411,7 +416,7 @@ class BlockUsageLocator(BlockLocatorBase, UsageKey): # TODO implement UsageKey
return self.course_key.is_fully_specified() return self.course_key.is_fully_specified()
@classmethod @classmethod
def make_relative(cls, course_locator, block_id): def make_relative(cls, course_locator, block_type, block_id):
""" """
Return a new instance which has the given block_id in the given course Return a new instance which has the given block_id in the given course
:param course_locator: may be a BlockUsageLocator in the same snapshot :param course_locator: may be a BlockUsageLocator in the same snapshot
...@@ -420,6 +425,7 @@ class BlockUsageLocator(BlockLocatorBase, UsageKey): # TODO implement UsageKey ...@@ -420,6 +425,7 @@ class BlockUsageLocator(BlockLocatorBase, UsageKey): # TODO implement UsageKey
course_locator = course_locator.course_key course_locator = course_locator.course_key
return BlockUsageLocator( return BlockUsageLocator(
course_key=course_locator, course_key=course_locator,
block_type=block_type,
block_id=block_id block_id=block_id
) )
...@@ -428,34 +434,17 @@ class BlockUsageLocator(BlockLocatorBase, UsageKey): # TODO implement UsageKey ...@@ -428,34 +434,17 @@ class BlockUsageLocator(BlockLocatorBase, UsageKey): # TODO implement UsageKey
Return a new instance which has the this block_id in the given course Return a new instance which has the this block_id in the given course
:param course_key: a CourseKey object representing the new course to map into :param course_key: a CourseKey object representing the new course to map into
""" """
return BlockUsageLocator.make_relative(course_key, self.block_id) return BlockUsageLocator.make_relative(course_key, self.block_type, self.block_id)
def url_reverse(self, prefix, postfix=''):
"""
Do what reverse is supposed to do but seems unable to do. Generate a url using prefix unicode(self) postfix
:param prefix: the beginning of the url (will be forced to begin and end with / if non-empty)
:param postfix: the part to append to the url (will be forced to begin w/ / if non-empty)
"""
if prefix:
if not prefix.endswith('/'):
prefix += '/'
if not prefix.startswith('/'):
prefix = '/' + prefix
else:
prefix = '/'
if postfix and not postfix.startswith('/'):
postfix = '/' + postfix
elif postfix is None:
postfix = ''
return prefix + unicode(self) + postfix
def _to_string(self): def _to_string(self):
""" """
Return a string representing this location. Return a string representing this location.
""" """
return u"{course_key}/{BLOCK_PREFIX}{block_id}".format( return u"{course_key}+{BLOCK_TYPE_PREFIX}+{block_type}+{BLOCK_PREFIX}+{block_id}".format(
course_key=self.course_key._to_string(), course_key=self.course_key._to_string(),
BLOCK_PREFIX=BLOCK_PREFIX, BLOCK_TYPE_PREFIX=self.BLOCK_TYPE_PREFIX,
block_type=self.block_type,
BLOCK_PREFIX=self.BLOCK_PREFIX,
block_id=self.block_id block_id=self.block_id
) )
...@@ -467,43 +456,61 @@ class BlockUsageLocator(BlockLocatorBase, UsageKey): # TODO implement UsageKey ...@@ -467,43 +456,61 @@ class BlockUsageLocator(BlockLocatorBase, UsageKey): # TODO implement UsageKey
place, but I have no way to override. We should clearly define the purpose and restrictions of this place, but I have no way to override. We should clearly define the purpose and restrictions of this
(e.g., I'm assuming periods are fine). (e.g., I'm assuming periods are fine).
""" """
return re.sub('[^\w-]', '-', self._to_string()) return unicode(self)
class DefinitionLocator(Locator): class DefinitionLocator(Locator, DefinitionKey):
""" """
Container for how to locate a description (the course-independent content). Container for how to locate a description (the course-independent content).
""" """
CANONICAL_NAMESPACE = 'defx' CANONICAL_NAMESPACE = 'defx'
KEY_FIELDS = ('definition_id',) KEY_FIELDS = ('definition_id', 'block_type')
URL_RE = re.compile(r'^defx:' + VERSION_PREFIX + '([^/]+)$', re.IGNORECASE) # override the abstractproperty
block_type = None
definition_id = None
def __init__(self, definition_id): def __init__(self, block_type, definition_id):
if isinstance(definition_id, LocalId): if isinstance(definition_id, LocalId):
super(DefinitionLocator, self).__init__(definition_id) super(DefinitionLocator, self).__init__(definition_id=definition_id, block_type=block_type)
elif isinstance(definition_id, basestring): elif isinstance(definition_id, basestring):
regex_match = self.URL_RE.match(definition_id) try:
if regex_match is not None: definition_id = self.as_object_id(definition_id)
super(DefinitionLocator, self).__init__(self.as_object_id(regex_match.group(1))) except ValueError:
else: raise InvalidKeyError(self, definition_id)
super(DefinitionLocator, self).__init__(self.as_object_id(definition_id)) super(DefinitionLocator, self).__init__(definition_id=definition_id, block_type=block_type)
else: elif isinstance(definition_id, ObjectId):
super(DefinitionLocator, self).__init__(self.as_object_id(definition_id)) super(DefinitionLocator, self).__init__(definition_id=definition_id, block_type=block_type)
def _to_string(self): def _to_string(self):
''' '''
Return a string representing this location. Return a string representing this location.
unicode(self) returns something like this: "version/519665f6223ebd6980884f2b" unicode(self) returns something like this: "519665f6223ebd6980884f2b+type+problem"
''' '''
return VERSION_PREFIX + str(self.definition_id) return u"{}+{}+{}".format(unicode(self.definition_id), self.BLOCK_TYPE_PREFIX, self.block_type)
def url(self): URL_RE = re.compile(
r"^(?P<definition_id>[A-F0-9]+)\+{}\+(?P<block_type>{ALLOWED_ID_CHARS}+)$".format(
Locator.BLOCK_TYPE_PREFIX, ALLOWED_ID_CHARS=Locator.ALLOWED_ID_CHARS
),
re.IGNORECASE | re.VERBOSE | re.UNICODE
)
@classmethod
def _from_string(cls, serialized):
""" """
Return a string containing the URL for this location. Return a DefinitionLocator parsing the given serialized string
url(self) returns something like this: 'defx:version/519665f6223ebd6980884f2b' :param serialized: matches the string to
""" """
return u'defx:' + self._to_string() parse = cls.URL_RE.match(serialized)
if not parse:
raise InvalidKeyError(cls, serialized)
parse = parse.groupdict()
if parse['definition_id']:
parse['definition_id'] = cls.as_object_id(parse['definition_id'])
return cls(**{key: parse.get(key) for key in cls.KEY_FIELDS})
def version(self): def version(self):
""" """
......
import re
# Prefix for the branch portion of a locator URL
BRANCH_PREFIX = r"branch/"
# Prefix for the block portion of a locator URL
BLOCK_PREFIX = r"block/"
# Prefix for the version portion of a locator URL, when it is preceded by a course ID
VERSION_PREFIX = r"version/"
ALLOWED_ID_CHARS = r'[\w\-~.:+]'
ALLOWED_ID_RE = re.compile(r'^{}+$'.format(ALLOWED_ID_CHARS), re.UNICODE)
# NOTE: if we need to support period in place of +, make it aggressive (take the first period in the string)
URL_RE_SOURCE = r"""
((?P<org>{ALLOWED_ID_CHARS}+)\+(?P<offering>{ALLOWED_ID_CHARS}+)/?)?
({BRANCH_PREFIX}(?P<branch>{ALLOWED_ID_CHARS}+)/?)?
({VERSION_PREFIX}(?P<version_guid>[A-F0-9]+)/?)?
({BLOCK_PREFIX}(?P<block_id>{ALLOWED_ID_CHARS}+))?
""".format(
ALLOWED_ID_CHARS=ALLOWED_ID_CHARS, BRANCH_PREFIX=BRANCH_PREFIX,
VERSION_PREFIX=VERSION_PREFIX, BLOCK_PREFIX=BLOCK_PREFIX
)
URL_RE = re.compile('^' + URL_RE_SOURCE + '$', re.IGNORECASE | re.VERBOSE | re.UNICODE)
def parse_url(string):
"""
followed by either a version_guid or a org + offering pair. If tag_optional, then
the url does not have to start with the tag and edx will be assumed.
Examples:
'edx:version/0123FFFF'
'edx:mit.eecs.6002x'
'edx:mit.eecs.6002x/branch/published'
'edx:mit.eecs.6002x/branch/published/block/HW3'
'edx:mit.eecs.6002x/branch/published/version/000eee12345/block/HW3'
This returns None if string cannot be parsed.
If it can be parsed as a version_guid with no preceding org + offering, returns a dict
with key 'version_guid' and the value,
If it can be parsed as a org + offering, returns a dict
with key 'id' and optional keys 'branch' and 'version_guid'.
"""
match = URL_RE.match(string)
if not match:
return None
matched_dict = match.groupdict()
return matched_dict
def parse_block_ref(string):
r"""
A block_ref is a string of url safe characters (see ALLOWED_ID_CHARS)
If string is a block_ref, returns a dict with key 'block_ref' and the value,
otherwise returns None.
"""
if ALLOWED_ID_RE.match(string):
return {'block_id': string}
return None
...@@ -108,6 +108,7 @@ class CachingDescriptorSystem(MakoDescriptorSystem): ...@@ -108,6 +108,7 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
offering=course_entry_override.get('offering'), offering=course_entry_override.get('offering'),
branch=course_entry_override.get('branch'), branch=course_entry_override.get('branch'),
), ),
block_type=json_data.get('category'),
block_id=block_id, block_id=block_id,
) )
...@@ -131,6 +132,7 @@ class CachingDescriptorSystem(MakoDescriptorSystem): ...@@ -131,6 +132,7 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
self, self,
BlockUsageLocator( BlockUsageLocator(
CourseLocator(version_guid=course_entry_override['structure']['_id']), CourseLocator(version_guid=course_entry_override['structure']['_id']),
block_type='error',
block_id=block_id block_id=block_id
), ),
error_msg=exc_info_to_str(sys.exc_info()) error_msg=exc_info_to_str(sys.exc_info())
......
...@@ -8,14 +8,14 @@ class DefinitionLazyLoader(object): ...@@ -8,14 +8,14 @@ class DefinitionLazyLoader(object):
object doesn't force access during init but waits until client wants the object doesn't force access during init but waits until client wants the
definition. Only works if the modulestore is a split mongo store. definition. Only works if the modulestore is a split mongo store.
""" """
def __init__(self, modulestore, definition_id): def __init__(self, modulestore, block_type, definition_id):
""" """
Simple placeholder for yet-to-be-fetched data Simple placeholder for yet-to-be-fetched data
:param modulestore: the pymongo db connection with the definitions :param modulestore: the pymongo db connection with the definitions
:param definition_locator: the id of the record in the above to fetch :param definition_locator: the id of the record in the above to fetch
""" """
self.modulestore = modulestore self.modulestore = modulestore
self.definition_locator = DefinitionLocator(definition_id) self.definition_locator = DefinitionLocator(block_type, definition_id)
def fetch(self): def fetch(self):
""" """
......
...@@ -152,7 +152,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase): ...@@ -152,7 +152,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
if lazy: if lazy:
for block in new_module_data.itervalues(): for block in new_module_data.itervalues():
block['definition'] = DefinitionLazyLoader(self, block['definition']) block['definition'] = DefinitionLazyLoader(self, block['category'], block['definition'])
else: else:
# Load all descendants by id # Load all descendants by id
descendent_definitions = self.db_connection.find_matching_definitions({ descendent_definitions = self.db_connection.find_matching_definitions({
...@@ -242,11 +242,6 @@ class SplitMongoModuleStore(ModuleStoreWriteBase): ...@@ -242,11 +242,6 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
:param course_locator: any subclass of CourseLocator :param course_locator: any subclass of CourseLocator
''' '''
# NOTE: if and when this uses cache, the update if changed logic will break if the cache
# holds the same objects as the descriptors!
if not course_locator.is_fully_specified():
raise InsufficientSpecificationError('Not fully specified: %s' % course_locator)
if course_locator.org and course_locator.offering and course_locator.branch: if course_locator.org and course_locator.offering and course_locator.branch:
# 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)
...@@ -258,6 +253,8 @@ class SplitMongoModuleStore(ModuleStoreWriteBase): ...@@ -258,6 +253,8 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
if course_locator.version_guid is not None and version_guid != course_locator.version_guid: if course_locator.version_guid is not None and version_guid != course_locator.version_guid:
# This may be a bit too touchy but it's hard to infer intent # This may be a bit too touchy but it's hard to infer intent
raise VersionConflictError(course_locator, version_guid) raise VersionConflictError(course_locator, version_guid)
elif course_locator.version_guid is None:
raise InsufficientSpecificationError(course_locator)
else: else:
# TODO should this raise an exception if branch was provided? # TODO should this raise an exception if branch was provided?
version_guid = course_locator.version_guid version_guid = course_locator.version_guid
...@@ -322,9 +319,6 @@ class SplitMongoModuleStore(ModuleStoreWriteBase): ...@@ -322,9 +319,6 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
def get_course(self, course_id, depth=None): def get_course(self, course_id, depth=None):
''' '''
Gets the course descriptor for the course identified by the locator Gets the course descriptor for the course identified by the locator
which may or may not be a blockLocator.
raises InsufficientSpecificationError
''' '''
assert(isinstance(course_id, CourseLocator)) assert(isinstance(course_id, CourseLocator))
course_entry = self._lookup_course(course_id) course_entry = self._lookup_course(course_id)
...@@ -458,6 +452,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase): ...@@ -458,6 +452,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
return [ return [
BlockUsageLocator.make_relative( BlockUsageLocator.make_relative(
locator, locator,
block_type=course['structure']['blocks'][parent_id].get('category'),
block_id=LocMapperStore.decode_key_from_mongo(parent_id), block_id=LocMapperStore.decode_key_from_mongo(parent_id),
) )
for parent_id in items for parent_id in items
...@@ -471,12 +466,13 @@ class SplitMongoModuleStore(ModuleStoreWriteBase): ...@@ -471,12 +466,13 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
course = self._lookup_course(course_key) course = self._lookup_course(course_key)
items = {LocMapperStore.decode_key_from_mongo(block_id) for block_id in course['structure']['blocks'].keys()} items = {LocMapperStore.decode_key_from_mongo(block_id) for block_id in course['structure']['blocks'].keys()}
items.remove(course['structure']['root']) items.remove(course['structure']['root'])
for block_id, block_data in course['structure']['blocks'].iteritems(): blocks = course['structure']['blocks']
for block_id, block_data in blocks.iteritems():
items.difference_update(block_data.get('fields', {}).get('children', [])) items.difference_update(block_data.get('fields', {}).get('children', []))
if block_data['category'] in detached_categories: if block_data['category'] in detached_categories:
items.discard(LocMapperStore.decode_key_from_mongo(block_id)) items.discard(LocMapperStore.decode_key_from_mongo(block_id))
return [ return [
BlockUsageLocator(course_key=course_key, block_id=block_id) BlockUsageLocator(course_key=course_key, block_type=blocks[block_id]['category'], block_id=block_id)
for block_id in items for block_id in items
] ]
...@@ -613,11 +609,11 @@ class SplitMongoModuleStore(ModuleStoreWriteBase): ...@@ -613,11 +609,11 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
# convert the results value sets to locators # convert the results value sets to locators
for k, versions in result.iteritems(): for k, versions in result.iteritems():
result[k] = [ result[k] = [
BlockUsageLocator(CourseLocator(version_guid=version), block_id=block_id) block_locator.for_version(version)
for version in versions for version in versions
] ]
return VersionTree( return VersionTree(
BlockUsageLocator(CourseLocator(version_guid=possible_roots[0]), block_id=block_id), block_locator.for_version(possible_roots[0]),
result result
) )
...@@ -650,7 +646,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase): ...@@ -650,7 +646,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
'schema_version': self.SCHEMA_VERSION, 'schema_version': self.SCHEMA_VERSION,
} }
self.db_connection.insert_definition(document) self.db_connection.insert_definition(document)
definition_locator = DefinitionLocator(new_id) definition_locator = DefinitionLocator(category, new_id)
return definition_locator return definition_locator
def update_definition_from_data(self, definition_locator, new_def_data, user_id): def update_definition_from_data(self, definition_locator, new_def_data, user_id):
...@@ -685,7 +681,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase): ...@@ -685,7 +681,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
old_definition['edit_info']['previous_version'] = definition_locator.definition_id old_definition['edit_info']['previous_version'] = definition_locator.definition_id
old_definition['schema_version'] = self.SCHEMA_VERSION old_definition['schema_version'] = self.SCHEMA_VERSION
self.db_connection.insert_definition(old_definition) self.db_connection.insert_definition(old_definition)
return DefinitionLocator(old_definition['_id']), True return DefinitionLocator(old_definition['category'], old_definition['_id']), True
else: else:
return definition_locator, False return definition_locator, False
...@@ -829,11 +825,13 @@ class SplitMongoModuleStore(ModuleStoreWriteBase): ...@@ -829,11 +825,13 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
self._update_head(index_entry, course_or_parent_locator.branch, new_id) self._update_head(index_entry, course_or_parent_locator.branch, new_id)
item_loc = BlockUsageLocator( item_loc = BlockUsageLocator(
course_or_parent_locator.version_agnostic(), course_or_parent_locator.version_agnostic(),
block_type=category,
block_id=new_block_id, block_id=new_block_id,
) )
else: else:
item_loc = BlockUsageLocator( item_loc = BlockUsageLocator(
CourseLocator(version_guid=new_id), CourseLocator(version_guid=new_id),
block_type=category,
block_id=new_block_id, block_id=new_block_id,
) )
...@@ -1029,7 +1027,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase): ...@@ -1029,7 +1027,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
course_key = CourseLocator(version_guid=new_id) course_key = CourseLocator(version_guid=new_id)
# fetch and return the new item--fetching is unnecessary but a good qc step # fetch and return the new item--fetching is unnecessary but a good qc step
new_locator = BlockUsageLocator(course_key, descriptor.location.block_id) new_locator = descriptor.location.map_into_course(course_key)
return self.get_item(new_locator) return self.get_item(new_locator)
else: else:
# nothing changed, just return the one sent in # nothing changed, just return the one sent in
...@@ -1101,18 +1099,14 @@ class SplitMongoModuleStore(ModuleStoreWriteBase): ...@@ -1101,18 +1099,14 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
self._update_head(index_entry, xblock.location.branch, new_id) self._update_head(index_entry, xblock.location.branch, new_id)
# fetch and return the new item--fetching is unnecessary but a good qc step # fetch and return the new item--fetching is unnecessary but a good qc step
return self.get_item( return self.get_item(xblock.location.for_version(new_id))
BlockUsageLocator(
xblock.location.course_key.for_version(new_id),
block_id=xblock.location.block_id,
)
)
else: else:
return xblock return xblock
def _persist_subdag(self, xblock, user_id, structure_blocks, new_id): def _persist_subdag(self, xblock, user_id, structure_blocks, new_id):
# persist the definition if persisted != passed # persist the definition if persisted != passed
new_def_data = self._filter_special_fields(xblock.get_explicitly_set_fields_by_scope(Scope.content)) new_def_data = self._filter_special_fields(xblock.get_explicitly_set_fields_by_scope(Scope.content))
is_updated = False
if xblock.definition_locator is None or isinstance(xblock.definition_locator.definition_id, LocalId): if xblock.definition_locator is None or isinstance(xblock.definition_locator.definition_id, LocalId):
xblock.definition_locator = self.create_definition_from_data( xblock.definition_locator = self.create_definition_from_data(
new_def_data, xblock.category, user_id) new_def_data, xblock.category, user_id)
...@@ -1134,9 +1128,6 @@ class SplitMongoModuleStore(ModuleStoreWriteBase): ...@@ -1134,9 +1128,6 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
else: else:
is_new = False is_new = False
encoded_block_id = LocMapperStore.encode_key_for_mongo(xblock.location.block_id) encoded_block_id = LocMapperStore.encode_key_for_mongo(xblock.location.block_id)
is_updated = is_updated or (
xblock.has_children and structure_blocks[encoded_block_id]['fields']['children'] != xblock.children
)
children = [] children = []
if xblock.has_children: if xblock.has_children:
...@@ -1147,6 +1138,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase): ...@@ -1147,6 +1138,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
children.append(child_block.location.block_id) children.append(child_block.location.block_id)
else: else:
children.append(child) children.append(child)
is_updated = is_updated or structure_blocks[encoded_block_id]['fields']['children'] != children
block_fields = xblock.get_explicitly_set_fields_by_scope(Scope.settings) block_fields = xblock.get_explicitly_set_fields_by_scope(Scope.settings)
if not is_new and not is_updated: if not is_new and not is_updated:
...@@ -1419,9 +1411,9 @@ class SplitMongoModuleStore(ModuleStoreWriteBase): ...@@ -1419,9 +1411,9 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
if isinstance(definition, DefinitionLazyLoader): if isinstance(definition, DefinitionLazyLoader):
return definition.definition_locator return definition.definition_locator
elif '_id' not in definition: elif '_id' not in definition:
return DefinitionLocator(LocalId()) return DefinitionLocator(definition.get('category'), LocalId())
else: else:
return DefinitionLocator(definition['_id']) return DefinitionLocator(definition['category'], definition['_id'])
def get_modulestore_type(self, course_id): def get_modulestore_type(self, course_id):
""" """
......
...@@ -32,14 +32,14 @@ class PersistentCourseFactory(SplitFactory): ...@@ -32,14 +32,14 @@ class PersistentCourseFactory(SplitFactory):
# pylint: disable=W0613 # pylint: disable=W0613
@classmethod @classmethod
def _create(cls, target_class, course_id='testX.999', org='testX', user_id='test_user', def _create(cls, target_class, offering='999', org='testX', user_id='test_user',
master_branch='draft', **kwargs): master_branch='draft', **kwargs):
modulestore = kwargs.pop('modulestore') modulestore = kwargs.pop('modulestore')
root_block_id = kwargs.pop('root_block_id', 'course') root_block_id = kwargs.pop('root_block_id', 'course')
# Write the data to the mongo datastore # Write the data to the mongo datastore
new_course = modulestore.create_course( new_course = modulestore.create_course(
course_id, org, user_id, fields=kwargs, org, offering, user_id, fields=kwargs,
master_branch=master_branch, root_block_id=root_block_id master_branch=master_branch, root_block_id=root_block_id
) )
......
...@@ -267,6 +267,7 @@ class TestLocationMapper(LocMapperSetupSansDjango): ...@@ -267,6 +267,7 @@ class TestLocationMapper(LocMapperSetupSansDjango):
) )
prob_locator = BlockUsageLocator( prob_locator = BlockUsageLocator(
prob_course_key, prob_course_key,
block_type='problem',
block_id='problem2', block_id='problem2',
) )
prob_location = loc_mapper().translate_locator_to_location(prob_locator) prob_location = loc_mapper().translate_locator_to_location(prob_locator)
...@@ -289,20 +290,21 @@ class TestLocationMapper(LocMapperSetupSansDjango): ...@@ -289,20 +290,21 @@ class TestLocationMapper(LocMapperSetupSansDjango):
prob_location = loc_mapper().translate_locator_to_location(prob_locator, get_course=True) prob_location = loc_mapper().translate_locator_to_location(prob_locator, get_course=True)
self.assertEqual(prob_location, SlashSeparatedCourseKey(org, course, run)) self.assertEqual(prob_location, SlashSeparatedCourseKey(org, course, run))
# explicit branch # explicit branch
prob_locator = BlockUsageLocator( prob_locator = prob_locator.for_branch('draft')
prob_course_key.for_branch('draft'), block_id=prob_locator.block_id
)
prob_location = loc_mapper().translate_locator_to_location(prob_locator) prob_location = loc_mapper().translate_locator_to_location(prob_locator)
# Even though the problem was set as draft, we always return revision=None to work # Even though the problem was set as draft, we always return revision=None to work
# with old mongo/draft modulestores. # with old mongo/draft modulestores.
self.assertEqual(prob_location, Location(org, course, run, 'problem', 'abc123', None)) self.assertEqual(prob_location, Location(org, course, run, 'problem', 'abc123', None))
prob_locator = BlockUsageLocator(prob_course_key.for_branch('production'), block_id='problem2') prob_locator = BlockUsageLocator(
prob_course_key.for_branch('production'),
block_type='problem', block_id='problem2'
)
prob_location = loc_mapper().translate_locator_to_location(prob_locator) prob_location = loc_mapper().translate_locator_to_location(prob_locator)
self.assertEqual(prob_location, Location(org, course, run, 'problem', 'abc123', None)) self.assertEqual(prob_location, Location(org, course, run, 'problem', 'abc123', None))
# same for chapter except chapter cannot be draft in old system # same for chapter except chapter cannot be draft in old system
chap_locator = BlockUsageLocator( chap_locator = BlockUsageLocator(
prob_course_key.for_branch('production'), prob_course_key.for_branch('production'),
block_id='chapter48f', block_type='chapter', block_id='chapter48f',
) )
chap_location = loc_mapper().translate_locator_to_location(chap_locator) chap_location = loc_mapper().translate_locator_to_location(chap_locator)
self.assertEqual(chap_location, Location(org, course, run, 'chapter', '48f23a10395384929234')) self.assertEqual(chap_location, Location(org, course, run, 'chapter', '48f23a10395384929234'))
...@@ -311,7 +313,7 @@ class TestLocationMapper(LocMapperSetupSansDjango): ...@@ -311,7 +313,7 @@ class TestLocationMapper(LocMapperSetupSansDjango):
chap_location = loc_mapper().translate_locator_to_location(chap_locator) chap_location = loc_mapper().translate_locator_to_location(chap_locator)
self.assertEqual(chap_location, Location(org, course, run, 'chapter', '48f23a10395384929234')) self.assertEqual(chap_location, Location(org, course, run, 'chapter', '48f23a10395384929234'))
chap_locator = BlockUsageLocator( chap_locator = BlockUsageLocator(
prob_course_key.for_branch('production'), block_id='chapter48f' prob_course_key.for_branch('production'), block_type='chapter', block_id='chapter48f'
) )
chap_location = loc_mapper().translate_locator_to_location(chap_locator) chap_location = loc_mapper().translate_locator_to_location(chap_locator)
self.assertEqual(chap_location, Location(org, course, run, 'chapter', '48f23a10395384929234')) self.assertEqual(chap_location, Location(org, course, run, 'chapter', '48f23a10395384929234'))
...@@ -319,7 +321,7 @@ class TestLocationMapper(LocMapperSetupSansDjango): ...@@ -319,7 +321,7 @@ class TestLocationMapper(LocMapperSetupSansDjango):
# look for non-existent problem # look for non-existent problem
prob_locator2 = BlockUsageLocator( prob_locator2 = BlockUsageLocator(
prob_course_key.for_branch('draft'), prob_course_key.for_branch('draft'),
block_id='problem3' block_type='problem', block_id='problem3'
) )
prob_location = loc_mapper().translate_locator_to_location(prob_locator2) prob_location = loc_mapper().translate_locator_to_location(prob_locator2)
self.assertIsNone(prob_location, 'Found non-existent problem') self.assertIsNone(prob_location, 'Found non-existent problem')
......
...@@ -7,8 +7,8 @@ import random ...@@ -7,8 +7,8 @@ import random
from bson.objectid import ObjectId from bson.objectid import ObjectId
from opaque_keys import InvalidKeyError from opaque_keys import InvalidKeyError
from xmodule.modulestore.locator import Locator, CourseLocator, BlockUsageLocator, DefinitionLocator from xmodule.modulestore.locator import Locator, CourseLocator, BlockUsageLocator, DefinitionLocator
from xmodule.modulestore.parsers import BRANCH_PREFIX, BLOCK_PREFIX, VERSION_PREFIX
from ddt import ddt, data from ddt import ddt, data
from xmodule.modulestore.keys import UsageKey, CourseKey, DefinitionKey
@ddt @ddt
...@@ -40,7 +40,7 @@ class LocatorTest(TestCase): ...@@ -40,7 +40,7 @@ class LocatorTest(TestCase):
testobj_1 = CourseLocator(version_guid=test_id_1) testobj_1 = CourseLocator(version_guid=test_id_1)
self.check_course_locn_fields(testobj_1, version_guid=test_id_1) self.check_course_locn_fields(testobj_1, version_guid=test_id_1)
self.assertEqual(str(testobj_1.version_guid), test_id_1_loc) self.assertEqual(str(testobj_1.version_guid), test_id_1_loc)
self.assertEqual(testobj_1._to_string(), VERSION_PREFIX + test_id_1_loc) self.assertEqual(testobj_1._to_string(), u'+'.join((testobj_1.VERSION_PREFIX, test_id_1_loc)))
# Test using a given string # Test using a given string
test_id_2_loc = '519665f6223ebd6980884f2b' test_id_2_loc = '519665f6223ebd6980884f2b'
...@@ -48,24 +48,24 @@ class LocatorTest(TestCase): ...@@ -48,24 +48,24 @@ class LocatorTest(TestCase):
testobj_2 = CourseLocator(version_guid=test_id_2) testobj_2 = CourseLocator(version_guid=test_id_2)
self.check_course_locn_fields(testobj_2, version_guid=test_id_2) self.check_course_locn_fields(testobj_2, version_guid=test_id_2)
self.assertEqual(str(testobj_2.version_guid), test_id_2_loc) self.assertEqual(str(testobj_2.version_guid), test_id_2_loc)
self.assertEqual(testobj_2._to_string(), VERSION_PREFIX + test_id_2_loc) self.assertEqual(testobj_2._to_string(), u'+'.join((testobj_2.VERSION_PREFIX, test_id_2_loc)))
@data( @data(
' mit.eecs', ' mit.eecs',
'mit.eecs ', 'mit.eecs ',
VERSION_PREFIX + 'mit.eecs', CourseLocator.VERSION_PREFIX + '+mit.eecs',
BLOCK_PREFIX + 'black/mit.eecs', BlockUsageLocator.BLOCK_PREFIX + '+black+mit.eecs',
'mit.ee cs', 'mit.ee cs',
'mit.ee,cs', 'mit.ee,cs',
'mit.ee/cs', 'mit.ee+cs',
'mit.ee&cs', 'mit.ee&cs',
'mit.ee()cs', 'mit.ee()cs',
BRANCH_PREFIX + 'this', CourseLocator.BRANCH_PREFIX + '+this',
'mit.eecs/' + BRANCH_PREFIX, 'mit.eecs+' + CourseLocator.BRANCH_PREFIX,
'mit.eecs/' + BRANCH_PREFIX + 'this/' + BRANCH_PREFIX + 'that', 'mit.eecs+' + CourseLocator.BRANCH_PREFIX + '+this+' + CourseLocator.BRANCH_PREFIX + '+that',
'mit.eecs/' + BRANCH_PREFIX + 'this/' + BRANCH_PREFIX, 'mit.eecs+' + CourseLocator.BRANCH_PREFIX + '+this+' + CourseLocator.BRANCH_PREFIX,
'mit.eecs/' + BRANCH_PREFIX + 'this ', 'mit.eecs+' + CourseLocator.BRANCH_PREFIX + '+this ',
'mit.eecs/' + BRANCH_PREFIX + 'th%is ', 'mit.eecs+' + CourseLocator.BRANCH_PREFIX + '+th%is ',
) )
def test_course_constructor_bad_package_id(self, bad_id): def test_course_constructor_bad_package_id(self, bad_id):
""" """
...@@ -78,18 +78,20 @@ class LocatorTest(TestCase): ...@@ -78,18 +78,20 @@ class LocatorTest(TestCase):
CourseLocator(org='test', offering=bad_id) CourseLocator(org='test', offering=bad_id)
with self.assertRaises(InvalidKeyError): with self.assertRaises(InvalidKeyError):
CourseLocator.from_string('course-locator:' + bad_id) CourseKey.from_string('course-locator:test+{}'.format(bad_id))
@data('course-locator:', 'course-locator:/mit.eecs', 'http:mit.eecs', 'course-locator//mit.eecs') @data('course-locator:', 'course-locator:/mit.eecs', 'http:mit.eecs', 'course-locator//mit.eecs')
def test_course_constructor_bad_url(self, bad_url): def test_course_constructor_bad_url(self, bad_url):
with self.assertRaises(InvalidKeyError): with self.assertRaises(InvalidKeyError):
CourseLocator.from_string(bad_url) CourseKey.from_string(bad_url)
def test_course_constructor_url(self): def test_course_constructor_url(self):
# Test parsing a url when it starts with a version ID and there is also a block ID. # Test parsing a url when it starts with a version ID and there is also a block ID.
# This hits the parsers parse_guid method. # This hits the parsers parse_guid method.
test_id_loc = '519665f6223ebd6980884f2b' test_id_loc = '519665f6223ebd6980884f2b'
testobj = CourseLocator.from_string("course-locator:{}{}/{}hw3".format(VERSION_PREFIX, test_id_loc, BLOCK_PREFIX)) testobj = CourseKey.from_string("course-locator:{}+{}+{}+hw3".format(
CourseLocator.VERSION_PREFIX, test_id_loc, CourseLocator.BLOCK_PREFIX
))
self.check_course_locn_fields( self.check_course_locn_fields(
testobj, testobj,
version_guid=ObjectId(test_id_loc) version_guid=ObjectId(test_id_loc)
...@@ -97,7 +99,9 @@ class LocatorTest(TestCase): ...@@ -97,7 +99,9 @@ class LocatorTest(TestCase):
def test_course_constructor_url_package_id_and_version_guid(self): def test_course_constructor_url_package_id_and_version_guid(self):
test_id_loc = '519665f6223ebd6980884f2b' test_id_loc = '519665f6223ebd6980884f2b'
testobj = CourseLocator.from_string('course-locator:mit.eecs+honors.6002x/' + VERSION_PREFIX + test_id_loc) testobj = CourseKey.from_string(
'course-locator:mit.eecs+honors.6002x+{}+{}'.format(CourseLocator.VERSION_PREFIX, test_id_loc)
)
self.check_course_locn_fields( self.check_course_locn_fields(
testobj, testobj,
org='mit.eecs', org='mit.eecs',
...@@ -109,8 +113,8 @@ class LocatorTest(TestCase): ...@@ -109,8 +113,8 @@ class LocatorTest(TestCase):
test_id_loc = '519665f6223ebd6980884f2b' test_id_loc = '519665f6223ebd6980884f2b'
org = 'mit.eecs' org = 'mit.eecs'
offering = '~6002x' offering = '~6002x'
testobj = CourseLocator.from_string('course-locator:{}+{}/{}draft-1/{}{}'.format( testobj = CourseKey.from_string('course-locator:{}+{}+{}+draft-1+{}+{}'.format(
org, offering, BRANCH_PREFIX, VERSION_PREFIX, test_id_loc org, offering, CourseLocator.BRANCH_PREFIX, CourseLocator.VERSION_PREFIX, test_id_loc
)) ))
self.check_course_locn_fields( self.check_course_locn_fields(
testobj, testobj,
...@@ -126,15 +130,13 @@ class LocatorTest(TestCase): ...@@ -126,15 +130,13 @@ class LocatorTest(TestCase):
testurn = '{}+{}'.format(org, offering) testurn = '{}+{}'.format(org, offering)
testobj = CourseLocator(org=org, offering=offering) testobj = CourseLocator(org=org, offering=offering)
self.check_course_locn_fields(testobj, org=org, offering=offering) self.check_course_locn_fields(testobj, org=org, offering=offering)
self.assertEqual(testobj.package_id, testurn)
self.assertEqual(testobj._to_string(), testurn) self.assertEqual(testobj._to_string(), testurn)
def test_course_constructor_package_id_separate_branch(self): def test_course_constructor_package_id_separate_branch(self):
org = 'mit.eecs' org = 'mit.eecs'
offering = '6002x' offering = '6002x'
testurn = '{}+{}'.format(org, offering)
test_branch = 'published' test_branch = 'published'
expected_urn = '{}+{}/{}{}'.format(org, offering, BRANCH_PREFIX, test_branch) expected_urn = '{}+{}+{}+{}'.format(org, offering, CourseLocator.BRANCH_PREFIX, test_branch)
testobj = CourseLocator(org=org, offering=offering, branch=test_branch) testobj = CourseLocator(org=org, offering=offering, branch=test_branch)
self.check_course_locn_fields( self.check_course_locn_fields(
testobj, testobj,
...@@ -142,7 +144,6 @@ class LocatorTest(TestCase): ...@@ -142,7 +144,6 @@ class LocatorTest(TestCase):
offering=offering, offering=offering,
branch=test_branch, branch=test_branch,
) )
self.assertEqual(testobj.package_id, testurn)
self.assertEqual(testobj.branch, test_branch) self.assertEqual(testobj.branch, test_branch)
self.assertEqual(testobj._to_string(), expected_urn) self.assertEqual(testobj._to_string(), expected_urn)
...@@ -151,17 +152,21 @@ class LocatorTest(TestCase): ...@@ -151,17 +152,21 @@ class LocatorTest(TestCase):
expected_offering = '6002x' expected_offering = '6002x'
expected_branch = 'published' expected_branch = 'published'
expected_block_ref = 'HW3' expected_block_ref = 'HW3'
testurn = 'edx:{}+{}/{}{}/{}{}'.format( testurn = 'edx:{}+{}+{}+{}+{}+{}+{}+{}'.format(
expected_org, expected_offering, BRANCH_PREFIX, expected_branch, BLOCK_PREFIX, 'HW3' expected_org, expected_offering, CourseLocator.BRANCH_PREFIX, expected_branch,
BlockUsageLocator.BLOCK_TYPE_PREFIX, 'problem', BlockUsageLocator.BLOCK_PREFIX, 'HW3'
)
testobj = UsageKey.from_string(testurn)
self.check_block_locn_fields(
testobj,
org=expected_org,
offering=expected_offering,
branch=expected_branch,
block_type='problem',
block=expected_block_ref
) )
testobj = BlockUsageLocator.from_string(testurn)
self.check_block_locn_fields(testobj,
org=expected_org,
offering=expected_offering,
branch=expected_branch,
block=expected_block_ref)
self.assertEqual(unicode(testobj), testurn) self.assertEqual(unicode(testobj), testurn)
testobj = BlockUsageLocator(testobj.course_key.for_version(ObjectId()), testobj.block_id) testobj = testobj.for_version(ObjectId())
agnostic = testobj.version_agnostic() agnostic = testobj.version_agnostic()
self.assertIsNone(agnostic.version_guid) self.assertIsNone(agnostic.version_guid)
self.check_block_locn_fields(agnostic, self.check_block_locn_fields(agnostic,
...@@ -172,13 +177,16 @@ class LocatorTest(TestCase): ...@@ -172,13 +177,16 @@ class LocatorTest(TestCase):
def test_block_constructor_url_version_prefix(self): def test_block_constructor_url_version_prefix(self):
test_id_loc = '519665f6223ebd6980884f2b' test_id_loc = '519665f6223ebd6980884f2b'
testobj = BlockUsageLocator.from_string( testobj = UsageKey.from_string(
'edx:mit.eecs+6002x/{}{}/{}lab2'.format(VERSION_PREFIX, test_id_loc, BLOCK_PREFIX) 'edx:mit.eecs+6002x+{}+{}+{}+problem+{}+lab2'.format(
CourseLocator.VERSION_PREFIX, test_id_loc, BlockUsageLocator.BLOCK_TYPE_PREFIX, BlockUsageLocator.BLOCK_PREFIX
)
) )
self.check_block_locn_fields( self.check_block_locn_fields(
testobj, testobj,
org='mit.eecs', org='mit.eecs',
offering='6002x', offering='6002x',
block_type='problem',
block='lab2', block='lab2',
version_guid=ObjectId(test_id_loc) version_guid=ObjectId(test_id_loc)
) )
...@@ -195,9 +203,10 @@ class LocatorTest(TestCase): ...@@ -195,9 +203,10 @@ class LocatorTest(TestCase):
def test_block_constructor_url_kitchen_sink(self): def test_block_constructor_url_kitchen_sink(self):
test_id_loc = '519665f6223ebd6980884f2b' test_id_loc = '519665f6223ebd6980884f2b'
testobj = BlockUsageLocator.from_string( testobj = UsageKey.from_string(
'edx:mit.eecs+6002x/{}draft/{}{}/{}lab2'.format( 'edx:mit.eecs+6002x+{}+draft+{}+{}+{}+problem+{}+lab2'.format(
BRANCH_PREFIX, VERSION_PREFIX, test_id_loc, BLOCK_PREFIX CourseLocator.BRANCH_PREFIX, CourseLocator.VERSION_PREFIX, test_id_loc,
BlockUsageLocator.BLOCK_TYPE_PREFIX, BlockUsageLocator.BLOCK_PREFIX
) )
) )
self.check_block_locn_fields( self.check_block_locn_fields(
...@@ -219,6 +228,7 @@ class LocatorTest(TestCase): ...@@ -219,6 +228,7 @@ class LocatorTest(TestCase):
block_id = 'problem:with-colon~2' block_id = 'problem:with-colon~2'
testobj = BlockUsageLocator( testobj = BlockUsageLocator(
CourseLocator(org=org, offering=offering, branch=branch), CourseLocator(org=org, offering=offering, branch=branch),
block_type='problem',
block_id=block_id block_id=block_id
) )
self.check_block_locn_fields( self.check_block_locn_fields(
...@@ -234,54 +244,32 @@ class LocatorTest(TestCase): ...@@ -234,54 +244,32 @@ class LocatorTest(TestCase):
branch = 'foo' branch = 'foo'
baseobj = CourseLocator(org=org, offering=offering, branch=branch) baseobj = CourseLocator(org=org, offering=offering, branch=branch)
block_id = 'problem:with-colon~2' block_id = 'problem:with-colon~2'
testobj = BlockUsageLocator.make_relative(baseobj, block_id) testobj = BlockUsageLocator.make_relative(baseobj, 'problem', block_id)
self.check_block_locn_fields( self.check_block_locn_fields(
testobj, org=org, offering=offering, branch=branch, block=block_id testobj, org=org, offering=offering, branch=branch, block=block_id
) )
block_id = 'completely_different' block_id = 'completely_different'
testobj = BlockUsageLocator.make_relative(testobj, block_id) testobj = BlockUsageLocator.make_relative(testobj, 'problem', block_id)
self.check_block_locn_fields( self.check_block_locn_fields(
testobj, org=org, offering=offering, branch=branch, block=block_id testobj, org=org, offering=offering, branch=branch, block=block_id
) )
def test_repr(self): def test_repr(self):
testurn = 'edx:mit.eecs+6002x/' + BRANCH_PREFIX + 'published/' + BLOCK_PREFIX + 'HW3' testurn = u'edx:mit.eecs+6002x+{}+published+{}+problem+{}+HW3'.format(
testobj = BlockUsageLocator.from_string(testurn) CourseLocator.BRANCH_PREFIX, BlockUsageLocator.BLOCK_TYPE_PREFIX, BlockUsageLocator.BLOCK_PREFIX
self.assertEqual("BlockUsageLocator(CourseLocator(u'mit.eecs', u'6002x', u'published', None), u'HW3')", repr(testobj))
def test_url_reverse(self):
"""
Test the url_reverse method
"""
locator = BlockUsageLocator(
CourseLocator(org="a", offering="fancy_course-id", branch="branch_1.2-3"),
block_id='element'
)
self.assertEqual(
'/expression/{}/format'.format(unicode(locator)),
locator.url_reverse('expression', 'format')
)
self.assertEqual(
'/expression/{}/format'.format(unicode(locator)),
locator.url_reverse('/expression', '/format')
)
self.assertEqual(
'/expression/{}'.format(unicode(locator)),
locator.url_reverse('expression/', None)
)
self.assertEqual(
'/expression/{}'.format(unicode(locator)),
locator.url_reverse('/expression/', '')
) )
testobj = UsageKey.from_string(testurn)
self.assertEqual("BlockUsageLocator(CourseLocator(u'mit.eecs', u'6002x', u'published', None), u'problem', u'HW3')", repr(testobj))
def test_description_locator_url(self): def test_description_locator_url(self):
object_id = '{:024x}'.format(random.randrange(16 ** 24)) object_id = '{:024x}'.format(random.randrange(16 ** 24))
definition_locator = DefinitionLocator(object_id) definition_locator = DefinitionLocator('html', object_id)
self.assertEqual('defx:' + VERSION_PREFIX + object_id, unicode(definition_locator)) self.assertEqual('defx:{}+{}+html'.format(object_id, DefinitionLocator.BLOCK_TYPE_PREFIX), unicode(definition_locator))
self.assertEqual(definition_locator, DefinitionKey.from_string(unicode(definition_locator)))
def test_description_locator_version(self): def test_description_locator_version(self):
object_id = '{:024x}'.format(random.randrange(16 ** 24)) object_id = '{:024x}'.format(random.randrange(16 ** 24))
definition_locator = DefinitionLocator(object_id) definition_locator = DefinitionLocator('html', object_id)
self.assertEqual(object_id, str(definition_locator.version())) self.assertEqual(object_id, str(definition_locator.version()))
# ------------------------------------------------------------------ # ------------------------------------------------------------------
...@@ -298,10 +286,12 @@ class LocatorTest(TestCase): ...@@ -298,10 +286,12 @@ class LocatorTest(TestCase):
self.assertEqual(testobj.branch, branch) self.assertEqual(testobj.branch, branch)
def check_block_locn_fields(self, testobj, version_guid=None, def check_block_locn_fields(self, testobj, version_guid=None,
org=None, offering=None, branch=None, block=None): org=None, offering=None, branch=None, block_type=None, block=None):
""" """
Does adds a block id check over and above the check_course_locn_fields tests Does adds a block id check over and above the check_course_locn_fields tests
""" """
self.check_course_locn_fields(testobj, version_guid, org, offering, self.check_course_locn_fields(testobj, version_guid, org, offering,
branch) branch)
if block_type is not None:
self.assertEqual(testobj.block_type, block_type)
self.assertEqual(testobj.block_id, block) self.assertEqual(testobj.block_id, block)
...@@ -442,7 +442,7 @@ class SplitModuleTest(unittest.TestCase): ...@@ -442,7 +442,7 @@ class SplitModuleTest(unittest.TestCase):
Sets up the initial data into the db Sets up the initial data into the db
''' '''
split_store = modulestore() split_store = modulestore()
for course_id, course_spec in SplitModuleTest.COURSE_CONTENT.iteritems(): for _course_id, course_spec in SplitModuleTest.COURSE_CONTENT.iteritems():
course = split_store.create_course( course = split_store.create_course(
course_spec['org'], course_spec['offering'], course_spec['user_id'], course_spec['org'], course_spec['offering'], course_spec['user_id'],
fields=course_spec['fields'], fields=course_spec['fields'],
...@@ -454,7 +454,8 @@ class SplitModuleTest(unittest.TestCase): ...@@ -454,7 +454,8 @@ class SplitModuleTest(unittest.TestCase):
if course.location.block_id == block_id: if course.location.block_id == block_id:
block = course block = course
else: else:
block_usage = BlockUsageLocator.make_relative(course.location, block_id) # not easy to figure out the category but get_item won't care
block_usage = BlockUsageLocator.make_relative(course.location, '', block_id)
block = split_store.get_item(block_usage) block = split_store.get_item(block_usage)
for key, value in fields.iteritems(): for key, value in fields.iteritems():
setattr(block, key, value) setattr(block, key, value)
...@@ -466,7 +467,7 @@ class SplitModuleTest(unittest.TestCase): ...@@ -466,7 +467,7 @@ class SplitModuleTest(unittest.TestCase):
elif spec['parent'] == course.location.block_id: elif spec['parent'] == course.location.block_id:
parent = course parent = course
else: else:
block_usage = BlockUsageLocator.make_relative(course.location, spec['parent']) block_usage = BlockUsageLocator.make_relative(course.location, '', spec['parent'])
parent = split_store.get_item(block_usage) parent = split_store.get_item(block_usage)
block_id = LocalId(spec['id']) block_id = LocalId(spec['id'])
child = split_store.create_xblock( child = split_store.create_xblock(
...@@ -477,6 +478,7 @@ class SplitModuleTest(unittest.TestCase): ...@@ -477,6 +478,7 @@ class SplitModuleTest(unittest.TestCase):
# publish "testx.wonderful" # publish "testx.wonderful"
to_publish = BlockUsageLocator( to_publish = BlockUsageLocator(
CourseLocator(org="testx", offering="wonderful", branch="draft"), CourseLocator(org="testx", offering="wonderful", branch="draft"),
block_type='course',
block_id="head23456" block_id="head23456"
) )
destination = CourseLocator(org="testx", offering="wonderful", branch="published") destination = CourseLocator(org="testx", offering="wonderful", branch="published")
...@@ -676,12 +678,12 @@ class SplitModuleItemTests(SplitModuleTest): ...@@ -676,12 +678,12 @@ class SplitModuleItemTests(SplitModuleTest):
course = modulestore().get_course(course_locator) course = modulestore().get_course(course_locator)
previous_version = course.previous_version previous_version = course.previous_version
# positive tests of various forms # positive tests of various forms
locator = BlockUsageLocator(CourseLocator(version_guid=previous_version), block_id='head12345') locator = course.location.map_into_course(CourseLocator(version_guid=previous_version))
self.assertTrue( self.assertTrue(
modulestore().has_item(locator), "couldn't find in %s" % previous_version modulestore().has_item(locator), "couldn't find in %s" % previous_version
) )
locator = BlockUsageLocator(course_locator, block_id='head12345') locator = course.location.version_agnostic()
self.assertTrue( self.assertTrue(
modulestore().has_item(locator), modulestore().has_item(locator),
) )
...@@ -689,6 +691,7 @@ class SplitModuleItemTests(SplitModuleTest): ...@@ -689,6 +691,7 @@ class SplitModuleItemTests(SplitModuleTest):
modulestore().has_item( modulestore().has_item(
BlockUsageLocator( BlockUsageLocator(
locator.course_key.for_branch('published'), locator.course_key.for_branch('published'),
block_type=locator.block_type,
block_id=locator.block_id block_id=locator.block_id
) )
), ),
...@@ -696,7 +699,7 @@ class SplitModuleItemTests(SplitModuleTest): ...@@ -696,7 +699,7 @@ class SplitModuleItemTests(SplitModuleTest):
) )
# not a course obj # not a course obj
locator = BlockUsageLocator(course_locator, block_id='chapter1') locator = BlockUsageLocator(course_locator, block_type='chapter', block_id='chapter1')
self.assertTrue( self.assertTrue(
modulestore().has_item(locator), modulestore().has_item(locator),
"couldn't find chapter1" "couldn't find chapter1"
...@@ -705,26 +708,25 @@ class SplitModuleItemTests(SplitModuleTest): ...@@ -705,26 +708,25 @@ class SplitModuleItemTests(SplitModuleTest):
# in published course # in published course
locator = BlockUsageLocator( locator = BlockUsageLocator(
CourseLocator(org="testx", offering="wonderful", branch='draft'), CourseLocator(org="testx", offering="wonderful", branch='draft'),
block_type="course",
block_id="head23456" block_id="head23456"
) )
self.assertTrue( self.assertTrue(
modulestore().has_item( modulestore().has_item(locator.for_branch("published"))
BlockUsageLocator(locator.course_key.for_branch("published"), block_id=locator.block_id)
)
) )
locator = locator.for_branch('published')
self.assertTrue(modulestore().has_item(locator), "couldn't find in published")
def test_negative_has_item(self): def test_negative_has_item(self):
# negative tests--not found # negative tests--not found
# no such course or block # no such course or block
locator = BlockUsageLocator( locator = BlockUsageLocator(
CourseLocator(org="foo", offering="doesnotexist", branch='draft'), CourseLocator(org="foo", offering="doesnotexist", branch='draft'),
block_type="course",
block_id="head23456" block_id="head23456"
) )
self.assertFalse(modulestore().has_item(locator)) self.assertFalse(modulestore().has_item(locator))
locator = BlockUsageLocator( locator = BlockUsageLocator(
CourseLocator(org="testx", offering="wonderful", branch='draft'), CourseLocator(org="testx", offering="wonderful", branch='draft'),
block_type="vertical",
block_id="doesnotexist" block_id="doesnotexist"
) )
self.assertFalse(modulestore().has_item(locator)) self.assertFalse(modulestore().has_item(locator))
...@@ -738,7 +740,7 @@ class SplitModuleItemTests(SplitModuleTest): ...@@ -738,7 +740,7 @@ class SplitModuleItemTests(SplitModuleTest):
previous_version = course.previous_version previous_version = course.previous_version
# positive tests of various forms # positive tests of various forms
locator = BlockUsageLocator(CourseLocator(version_guid=previous_version), block_id='head12345') locator = course.location.map_into_course(CourseLocator(version_guid=previous_version))
block = modulestore().get_item(locator) block = modulestore().get_item(locator)
self.assertIsInstance(block, CourseDescriptor) self.assertIsInstance(block, CourseDescriptor)
self.assertIsInstance(modulestore().get_item(locator), CourseDescriptor) self.assertIsInstance(modulestore().get_item(locator), CourseDescriptor)
...@@ -759,36 +761,27 @@ class SplitModuleItemTests(SplitModuleTest): ...@@ -759,36 +761,27 @@ class SplitModuleItemTests(SplitModuleTest):
block.grade_cutoffs, {"Pass": 0.45}, block.grade_cutoffs, {"Pass": 0.45},
) )
locator = BlockUsageLocator(hero_locator, block_id='head12345') verify_greek_hero(modulestore().get_item(course.location))
verify_greek_hero(modulestore().get_item(locator))
# try to look up other branches # try to look up other branches
with self.assertRaises(ItemNotFoundError): with self.assertRaises(ItemNotFoundError):
modulestore().get_item( modulestore().get_item(course.location.for_branch("published"))
BlockUsageLocator(
hero_locator.for_branch("published"),
block_id=locator.block_id,
)
)
self.assertIsInstance(
modulestore().get_item(locator),
CourseDescriptor
)
def test_get_non_root(self): def test_get_non_root(self):
# not a course obj # not a course obj
locator = BlockUsageLocator( locator = BlockUsageLocator(
CourseLocator(org='testx', offering='GreekHero', branch='draft'), 'chapter1' CourseLocator(org='testx', offering='GreekHero', branch='draft'), 'chapter', 'chapter1'
) )
block = modulestore().get_item(locator) block = modulestore().get_item(locator)
self.assertEqual(block.location.package_id, "testx+GreekHero") self.assertEqual(block.location.org, "testx")
self.assertEqual(block.location.offering, "GreekHero")
self.assertEqual(block.category, 'chapter') self.assertEqual(block.category, 'chapter')
self.assertEqual(block.display_name, "Hercules") self.assertEqual(block.display_name, "Hercules")
self.assertEqual(block.edited_by, "testassist@edx.org") self.assertEqual(block.edited_by, "testassist@edx.org")
# in published course # in published course
locator = BlockUsageLocator( locator = BlockUsageLocator(
CourseLocator(org='testx', offering='wonderful', branch='published'), 'head23456' CourseLocator(org='testx', offering='wonderful', branch='published'), 'course', 'head23456'
) )
self.assertIsInstance( self.assertIsInstance(
modulestore().get_item(locator), modulestore().get_item(locator),
...@@ -798,12 +791,12 @@ class SplitModuleItemTests(SplitModuleTest): ...@@ -798,12 +791,12 @@ class SplitModuleItemTests(SplitModuleTest):
# negative tests--not found # negative tests--not found
# no such course or block # no such course or block
locator = BlockUsageLocator( locator = BlockUsageLocator(
CourseLocator(org='doesnotexist', offering='doesnotexist', branch='draft'), 'head23456' CourseLocator(org='doesnotexist', offering='doesnotexist', branch='draft'), 'course', 'head23456'
) )
with self.assertRaises(ItemNotFoundError): with self.assertRaises(ItemNotFoundError):
modulestore().get_item(locator) modulestore().get_item(locator)
locator = BlockUsageLocator( locator = BlockUsageLocator(
CourseLocator(org='testx', offering='wonderful', branch='draft'), 'doesnotexist' CourseLocator(org='testx', offering='wonderful', branch='draft'), 'html', 'doesnotexist'
) )
with self.assertRaises(ItemNotFoundError): with self.assertRaises(ItemNotFoundError):
modulestore().get_item(locator) modulestore().get_item(locator)
...@@ -864,7 +857,7 @@ class SplitModuleItemTests(SplitModuleTest): ...@@ -864,7 +857,7 @@ class SplitModuleItemTests(SplitModuleTest):
''' '''
locator = BlockUsageLocator( locator = BlockUsageLocator(
CourseLocator(org='testx', offering='GreekHero', branch='draft'), CourseLocator(org='testx', offering='GreekHero', branch='draft'),
block_id='chapter1' 'chapter', block_id='chapter1'
) )
parents = modulestore().get_parent_locations(locator) parents = modulestore().get_parent_locations(locator)
self.assertEqual(len(parents), 1) self.assertEqual(len(parents), 1)
...@@ -884,7 +877,7 @@ class SplitModuleItemTests(SplitModuleTest): ...@@ -884,7 +877,7 @@ class SplitModuleItemTests(SplitModuleTest):
Test the existing get_children method on xdescriptors Test the existing get_children method on xdescriptors
""" """
locator = BlockUsageLocator( locator = BlockUsageLocator(
CourseLocator(org='testx', offering='GreekHero', branch='draft'), 'head12345' CourseLocator(org='testx', offering='GreekHero', branch='draft'), 'course', 'head12345'
) )
block = modulestore().get_item(locator) block = modulestore().get_item(locator)
children = block.get_children() children = block.get_children()
...@@ -952,11 +945,11 @@ class TestItemCrud(SplitModuleTest): ...@@ -952,11 +945,11 @@ class TestItemCrud(SplitModuleTest):
self.assertIsNotNone(new_module.definition_locator) self.assertIsNotNone(new_module.definition_locator)
self.assertEqual(new_module.display_name, 'new sequential') self.assertEqual(new_module.display_name, 'new sequential')
# check that block does not exist in previous version # check that block does not exist in previous version
locator = BlockUsageLocator( locator = new_module.location.map_into_course(
CourseLocator(version_guid=premod_course.location.version_guid), CourseLocator(version_guid=premod_course.location.version_guid)
block_id=new_module.location.block_id
) )
self.assertRaises(ItemNotFoundError, modulestore().get_item, locator) with self.assertRaises(ItemNotFoundError):
modulestore().get_item(locator)
def test_create_parented_item(self): def test_create_parented_item(self):
""" """
...@@ -964,12 +957,12 @@ class TestItemCrud(SplitModuleTest): ...@@ -964,12 +957,12 @@ class TestItemCrud(SplitModuleTest):
""" """
locator = BlockUsageLocator( locator = BlockUsageLocator(
CourseLocator(org='testx', offering='GreekHero', branch='draft'), CourseLocator(org='testx', offering='GreekHero', branch='draft'),
block_id='chapter2' 'chapter', block_id='chapter2'
) )
original = modulestore().get_item(locator) original = modulestore().get_item(locator)
locator = BlockUsageLocator( locator = BlockUsageLocator(
CourseLocator(org='testx', offering='wonderful', branch='draft'), 'head23456' CourseLocator(org='testx', offering='wonderful', branch='draft'), 'course', 'head23456'
) )
premod_course = modulestore().get_course(locator.course_key) premod_course = modulestore().get_course(locator.course_key)
category = 'chapter' category = 'chapter'
...@@ -992,12 +985,12 @@ class TestItemCrud(SplitModuleTest): ...@@ -992,12 +985,12 @@ class TestItemCrud(SplitModuleTest):
""" """
locator = BlockUsageLocator( locator = BlockUsageLocator(
CourseLocator(org='testx', offering='GreekHero', branch='draft'), CourseLocator(org='testx', offering='GreekHero', branch='draft'),
block_id='problem1' 'problem', block_id='problem1'
) )
original = modulestore().get_item(locator) original = modulestore().get_item(locator)
locator = BlockUsageLocator( locator = BlockUsageLocator(
CourseLocator(org='guestx', offering='contender', branch='draft'), 'head345679' CourseLocator(org='guestx', offering='contender', branch='draft'), 'course', 'head345679'
) )
category = 'problem' category = 'problem'
new_payload = "<problem>empty</problem>" new_payload = "<problem>empty</problem>"
...@@ -1031,8 +1024,8 @@ class TestItemCrud(SplitModuleTest): ...@@ -1031,8 +1024,8 @@ class TestItemCrud(SplitModuleTest):
Check that using odd characters in block id don't break ability to add and retrieve block. Check that using odd characters in block id don't break ability to add and retrieve block.
""" """
course_key = CourseLocator(org='guestx', offering='contender', branch='draft') course_key = CourseLocator(org='guestx', offering='contender', branch='draft')
parent_locator = BlockUsageLocator(course_key, block_id="head345679") parent_locator = BlockUsageLocator(course_key, 'course', block_id="head345679")
chapter_locator = BlockUsageLocator(course_key, block_id="foo.bar_-~:0") chapter_locator = BlockUsageLocator(course_key, 'chapter', block_id="foo.bar_-~:0")
modulestore().create_item( modulestore().create_item(
parent_locator, 'chapter', 'anotheruser', parent_locator, 'chapter', 'anotheruser',
block_id=chapter_locator.block_id, block_id=chapter_locator.block_id,
...@@ -1043,7 +1036,7 @@ class TestItemCrud(SplitModuleTest): ...@@ -1043,7 +1036,7 @@ class TestItemCrud(SplitModuleTest):
self.assertEqual(new_module.location.block_id, "foo.bar_-~:0") # hardcode to ensure BUL init didn't change self.assertEqual(new_module.location.block_id, "foo.bar_-~:0") # hardcode to ensure BUL init didn't change
# now try making that a parent of something # now try making that a parent of something
new_payload = "<problem>empty</problem>" new_payload = "<problem>empty</problem>"
problem_locator = BlockUsageLocator(course_key, block_id="prob.bar_-~:99a") problem_locator = BlockUsageLocator(course_key, 'problem', block_id="prob.bar_-~:99a")
modulestore().create_item( modulestore().create_item(
chapter_locator, 'problem', 'anotheruser', chapter_locator, 'problem', 'anotheruser',
block_id=problem_locator.block_id, block_id=problem_locator.block_id,
...@@ -1119,10 +1112,7 @@ class TestItemCrud(SplitModuleTest): ...@@ -1119,10 +1112,7 @@ class TestItemCrud(SplitModuleTest):
) )
# add new child to old parent in continued (leave off version_guid) # add new child to old parent in continued (leave off version_guid)
course_module_locator = BlockUsageLocator( course_module_locator = new_course.location.version_agnostic()
new_course.location.course_key.version_agnostic(),
block_id=new_course.location.block_id,
)
new_ele = modulestore().create_item( new_ele = modulestore().create_item(
course_module_locator, 'chapter', user, course_module_locator, 'chapter', user,
fields={'display_name': 'chapter 4'}, fields={'display_name': 'chapter 4'},
...@@ -1143,7 +1133,7 @@ class TestItemCrud(SplitModuleTest): ...@@ -1143,7 +1133,7 @@ class TestItemCrud(SplitModuleTest):
""" """
locator = BlockUsageLocator( locator = BlockUsageLocator(
CourseLocator(org="testx", offering="GreekHero", branch='draft'), CourseLocator(org="testx", offering="GreekHero", branch='draft'),
block_id="problem3_2" 'problem', block_id="problem3_2"
) )
problem = modulestore().get_item(locator) problem = modulestore().get_item(locator)
pre_def_id = problem.definition_locator.definition_id pre_def_id = problem.definition_locator.definition_id
...@@ -1160,10 +1150,7 @@ class TestItemCrud(SplitModuleTest): ...@@ -1160,10 +1150,7 @@ class TestItemCrud(SplitModuleTest):
self.assertNotEqual(updated_problem.location.version_guid, pre_version_guid) self.assertNotEqual(updated_problem.location.version_guid, pre_version_guid)
self.assertEqual(updated_problem.max_attempts, 4) self.assertEqual(updated_problem.max_attempts, 4)
# refetch to ensure original didn't change # refetch to ensure original didn't change
original_location = BlockUsageLocator( original_location = problem.location.map_into_course(CourseLocator(version_guid=pre_version_guid))
CourseLocator(version_guid=pre_version_guid),
block_id=problem.location.block_id
)
problem = modulestore().get_item(original_location) problem = modulestore().get_item(original_location)
self.assertNotEqual(problem.max_attempts, 4, "original changed") self.assertNotEqual(problem.max_attempts, 4, "original changed")
...@@ -1179,7 +1166,7 @@ class TestItemCrud(SplitModuleTest): ...@@ -1179,7 +1166,7 @@ class TestItemCrud(SplitModuleTest):
test updating an item's children ensuring the definition doesn't version but the course does if it should test updating an item's children ensuring the definition doesn't version but the course does if it should
""" """
locator = BlockUsageLocator( locator = BlockUsageLocator(
CourseLocator(org='testx', offering='GreekHero', branch='draft'), 'chapter3' CourseLocator(org='testx', offering='GreekHero', branch='draft'), 'chapter', 'chapter3'
) )
block = modulestore().get_item(locator) block = modulestore().get_item(locator)
pre_def_id = block.definition_locator.definition_id pre_def_id = block.definition_locator.definition_id
...@@ -1206,7 +1193,7 @@ class TestItemCrud(SplitModuleTest): ...@@ -1206,7 +1193,7 @@ class TestItemCrud(SplitModuleTest):
test updating an item's definition: ensure it gets versioned as well as the course getting versioned test updating an item's definition: ensure it gets versioned as well as the course getting versioned
""" """
locator = BlockUsageLocator( locator = BlockUsageLocator(
CourseLocator(org='testx', offering='GreekHero', branch='draft'), 'head12345' CourseLocator(org='testx', offering='GreekHero', branch='draft'), 'course', 'head12345'
) )
block = modulestore().get_item(locator) block = modulestore().get_item(locator)
pre_def_id = block.definition_locator.definition_id pre_def_id = block.definition_locator.definition_id
...@@ -1226,13 +1213,13 @@ class TestItemCrud(SplitModuleTest): ...@@ -1226,13 +1213,13 @@ class TestItemCrud(SplitModuleTest):
""" """
locator = BlockUsageLocator( locator = BlockUsageLocator(
CourseLocator('testx', 'GreekHero', branch='draft'), CourseLocator('testx', 'GreekHero', branch='draft'),
block_id='problem1' 'problem', block_id='problem1'
) )
original = modulestore().get_item(locator) original = modulestore().get_item(locator)
# first add 2 children to the course for the update to manipulate # first add 2 children to the course for the update to manipulate
locator = BlockUsageLocator( locator = BlockUsageLocator(
CourseLocator('guestx', 'contender', branch='draft'), CourseLocator('guestx', 'contender', branch='draft'),
block_id="head345679" 'course', block_id="head345679"
) )
category = 'problem' category = 'problem'
new_payload = "<problem>empty</problem>" new_payload = "<problem>empty</problem>"
...@@ -1282,11 +1269,7 @@ class TestItemCrud(SplitModuleTest): ...@@ -1282,11 +1269,7 @@ class TestItemCrud(SplitModuleTest):
with self.assertRaises(VersionConflictError): with self.assertRaises(VersionConflictError):
modulestore().has_item(locn_to_del) modulestore().has_item(locn_to_del)
locator = BlockUsageLocator( self.assertTrue(modulestore().has_item(locn_to_del.course_agnostic()))
CourseLocator(version_guid=locn_to_del.version_guid),
block_id=locn_to_del.block_id
)
self.assertTrue(modulestore().has_item(locator))
self.assertNotEqual(new_course_loc.version_guid, course.location.version_guid) self.assertNotEqual(new_course_loc.version_guid, course.location.version_guid)
# delete a subtree # delete a subtree
...@@ -1301,22 +1284,9 @@ class TestItemCrud(SplitModuleTest): ...@@ -1301,22 +1284,9 @@ class TestItemCrud(SplitModuleTest):
if node: if node:
node_loc = node.location node_loc = node.location
self.assertFalse( self.assertFalse(
modulestore().has_item( modulestore().has_item(node_loc.version_agnostic())
BlockUsageLocator(
CourseLocator(
org=node_loc.org,
offering=node_loc.offering,
branch=node_loc.branch,
),
block_id=node_loc.block_id
)
)
) )
locator = BlockUsageLocator( self.assertTrue(modulestore().has_item(node_loc.course_agnostic()))
CourseLocator(version_guid=node.location.version_guid),
block_id=node.location.block_id
)
self.assertTrue(modulestore().has_item(locator))
if node.has_children: if node.has_children:
for sub in node.get_children(): for sub in node.get_children():
check_subtree(sub) check_subtree(sub)
...@@ -1327,10 +1297,7 @@ class TestItemCrud(SplitModuleTest): ...@@ -1327,10 +1297,7 @@ class TestItemCrud(SplitModuleTest):
Create a course we can delete Create a course we can delete
""" """
course = modulestore().create_course('nihilx', 'deletion', 'deleting_user') course = modulestore().create_course('nihilx', 'deletion', 'deleting_user')
root = BlockUsageLocator( root = course.location.version_agnostic().for_branch('draft')
course.id.version_agnostic().for_branch('draft'),
block_id=course.location.block_id,
)
for _ in range(4): for _ in range(4):
self.create_subtree_for_deletion(root, ['chapter', 'vertical', 'problem']) self.create_subtree_for_deletion(root, ['chapter', 'vertical', 'problem'])
return modulestore().get_item(root) return modulestore().get_item(root)
...@@ -1342,7 +1309,7 @@ class TestItemCrud(SplitModuleTest): ...@@ -1342,7 +1309,7 @@ class TestItemCrud(SplitModuleTest):
if not category_queue: if not category_queue:
return return
node = modulestore().create_item(parent.version_agnostic(), category_queue[0], 'deleting_user') node = modulestore().create_item(parent.version_agnostic(), category_queue[0], 'deleting_user')
node_loc = BlockUsageLocator(parent.course_key, block_id=node.location.block_id) node_loc = node.location.map_into_course(parent.course_key)
for _ in range(4): for _ in range(4):
self.create_subtree_for_deletion(node_loc, category_queue[1:]) self.create_subtree_for_deletion(node_loc, category_queue[1:])
...@@ -1523,13 +1490,13 @@ class TestInheritance(SplitModuleTest): ...@@ -1523,13 +1490,13 @@ class TestInheritance(SplitModuleTest):
# Note, not testing value where defined (course) b/c there's no # Note, not testing value where defined (course) b/c there's no
# defined accessor for it on CourseDescriptor. # defined accessor for it on CourseDescriptor.
locator = BlockUsageLocator( locator = BlockUsageLocator(
CourseLocator(org='testx', offering='GreekHero', branch='draft'), 'problem3_2' CourseLocator(org='testx', offering='GreekHero', branch='draft'), 'problem', 'problem3_2'
) )
node = modulestore().get_item(locator) node = modulestore().get_item(locator)
# inherited # inherited
self.assertEqual(node.graceperiod, datetime.timedelta(hours=2)) self.assertEqual(node.graceperiod, datetime.timedelta(hours=2))
locator = BlockUsageLocator( locator = BlockUsageLocator(
CourseLocator(org='testx', offering='GreekHero', branch='draft'), 'problem1' CourseLocator(org='testx', offering='GreekHero', branch='draft'), 'problem', 'problem1'
) )
node = modulestore().get_item(locator) node = modulestore().get_item(locator)
# overridden # overridden
...@@ -1560,19 +1527,19 @@ class TestPublish(SplitModuleTest): ...@@ -1560,19 +1527,19 @@ class TestPublish(SplitModuleTest):
) )
# add a child under chapter1 # add a child under chapter1
new_module = modulestore().create_item( new_module = modulestore().create_item(
BlockUsageLocator.make_relative(source_course, "chapter1"), "sequential", self.user, BlockUsageLocator.make_relative(source_course, "chapter", "chapter1"), "sequential", self.user,
fields={'display_name': 'new sequential'}, fields={'display_name': 'new sequential'},
) )
# remove chapter1 from expected b/c its pub'd version != the source anymore since source changed # remove chapter1 from expected b/c its pub'd version != the source anymore since source changed
expected.remove("chapter1") expected.remove("chapter1")
# check that it's not in published course # check that it's not in published course
with self.assertRaises(ItemNotFoundError): with self.assertRaises(ItemNotFoundError):
modulestore().get_item(BlockUsageLocator.make_relative(dest_course, new_module.location.block_id)) modulestore().get_item(new_module.location.map_into_course(dest_course))
# publish it # publish it
modulestore().xblock_publish(self.user, source_course, dest_course, [new_module.location.block_id], None) modulestore().xblock_publish(self.user, source_course, dest_course, [new_module.location.block_id], None)
expected.append(new_module.location.block_id) expected.append(new_module.location.block_id)
# check that it is in the published course and that its parent is the chapter # check that it is in the published course and that its parent is the chapter
pub_module = modulestore().get_item(BlockUsageLocator.make_relative(dest_course, new_module.location.block_id)) pub_module = modulestore().get_item(new_module.location.map_into_course(dest_course))
self.assertEqual( self.assertEqual(
modulestore().get_parent_locations(pub_module.location)[0].block_id, "chapter1" modulestore().get_parent_locations(pub_module.location)[0].block_id, "chapter1"
) )
...@@ -1584,7 +1551,7 @@ class TestPublish(SplitModuleTest): ...@@ -1584,7 +1551,7 @@ class TestPublish(SplitModuleTest):
modulestore().xblock_publish(self.user, source_course, dest_course, [new_module.location.block_id], None) modulestore().xblock_publish(self.user, source_course, dest_course, [new_module.location.block_id], None)
expected.append(new_module.location.block_id) expected.append(new_module.location.block_id)
# check that it is in the published course (no error means it worked) # check that it is in the published course (no error means it worked)
pub_module = modulestore().get_item(BlockUsageLocator.make_relative(dest_course, new_module.location.block_id)) pub_module = modulestore().get_item(new_module.location.map_into_course(dest_course))
self._check_course( self._check_course(
source_course, dest_course, expected, ["chapter2", "chapter3", "problem1", "problem3_2"] source_course, dest_course, expected, ["chapter2", "chapter3", "problem1", "problem3_2"]
) )
...@@ -1617,11 +1584,11 @@ class TestPublish(SplitModuleTest): ...@@ -1617,11 +1584,11 @@ class TestPublish(SplitModuleTest):
expected = ["head12345", "chapter1", "chapter3", "problem1", "problem3_2"] expected = ["head12345", "chapter1", "chapter3", "problem1", "problem3_2"]
self._check_course(source_course, dest_course, expected, ["chapter2"]) self._check_course(source_course, dest_course, expected, ["chapter2"])
# now move problem1 and delete problem3_2 # now move problem1 and delete problem3_2
chapter1 = modulestore().get_item(BlockUsageLocator.make_relative(source_course, "chapter1")) chapter1 = modulestore().get_item(source_course.make_usage_key("chapter", "chapter1"))
chapter3 = modulestore().get_item(BlockUsageLocator.make_relative(source_course, "chapter3")) chapter3 = modulestore().get_item(source_course.make_usage_key("chapter", "chapter3"))
chapter1.children.append("problem1") chapter1.children.append("problem1")
chapter3.children.remove("problem1") chapter3.children.remove("problem1")
modulestore().delete_item(BlockUsageLocator.make_relative(source_course, "problem3_2"), self.user) modulestore().delete_item(source_course.make_usage_key("problem", "problem3_2"), self.user)
modulestore().xblock_publish(self.user, source_course, dest_course, ["head12345"], ["chapter2"]) modulestore().xblock_publish(self.user, source_course, dest_course, ["head12345"], ["chapter2"])
expected = ["head12345", "chapter1", "chapter3", "problem1"] expected = ["head12345", "chapter1", "chapter3", "problem1"]
self._check_course(source_course, dest_course, expected, ["chapter2", "problem3_2"]) self._check_course(source_course, dest_course, expected, ["chapter2", "problem3_2"])
...@@ -1633,8 +1600,9 @@ class TestPublish(SplitModuleTest): ...@@ -1633,8 +1600,9 @@ class TestPublish(SplitModuleTest):
history_info = modulestore().get_course_history_info(dest_course_loc) history_info = modulestore().get_course_history_info(dest_course_loc)
self.assertEqual(history_info['edited_by'], self.user) self.assertEqual(history_info['edited_by'], self.user)
for expected in expected_blocks: for expected in expected_blocks:
source = modulestore().get_item(BlockUsageLocator.make_relative(source_course_loc, expected)) # since block_type has no impact on identity, we can just provide an empty string
pub_copy = modulestore().get_item(BlockUsageLocator.make_relative(dest_course_loc, expected)) source = modulestore().get_item(source_course_loc.make_usage_key("", expected))
pub_copy = modulestore().get_item(dest_course_loc.make_usage_key("", expected))
# everything except previous_version & children should be the same # everything except previous_version & children should be the same
self.assertEqual(source.category, pub_copy.category) self.assertEqual(source.category, pub_copy.category)
self.assertEqual(source.update_version, pub_copy.update_version) self.assertEqual(source.update_version, pub_copy.update_version)
...@@ -1649,7 +1617,7 @@ class TestPublish(SplitModuleTest): ...@@ -1649,7 +1617,7 @@ class TestPublish(SplitModuleTest):
self.assertEqual(field.read_from(source), field.read_from(pub_copy)) self.assertEqual(field.read_from(source), field.read_from(pub_copy))
for unexp in unexpected_blocks: for unexp in unexpected_blocks:
with self.assertRaises(ItemNotFoundError): with self.assertRaises(ItemNotFoundError):
modulestore().get_item(BlockUsageLocator.make_relative(dest_course_loc, unexp)) modulestore().get_item(dest_course_loc.make_usage_key("", unexp))
def _compare_children(self, source_children, dest_children, unexpected): def _compare_children(self, source_children, dest_children, unexpected):
""" """
......
...@@ -105,6 +105,7 @@ class SplitWMongoCourseBoostrapper(unittest.TestCase): ...@@ -105,6 +105,7 @@ class SplitWMongoCourseBoostrapper(unittest.TestCase):
# create pointer for split # create pointer for split
course_or_parent_locator = BlockUsageLocator( course_or_parent_locator = BlockUsageLocator(
course_key=self.split_course_key, course_key=self.split_course_key,
block_type=parent_category,
block_id=parent_name block_id=parent_name
) )
else: 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