diff --git a/common/lib/xmodule/xmodule/modulestore/locator.py b/common/lib/xmodule/xmodule/modulestore/locator.py deleted file mode 100644 index b568b6e808..0000000000 --- a/common/lib/xmodule/xmodule/modulestore/locator.py +++ /dev/null @@ -1,527 +0,0 @@ -""" -Identifier for course resources. -""" - -from __future__ import absolute_import -import logging -import inspect -import re -from abc import abstractmethod - -from bson.objectid import ObjectId -from bson.errors import InvalidId - -from opaque_keys import OpaqueKey, InvalidKeyError - -from opaque_keys.edx.keys import CourseKey, UsageKey, DefinitionKey - -log = logging.getLogger(__name__) - - -class LocalId(object): - """ - Class for local ids for non-persisted xblocks (which can have hardcoded block_ids if necessary) - """ - def __init__(self, block_id=None): - self.block_id = block_id - super(LocalId, self).__init__() - - def __str__(self): - return "localid_{}".format(self.block_id or id(self)) - - -class Locator(OpaqueKey): - """ - A locator is like a URL, it refers to a course resource. - - Locator is an abstract base class: do not instantiate - """ - - BLOCK_TYPE_PREFIX = r"type" - # 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\-~.:]' - - def __str__(self): - ''' - str(self) returns something like this: "mit.eecs.6002x" - ''' - return unicode(self).encode('utf-8') - - @abstractmethod - def version(self): - """ - Returns the ObjectId referencing this specific location. - Raises InvalidKeyError if the instance - doesn't have a complete enough specification. - """ - raise NotImplementedError() - - @classmethod - def as_object_id(cls, value): - """ - Attempts to cast value as a bson.objectid.ObjectId. - If cast fails, raises ValueError - """ - try: - return ObjectId(value) - except InvalidId: - raise ValueError('"%s" is not a valid version_guid' % value) - - -class BlockLocatorBase(Locator): - - # Token separating org from offering - ORG_SEPARATOR = '+' - - # 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" - - ALLOWED_ID_RE = re.compile(r'^' + Locator.ALLOWED_ID_CHARS + '+$', re.UNICODE) - - URL_RE_SOURCE = r""" - ((?P{ALLOWED_ID_CHARS}+)\+(?P{ALLOWED_ID_CHARS}+)\+?)?? - ({BRANCH_PREFIX}\+(?P{ALLOWED_ID_CHARS}+)\+?)? - ({VERSION_PREFIX}\+(?P[A-F0-9]+)\+?)? - ({BLOCK_TYPE_PREFIX}\+(?P{ALLOWED_ID_CHARS}+)\+?)? - ({BLOCK_PREFIX}\+(?P{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) - - @classmethod - def parse_url(cls, string): - """ - Raises InvalidKeyError 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 = cls.URL_RE.match(string) - if not match: - raise InvalidKeyError(cls, string) - return match.groupdict() - - -class CourseLocator(BlockLocatorBase, CourseKey): - """ - Examples of valid CourseLocator specifications: - CourseLocator(version_guid=ObjectId('519665f6223ebd6980884f2b')) - CourseLocator(org='mit.eecs', offering='6.002x') - CourseLocator(org='mit.eecs', offering='6002x', branch = 'published') - CourseLocator.from_string('course-locator:version+519665f6223ebd6980884f2b') - CourseLocator.from_string('course-locator:mit.eecs+6002x') - CourseLocator.from_string('course-locator:mit.eecs+6002x+branch+published') - CourseLocator.from_string('course-locator: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/ - versions) with optional 'branch', - or version_guid (which points to a specific version). Can contain both in which case - the persistence layer may raise exceptions if the given version != the current such version - of the course. - """ - CANONICAL_NAMESPACE = 'course-locator' - KEY_FIELDS = ('org', 'offering', 'branch', 'version_guid') - - # stubs to fake out the abstractproperty class instrospection and allow treatment as attrs in instances - org = None - offering = None - - def __init__(self, org=None, offering=None, branch=None, version_guid=None): - """ - Construct a CourseLocator - - Args: - version_guid (string or ObjectId): optional unique id for the version - org, offering (string): the standard definition. Optional only if version_guid given - branch (string): the branch such as 'draft', 'published', 'staged', 'beta' - """ - if version_guid: - version_guid = self.as_object_id(version_guid) - - 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]) - - super(CourseLocator, self).__init__( - org=org, - offering=offering, - branch=branch, - version_guid=version_guid - ) - - 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") - - def version(self): - """ - Returns the ObjectId referencing this specific location. - """ - return self.version_guid - - @classmethod - def _from_string(cls, serialized): - """ - Return a CourseLocator parsing the given serialized string - :param serialized: matches the string to a CourseLocator - """ - parse = cls.parse_url(serialized) - - if parse['version_guid']: - parse['version_guid'] = cls.as_object_id(parse['version_guid']) - - return cls(**{key: parse.get(key) for key in cls.KEY_FIELDS}) - - def html_id(self): - """ - Generate a discussion group id based on course - - To make compatible with old Location object functionality. I don't believe this behavior fits at 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). - """ - return unicode(self) - - def make_usage_key(self, block_type, block_id): - return BlockUsageLocator( - course_key=self, - block_type=block_type, - block_id=block_id - ) - - def make_asset_key(self, asset_type, path): - raise NotImplementedError() - - def version_agnostic(self): - """ - We don't care if the locator's version is not the current head; so, avoid version conflict - by reducing info. - Returns a copy of itself without any version info. - - :raises: ValueError if the block locator has no org & offering - """ - return CourseLocator( - org=self.org, - offering=self.offering, - branch=self.branch, - version_guid=None - ) - - def course_agnostic(self): - """ - We only care about the locator's version not its course. - Returns a copy of itself without any course info. - - :raises: ValueError if the block locator has no version_guid - """ - return CourseLocator( - org=None, - offering=None, - branch=None, - version_guid=self.version_guid - ) - - def for_branch(self, branch): - """ - Return a new CourseLocator for another branch of the same course (also version agnostic) - """ - if self.org is None: - raise InvalidKeyError(self.__class__, "Branches must have full course ids not just versions") - return CourseLocator( - org=self.org, - offering=self.offering, - branch=branch, - version_guid=None - ) - - def for_version(self, version_guid): - """ - Return a new CourseLocator for another version of the same course and branch. Usually used - when the head is updated (and thus the course x branch now points to this version) - """ - return CourseLocator( - org=self.org, - offering=self.offering, - branch=self.branch, - version_guid=version_guid - ) - - def _to_string(self): - """ - Return a string representing this location. - """ - parts = [] - if self.offering: - parts.extend([self.org, self.offering]) - if self.branch: - parts.append(u"{prefix}+{branch}".format(prefix=self.BRANCH_PREFIX, branch=self.branch)) - if self.version_guid: - parts.append(u"{prefix}+{guid}".format(prefix=self.VERSION_PREFIX, guid=self.version_guid)) - return u"+".join(parts) - - -class BlockUsageLocator(BlockLocatorBase, UsageKey): - """ - Encodes a location. - - Locations address modules (aka blocks) which are definitions situated in a - course instance. Thus, a Location must identify the course and the occurrence of - the defined element in the course. Courses can be a version of an offering, the - current draft head, or the current production version. - - Locators can contain both a version and a org + offering w/ branch. The split mongo functions - may raise errors if these conflict w/ the current db state (i.e., the course's branch != - the version_guid) - - Locations can express as urls as well as dictionaries. They consist of - package_identifier: course_guid | version_guid - block : guid - branch : string - """ - CANONICAL_NAMESPACE = 'edx' - KEY_FIELDS = ('course_key', 'block_type', 'block_id') - - # fake out class instrospection as this is an attr in this class's instances - course_key = None - block_type = None - - def __init__(self, course_key, block_type, block_id): - """ - Construct a BlockUsageLocator - """ - block_id = self._parse_block_ref(block_id) - if block_id is None: - raise InvalidKeyError(self.__class__, "Missing block id") - - super(BlockUsageLocator, self).__init__(course_key=course_key, block_type=block_type, block_id=block_id) - - @classmethod - def _from_string(cls, serialized): - """ - Requests CourseLocator to deserialize its part and then adds the local deserialization of block - """ - course_key = CourseLocator._from_string(serialized) - parsed_parts = cls.parse_url(serialized) - block_id = parsed_parts.get('block_id', None) - if block_id is None: - raise InvalidKeyError(cls, serialized) - return cls(course_key, parsed_parts.get('block_type'), block_id) - - def version_agnostic(self): - """ - We don't care if the locator's version is not the current head; so, avoid version conflict - by reducing info. - Returns a copy of itself without any version info. - - :raises: ValueError if the block locator has no org and offering - """ - return BlockUsageLocator( - course_key=self.course_key.version_agnostic(), - block_type=self.block_type, - block_id=self.block_id, - ) - - def course_agnostic(self): - """ - We only care about the locator's version not its course. - Returns a copy of itself without any course info. - - :raises: ValueError if the block locator has no version_guid - """ - return BlockUsageLocator( - course_key=self.course_key.course_agnostic(), - block_type=self.block_type, - block_id=self.block_id - ) - - def for_branch(self, branch): - """ - Return a UsageLocator for the same block in a different branch of the course. - """ - return BlockUsageLocator( - 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 - ) - - @classmethod - def _parse_block_ref(cls, block_ref): - if isinstance(block_ref, LocalId): - return block_ref - elif len(block_ref) > 0 and cls.ALLOWED_ID_RE.match(block_ref): - return block_ref - else: - raise InvalidKeyError(cls, block_ref) - - @property - def definition_key(self): - raise NotImplementedError() - - @property - def org(self): - return self.course_key.org - - @property - def offering(self): - return self.course_key.offering - - @property - def branch(self): - return self.course_key.branch - - @property - def version_guid(self): - return self.course_key.version_guid - - def version(self): - return self.course_key.version_guid - - @property - def name(self): - """ - The ambiguously named field from Location which code expects to find - """ - return self.block_id - - def is_fully_specified(self): - return self.course_key.is_fully_specified() - - @classmethod - def make_relative(cls, course_locator, block_type, block_id): - """ - 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 - """ - if hasattr(course_locator, 'course_key'): - course_locator = course_locator.course_key - return BlockUsageLocator( - course_key=course_locator, - block_type=block_type, - block_id=block_id - ) - - def map_into_course(self, course_key): - """ - 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 - """ - return BlockUsageLocator.make_relative(course_key, self.block_type, self.block_id) - - def _to_string(self): - """ - Return a string representing this location. - """ - return u"{course_key}+{BLOCK_TYPE_PREFIX}+{block_type}+{BLOCK_PREFIX}+{block_id}".format( - course_key=self.course_key._to_string(), - BLOCK_TYPE_PREFIX=self.BLOCK_TYPE_PREFIX, - block_type=self.block_type, - BLOCK_PREFIX=self.BLOCK_PREFIX, - block_id=self.block_id - ) - - def html_id(self): - """ - Generate a discussion group id based on course - - To make compatible with old Location object functionality. I don't believe this behavior fits at 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). - """ - return unicode(self) - - -class DefinitionLocator(Locator, DefinitionKey): - """ - Container for how to locate a description (the course-independent content). - """ - CANONICAL_NAMESPACE = 'defx' - KEY_FIELDS = ('definition_id', 'block_type') - - # override the abstractproperty - block_type = None - definition_id = None - - def __init__(self, block_type, definition_id): - if isinstance(definition_id, LocalId): - super(DefinitionLocator, self).__init__(definition_id=definition_id, block_type=block_type) - elif isinstance(definition_id, basestring): - try: - definition_id = self.as_object_id(definition_id) - except ValueError: - raise InvalidKeyError(self, definition_id) - super(DefinitionLocator, self).__init__(definition_id=definition_id, block_type=block_type) - elif isinstance(definition_id, ObjectId): - super(DefinitionLocator, self).__init__(definition_id=definition_id, block_type=block_type) - - def _to_string(self): - ''' - Return a string representing this location. - unicode(self) returns something like this: "519665f6223ebd6980884f2b+type+problem" - ''' - return u"{}+{}+{}".format(unicode(self.definition_id), self.BLOCK_TYPE_PREFIX, self.block_type) - - URL_RE = re.compile( - r"^(?P[A-F0-9]+)\+{}\+(?P{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 DefinitionLocator parsing the given serialized string - :param serialized: matches the string to - """ - 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): - """ - Returns the ObjectId referencing this specific location. - """ - return self.definition_id - - -class VersionTree(object): - """ - Holds trees of Locators to represent version histories. - """ - def __init__(self, locator, tree_dict=None): - """ - :param locator: must be version specific (Course has version_guid or definition had id) - """ - if not isinstance(locator, Locator) and not inspect.isabstract(locator): - raise TypeError("locator {} must be a concrete subclass of Locator".format(locator)) - if not locator.version(): - raise ValueError("locator must be version specific (Course has version_guid or definition had id)") - self.locator = locator - if tree_dict is None: - self.children = [] - else: - self.children = [VersionTree(child, tree_dict) - for child in tree_dict.get(locator.version(), [])] diff --git a/common/lib/xmodule/xmodule/modulestore/tests/test_locators.py b/common/lib/xmodule/xmodule/modulestore/tests/test_locators.py deleted file mode 100644 index b3b571f88b..0000000000 --- a/common/lib/xmodule/xmodule/modulestore/tests/test_locators.py +++ /dev/null @@ -1,297 +0,0 @@ -""" -Tests for opaque_keys.edx.locator. -""" -from unittest import TestCase - -import random -from bson.objectid import ObjectId -from opaque_keys import InvalidKeyError -from opaque_keys.edx.locator import Locator, CourseLocator, BlockUsageLocator, DefinitionLocator -from ddt import ddt, data -from opaque_keys.edx.keys import UsageKey, CourseKey, DefinitionKey - - -@ddt -class LocatorTest(TestCase): - """ - Tests for subclasses of Locator. - """ - - def test_cant_instantiate_abstract_class(self): - self.assertRaises(TypeError, Locator) - - def test_course_constructor_underspecified(self): - with self.assertRaises(InvalidKeyError): - CourseLocator() - with self.assertRaises(InvalidKeyError): - CourseLocator(branch='published') - - def test_course_constructor_bad_version_guid(self): - with self.assertRaises(ValueError): - CourseLocator(version_guid="012345") - - with self.assertRaises(InvalidKeyError): - CourseLocator(version_guid=None) - - def test_course_constructor_version_guid(self): - # generate a random location - test_id_1 = ObjectId() - test_id_1_loc = str(test_id_1) - testobj_1 = CourseLocator(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(testobj_1._to_string(), u'+'.join((testobj_1.VERSION_PREFIX, test_id_1_loc))) - - # Test using a given string - test_id_2_loc = '519665f6223ebd6980884f2b' - test_id_2 = ObjectId(test_id_2_loc) - testobj_2 = CourseLocator(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(testobj_2._to_string(), u'+'.join((testobj_2.VERSION_PREFIX, test_id_2_loc))) - - @data( - ' mit.eecs', - 'mit.eecs ', - CourseLocator.VERSION_PREFIX + '+mit.eecs', - BlockUsageLocator.BLOCK_PREFIX + '+black+mit.eecs', - 'mit.ee cs', - 'mit.ee,cs', - 'mit.ee+cs', - 'mit.ee&cs', - 'mit.ee()cs', - CourseLocator.BRANCH_PREFIX + '+this', - 'mit.eecs+' + CourseLocator.BRANCH_PREFIX, - 'mit.eecs+' + CourseLocator.BRANCH_PREFIX + '+this+' + CourseLocator.BRANCH_PREFIX + '+that', - 'mit.eecs+' + CourseLocator.BRANCH_PREFIX + '+this+' + CourseLocator.BRANCH_PREFIX, - 'mit.eecs+' + CourseLocator.BRANCH_PREFIX + '+this ', - 'mit.eecs+' + CourseLocator.BRANCH_PREFIX + '+th%is ', - ) - def test_course_constructor_bad_package_id(self, bad_id): - """ - Test all sorts of badly-formed package_ids (and urls with those package_ids) - """ - with self.assertRaises(InvalidKeyError): - CourseLocator(org=bad_id, offering='test') - - with self.assertRaises(InvalidKeyError): - CourseLocator(org='test', offering=bad_id) - - with self.assertRaises(InvalidKeyError): - CourseKey.from_string('course-locator:test+{}'.format(bad_id)) - - @data('course-locator:', 'course-locator:/mit.eecs', 'http:mit.eecs', 'course-locator//mit.eecs') - def test_course_constructor_bad_url(self, bad_url): - with self.assertRaises(InvalidKeyError): - CourseKey.from_string(bad_url) - - def test_course_constructor_url(self): - # 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. - test_id_loc = '519665f6223ebd6980884f2b' - testobj = CourseKey.from_string("course-locator:{}+{}+{}+hw3".format( - CourseLocator.VERSION_PREFIX, test_id_loc, CourseLocator.BLOCK_PREFIX - )) - self.check_course_locn_fields( - testobj, - version_guid=ObjectId(test_id_loc) - ) - - def test_course_constructor_url_package_id_and_version_guid(self): - test_id_loc = '519665f6223ebd6980884f2b' - testobj = CourseKey.from_string( - 'course-locator:mit.eecs+honors.6002x+{}+{}'.format(CourseLocator.VERSION_PREFIX, test_id_loc) - ) - self.check_course_locn_fields( - testobj, - org='mit.eecs', - offering='honors.6002x', - version_guid=ObjectId(test_id_loc) - ) - - def test_course_constructor_url_package_id_branch_and_version_guid(self): - test_id_loc = '519665f6223ebd6980884f2b' - org = 'mit.eecs' - offering = '~6002x' - testobj = CourseKey.from_string('course-locator:{}+{}+{}+draft-1+{}+{}'.format( - org, offering, CourseLocator.BRANCH_PREFIX, CourseLocator.VERSION_PREFIX, test_id_loc - )) - self.check_course_locn_fields( - testobj, - org=org, - offering=offering, - branch='draft-1', - version_guid=ObjectId(test_id_loc) - ) - - def test_course_constructor_package_id_no_branch(self): - org = 'mit.eecs' - offering = '6002x' - testurn = '{}+{}'.format(org, offering) - testobj = CourseLocator(org=org, offering=offering) - self.check_course_locn_fields(testobj, org=org, offering=offering) - self.assertEqual(testobj._to_string(), testurn) - - def test_course_constructor_package_id_separate_branch(self): - org = 'mit.eecs' - offering = '6002x' - test_branch = 'published' - expected_urn = '{}+{}+{}+{}'.format(org, offering, CourseLocator.BRANCH_PREFIX, test_branch) - testobj = CourseLocator(org=org, offering=offering, branch=test_branch) - self.check_course_locn_fields( - testobj, - org=org, - offering=offering, - branch=test_branch, - ) - self.assertEqual(testobj.branch, test_branch) - self.assertEqual(testobj._to_string(), expected_urn) - - def test_block_constructor(self): - expected_org = 'mit.eecs' - expected_offering = '6002x' - expected_branch = 'published' - expected_block_ref = 'HW3' - testurn = 'edx:{}+{}+{}+{}+{}+{}+{}+{}'.format( - 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 - ) - self.assertEqual(unicode(testobj), testurn) - testobj = testobj.for_version(ObjectId()) - agnostic = testobj.version_agnostic() - self.assertIsNone(agnostic.version_guid) - self.check_block_locn_fields(agnostic, - org=expected_org, - offering=expected_offering, - branch=expected_branch, - block=expected_block_ref) - - def test_block_constructor_url_version_prefix(self): - test_id_loc = '519665f6223ebd6980884f2b' - testobj = UsageKey.from_string( - '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( - testobj, - org='mit.eecs', - offering='6002x', - block_type='problem', - block='lab2', - version_guid=ObjectId(test_id_loc) - ) - agnostic = testobj.course_agnostic() - self.check_block_locn_fields( - agnostic, - block='lab2', - org=None, - offering=None, - version_guid=ObjectId(test_id_loc) - ) - self.assertIsNone(agnostic.offering) - self.assertIsNone(agnostic.org) - - def test_block_constructor_url_kitchen_sink(self): - test_id_loc = '519665f6223ebd6980884f2b' - testobj = UsageKey.from_string( - 'edx:mit.eecs+6002x+{}+draft+{}+{}+{}+problem+{}+lab2'.format( - CourseLocator.BRANCH_PREFIX, CourseLocator.VERSION_PREFIX, test_id_loc, - BlockUsageLocator.BLOCK_TYPE_PREFIX, BlockUsageLocator.BLOCK_PREFIX - ) - ) - self.check_block_locn_fields( - testobj, - org='mit.eecs', - offering='6002x', - branch='draft', - block='lab2', - version_guid=ObjectId(test_id_loc) - ) - - def test_colon_name(self): - """ - It seems we used to use colons in names; so, ensure they're acceptable. - """ - org = 'mit.eecs' - offering = '1' - branch = 'foo' - block_id = 'problem:with-colon~2' - testobj = BlockUsageLocator( - CourseLocator(org=org, offering=offering, branch=branch), - block_type='problem', - block_id=block_id - ) - self.check_block_locn_fields( - testobj, org=org, offering=offering, branch=branch, block=block_id - ) - - def test_relative(self): - """ - Test making a relative usage locator. - """ - org = 'mit.eecs' - offering = '1' - branch = 'foo' - baseobj = CourseLocator(org=org, offering=offering, branch=branch) - block_id = 'problem:with-colon~2' - testobj = BlockUsageLocator.make_relative(baseobj, 'problem', block_id) - self.check_block_locn_fields( - testobj, org=org, offering=offering, branch=branch, block=block_id - ) - block_id = 'completely_different' - testobj = BlockUsageLocator.make_relative(testobj, 'problem', block_id) - self.check_block_locn_fields( - testobj, org=org, offering=offering, branch=branch, block=block_id - ) - - def test_repr(self): - testurn = u'edx:mit.eecs+6002x+{}+published+{}+problem+{}+HW3'.format( - CourseLocator.BRANCH_PREFIX, BlockUsageLocator.BLOCK_TYPE_PREFIX, BlockUsageLocator.BLOCK_PREFIX - ) - 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): - object_id = '{:024x}'.format(random.randrange(16 ** 24)) - definition_locator = DefinitionLocator('html', object_id) - 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): - object_id = '{:024x}'.format(random.randrange(16 ** 24)) - definition_locator = DefinitionLocator('html', object_id) - self.assertEqual(object_id, str(definition_locator.version())) - - # ------------------------------------------------------------------ - # Utilities - - def check_course_locn_fields(self, testobj, version_guid=None, - org=None, offering=None, branch=None): - """ - Checks the version, org, offering, and branch in testobj - """ - self.assertEqual(testobj.version_guid, version_guid) - self.assertEqual(testobj.org, org) - self.assertEqual(testobj.offering, offering) - self.assertEqual(testobj.branch, branch) - - def check_block_locn_fields(self, testobj, version_guid=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 - """ - self.check_course_locn_fields(testobj, version_guid, org, offering, - branch) - if block_type is not None: - self.assertEqual(testobj.block_type, block_type) - self.assertEqual(testobj.block_id, block)