Commit 9cbd2312 by Don Mitchell

Merge pull request #1416 from edx/dhm/regex_courseid

Parse locator url better
parents 1db76191 8902a89f
...@@ -61,7 +61,7 @@ class TestCourseIndex(CourseTestCase): ...@@ -61,7 +61,7 @@ class TestCourseIndex(CourseTestCase):
Test the error conditions for the access Test the error conditions for the access
""" """
locator = loc_mapper().translate_location(self.course.location.course_id, self.course.location, False, True) locator = loc_mapper().translate_location(self.course.location.course_id, self.course.location, False, True)
outline_url = reverse('contentstore.views.course_handler', kwargs={'course_url': unicode(locator)}) outline_url = locator.url_reverse('course/', '')
# register a non-staff member and try to delete the course branch # register a non-staff member and try to delete the course branch
non_staff_client, _ = self.createNonStaffAuthedUserClient() non_staff_client, _ = self.createNonStaffAuthedUserClient()
response = non_staff_client.delete(outline_url, {}, HTTP_ACCEPT='application/json') response = non_staff_client.delete(outline_url, {}, HTTP_ACCEPT='application/json')
......
...@@ -60,7 +60,7 @@ __all__ = ['create_new_course', 'course_info', 'course_handler', ...@@ -60,7 +60,7 @@ __all__ = ['create_new_course', 'course_info', 'course_handler',
@login_required @login_required
def course_handler(request, course_url): def course_handler(request, tag=None, course_id=None, branch=None, version_guid=None, block=None):
""" """
The restful handler for course specific requests. The restful handler for course specific requests.
It provides the course tree with the necessary information for identifying and labeling the parts. The root It provides the course tree with the necessary information for identifying and labeling the parts. The root
...@@ -84,7 +84,10 @@ def course_handler(request, course_url): ...@@ -84,7 +84,10 @@ def course_handler(request, course_url):
if 'application/json' in request.META.get('HTTP_ACCEPT', 'application/json'): if 'application/json' in request.META.get('HTTP_ACCEPT', 'application/json'):
if request.method == 'GET': if request.method == 'GET':
raise NotImplementedError('coming soon') raise NotImplementedError('coming soon')
elif not has_access(request.user, BlockUsageLocator(course_url)): elif not has_access(
request.user,
BlockUsageLocator(course_id=course_id, branch=branch, version_guid=version_guid, usage_id=block)
):
raise PermissionDenied() raise PermissionDenied()
elif request.method == 'POST': elif request.method == 'POST':
raise NotImplementedError() raise NotImplementedError()
...@@ -95,7 +98,7 @@ def course_handler(request, course_url): ...@@ -95,7 +98,7 @@ def course_handler(request, course_url):
else: else:
return HttpResponseBadRequest() return HttpResponseBadRequest()
elif request.method == 'GET': # assume html elif request.method == 'GET': # assume html
return course_index(request, course_url) return course_index(request, course_id, branch, version_guid, block)
else: else:
return HttpResponseNotFound() return HttpResponseNotFound()
...@@ -108,18 +111,18 @@ def old_course_index_shim(request, org, course, name): ...@@ -108,18 +111,18 @@ def old_course_index_shim(request, org, course, name):
""" """
old_location = Location(['i4x', org, course, 'course', name]) old_location = Location(['i4x', org, course, 'course', name])
locator = loc_mapper().translate_location(old_location.course_id, old_location, False, True) locator = loc_mapper().translate_location(old_location.course_id, old_location, False, True)
return course_index(request, locator) return course_index(request, locator.course_id, locator.branch, locator.version_guid, locator.usage_id)
@login_required @login_required
@ensure_csrf_cookie @ensure_csrf_cookie
def course_index(request, course_url): def course_index(request, course_id, branch, version_guid, block):
""" """
Display an editable course overview. Display an editable course overview.
org, course, name: Attributes of the Location for the item to edit org, course, name: Attributes of the Location for the item to edit
""" """
location = BlockUsageLocator(course_url) location = BlockUsageLocator(course_id=course_id, branch=branch, version_guid=version_guid, usage_id=block)
# TODO: when converting to split backend, if location does not have a usage_id, # TODO: when converting to split backend, if location does not have a usage_id,
# we'll need to get the course's root block_id # we'll need to get the course's root block_id
if not has_access(request.user, location): if not has_access(request.user, location):
......
...@@ -47,12 +47,13 @@ def index(request): ...@@ -47,12 +47,13 @@ def index(request):
def format_course_for_view(course): def format_course_for_view(course):
# published = false b/c studio manipulates draft versions not b/c the course isn't pub'd # published = false b/c studio manipulates draft versions not b/c the course isn't pub'd
course_url = loc_mapper().translate_location( course_loc = loc_mapper().translate_location(
course.location.course_id, course.location, published=False, add_entry_if_missing=True course.location.course_id, course.location, published=False, add_entry_if_missing=True
) )
return ( return (
course.display_name, course.display_name,
reverse("contentstore.views.course_handler", kwargs={'course_url': course_url}), # note, couldn't get django reverse to work; so, wrote workaround
course_loc.url_reverse('course/', ''),
get_lms_link_for_item( get_lms_link_for_item(
course.location course.location
), ),
......
...@@ -15,10 +15,7 @@ ...@@ -15,10 +15,7 @@
% if context_course: % if context_course:
<% <%
ctx_loc = context_course.location ctx_loc = context_course.location
index_url = reverse( index_url = loc_mapper().translate_location(ctx_loc.course_id, ctx_loc, False, True).url_reverse('course/', '')
'contentstore.views.course_handler',
kwargs={'course_url': loc_mapper().translate_location(ctx_loc.course_id, ctx_loc, False, True)}
)
%> %>
<h2 class="info-course"> <h2 class="info-course">
<span class="sr">${_("Current Course:")}</span> <span class="sr">${_("Current Course:")}</span>
......
import re
from django.conf import settings from django.conf import settings
from django.conf.urls import patterns, include, url from django.conf.urls import patterns, include, url
# TODO: This should be removed once the CMS is running via wsgi on all production servers # TODO: This should be removed once the CMS is running via wsgi on all production servers
import cms.startup as startup import cms.startup as startup
from xmodule.modulestore import parsers
startup.run() startup.run()
# There is a course creators admin table. # There is a course creators admin table.
...@@ -136,7 +138,8 @@ urlpatterns += patterns( ...@@ -136,7 +138,8 @@ urlpatterns += patterns(
), ),
url(r'^course$', 'index'), url(r'^course$', 'index'),
url(r'^course/(?P<course_url>.*)$', 'course_handler'), # (?ix) == ignore case and verbose (multiline regex)
url(r'(?ix)^course/{}$'.format(parsers.URL_RE_SOURCE), 'course_handler'),
) )
js_info_dict = { js_info_dict = {
......
...@@ -42,7 +42,7 @@ class VersionConflictError(Exception): ...@@ -42,7 +42,7 @@ class VersionConflictError(Exception):
""" """
The caller asked for either draft or published head and gave a version which conflicted with it. The caller asked for either draft or published head and gave a version which conflicted with it.
""" """
def __init__(self, requestedLocation, currentHead): def __init__(self, requestedLocation, currentHeadVersionGuid):
super(VersionConflictError, self).__init__() super(VersionConflictError, self).__init__()
self.requestedLocation = requestedLocation self.requestedLocation = requestedLocation
self.currentHead = currentHead self.currentHeadVersionGuid = currentHeadVersionGuid
...@@ -13,7 +13,7 @@ from bson.errors import InvalidId ...@@ -13,7 +13,7 @@ from bson.errors import InvalidId
from xmodule.modulestore.exceptions import InsufficientSpecificationError, OverSpecificationError from xmodule.modulestore.exceptions import InsufficientSpecificationError, OverSpecificationError
from .parsers import parse_url, parse_course_id, parse_block_ref from .parsers import parse_url, parse_course_id, parse_block_ref
from .parsers import BRANCH_PREFIX, BLOCK_PREFIX, URL_VERSION_PREFIX from .parsers import BRANCH_PREFIX, BLOCK_PREFIX, VERSION_PREFIX
import re import re
from xmodule.modulestore import Location from xmodule.modulestore import Location
...@@ -190,8 +190,8 @@ class CourseLocator(Locator): ...@@ -190,8 +190,8 @@ class CourseLocator(Locator):
self.init_from_version_guid(version_guid) self.init_from_version_guid(version_guid)
if course_id or branch: if course_id or branch:
self.init_from_course_id(course_id, branch) self.init_from_course_id(course_id, branch)
assert self.version_guid or self.course_id, \ if self.version_guid is None and self.course_id is None:
"Either version_guid or course_id should be set." raise ValueError("Either version_guid or course_id should be set: {}".format(url))
def __unicode__(self): def __unicode__(self):
""" """
...@@ -200,10 +200,10 @@ class CourseLocator(Locator): ...@@ -200,10 +200,10 @@ class CourseLocator(Locator):
if self.course_id: if self.course_id:
result = self.course_id result = self.course_id
if self.branch: if self.branch:
result += BRANCH_PREFIX + self.branch result += '/' + BRANCH_PREFIX + self.branch
return result return result
elif self.version_guid: elif self.version_guid:
return URL_VERSION_PREFIX + str(self.version_guid) return VERSION_PREFIX + str(self.version_guid)
else: else:
# raise InsufficientSpecificationError("missing course_id or version_guid") # raise InsufficientSpecificationError("missing course_id or version_guid")
return '<InsufficientSpecificationError: missing course_id or version_guid>' return '<InsufficientSpecificationError: missing course_id or version_guid>'
...@@ -263,22 +263,42 @@ class CourseLocator(Locator): ...@@ -263,22 +263,42 @@ class CourseLocator(Locator):
version_guid=self.version_guid, version_guid=self.version_guid,
branch=self.branch) branch=self.branch)
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 init_from_url(self, url): def init_from_url(self, url):
""" """
url must be a string beginning with 'edx://' and containing url must be a string beginning with 'edx://' and containing
either a valid version_guid or course_id (with optional branch), or both. either a valid version_guid or course_id (with optional branch), or both.
""" """
if isinstance(url, Locator): if isinstance(url, Locator):
url = url.url() parse = url.__dict__
if not isinstance(url, basestring): elif not isinstance(url, basestring):
raise TypeError('%s is not an instance of basestring' % url) raise TypeError('%s is not an instance of basestring' % url)
else:
parse = parse_url(url, tag_optional=True) parse = parse_url(url, tag_optional=True)
if not parse: if not parse:
raise ValueError('Could not parse "%s" as a url' % url) raise ValueError('Could not parse "%s" as a url' % url)
self._set_value( self._set_value(
parse, 'version_guid', lambda (new_guid): self.set_version_guid(self.as_object_id(new_guid)) parse, 'version_guid', lambda (new_guid): self.set_version_guid(self.as_object_id(new_guid))
) )
self._set_value(parse, 'id', lambda (new_id): self.set_course_id(new_id)) self._set_value(parse, 'course_id', lambda (new_id): self.set_course_id(new_id))
self._set_value(parse, 'branch', lambda (new_branch): self.set_branch(new_branch)) self._set_value(parse, 'branch', lambda (new_branch): self.set_branch(new_branch))
def init_from_version_guid(self, version_guid): def init_from_version_guid(self, version_guid):
...@@ -313,9 +333,9 @@ class CourseLocator(Locator): ...@@ -313,9 +333,9 @@ class CourseLocator(Locator):
raise ValueError("%s does not have a valid course_id" % course_id) raise ValueError("%s does not have a valid course_id" % course_id)
parse = parse_course_id(course_id) parse = parse_course_id(course_id)
if not parse: if not parse or parse['course_id'] is None:
raise ValueError('Could not parse "%s" as a course_id' % course_id) raise ValueError('Could not parse "%s" as a course_id' % course_id)
self.set_course_id(parse['id']) self.set_course_id(parse['course_id'])
rev = parse['branch'] rev = parse['branch']
if rev: if rev:
self.set_branch(rev) self.set_branch(rev)
...@@ -396,11 +416,12 @@ class BlockUsageLocator(CourseLocator): ...@@ -396,11 +416,12 @@ class BlockUsageLocator(CourseLocator):
self.init_block_ref_from_course_id(course_id) self.init_block_ref_from_course_id(course_id)
if usage_id: if usage_id:
self.init_block_ref(usage_id) self.init_block_ref(usage_id)
CourseLocator.__init__(self, super(BlockUsageLocator, self).__init__(
url=url, url=url,
version_guid=version_guid, version_guid=version_guid,
course_id=course_id, course_id=course_id,
branch=branch) branch=branch
)
def is_initialized(self): def is_initialized(self):
""" """
...@@ -459,10 +480,12 @@ class BlockUsageLocator(CourseLocator): ...@@ -459,10 +480,12 @@ class BlockUsageLocator(CourseLocator):
def init_block_ref_from_course_id(self, course_id): def init_block_ref_from_course_id(self, course_id):
if isinstance(course_id, CourseLocator): if isinstance(course_id, CourseLocator):
# FIXME the parsed course_id should never contain a block ref
course_id = course_id.course_id course_id = course_id.course_id
assert course_id, "%s does not have a valid course_id" assert course_id, "%s does not have a valid course_id"
parse = parse_course_id(course_id) parse = parse_course_id(course_id)
assert parse, 'Could not parse "%s" as a course_id' % course_id if parse is None:
raise ValueError('Could not parse "%s" as a course_id' % course_id)
self._set_value(parse, 'block', lambda(new_block): self.set_usage_id(new_block)) self._set_value(parse, 'block', lambda(new_block): self.set_usage_id(new_block))
def __unicode__(self): def __unicode__(self):
...@@ -470,7 +493,7 @@ class BlockUsageLocator(CourseLocator): ...@@ -470,7 +493,7 @@ class BlockUsageLocator(CourseLocator):
Return a string representing this location. Return a string representing this location.
""" """
rep = super(BlockUsageLocator, self).__unicode__() rep = super(BlockUsageLocator, self).__unicode__()
return rep + BLOCK_PREFIX + unicode(self.usage_id) return rep + '/' + BLOCK_PREFIX + unicode(self.usage_id)
class DefinitionLocator(Locator): class DefinitionLocator(Locator):
...@@ -478,7 +501,7 @@ class DefinitionLocator(Locator): ...@@ -478,7 +501,7 @@ class DefinitionLocator(Locator):
Container for how to locate a description (the course-independent content). Container for how to locate a description (the course-independent content).
""" """
URL_RE = re.compile(r'^defx://' + URL_VERSION_PREFIX + '([^/]+)$', re.IGNORECASE) URL_RE = re.compile(r'^defx://' + VERSION_PREFIX + '([^/]+)$', re.IGNORECASE)
def __init__(self, definition_id): def __init__(self, definition_id):
if isinstance(definition_id, basestring): if isinstance(definition_id, basestring):
regex_match = self.URL_RE.match(definition_id) regex_match = self.URL_RE.match(definition_id)
...@@ -491,7 +514,7 @@ class DefinitionLocator(Locator): ...@@ -491,7 +514,7 @@ class DefinitionLocator(Locator):
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: "version/519665f6223ebd6980884f2b"
''' '''
return URL_VERSION_PREFIX + str(self.definition_id) return VERSION_PREFIX + str(self.definition_id)
def url(self): def url(self):
""" """
......
import re import re
# Prefix for the branch portion of a locator URL # Prefix for the branch portion of a locator URL
BRANCH_PREFIX = "/branch/" BRANCH_PREFIX = r"branch/"
# Prefix for the block portion of a locator URL # Prefix for the block portion of a locator URL
BLOCK_PREFIX = "/block/" BLOCK_PREFIX = r"block/"
# Prefix for the version portion of a locator URL, when it is preceded by a course ID # Prefix for the version portion of a locator URL, when it is preceded by a course ID
VERSION_PREFIX = "/version/" VERSION_PREFIX = r"version/"
# Prefix for version when it begins the URL (no course ID).
URL_VERSION_PREFIX = 'version/'
URL_RE = re.compile(r'^(edx://)?(.+)$', re.IGNORECASE)
ALLOWED_ID_CHARS = r'[a-zA-Z0-9_\-~.]' ALLOWED_ID_CHARS = r'[a-zA-Z0-9_\-~.]'
URL_RE_SOURCE = r"""
(?P<tag>edx://)?
((?P<course_id>{ALLOWED_ID_CHARS}+)/?)?
({BRANCH_PREFIX}(?P<branch>{ALLOWED_ID_CHARS}+)/?)?
({VERSION_PREFIX}(?P<version_guid>[A-F0-9]+)/?)?
({BLOCK_PREFIX}(?P<block>{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)
def parse_url(string, tag_optional=False): def parse_url(string, tag_optional=False):
""" """
A url usually begins with 'edx://' (case-insensitive match), A url usually begins with 'edx://' (case-insensitive match),
...@@ -21,9 +32,9 @@ def parse_url(string, tag_optional=False): ...@@ -21,9 +32,9 @@ def parse_url(string, tag_optional=False):
Examples: Examples:
'edx://version/0123FFFF' 'edx://version/0123FFFF'
'edx://mit.eecs.6002x' 'edx://mit.eecs.6002x'
'edx://mit.eecs.6002x;published' 'edx://mit.eecs.6002x/branch/published'
'edx://mit.eecs.6002x;published/block/HW3' 'edx://mit.eecs.6002x/branch/published/block/HW3'
'edx://mit.eecs.6002x;published/version/000eee12345/block/HW3' 'edx://mit.eecs.6002x/branch/published/version/000eee12345/block/HW3'
This returns None if string cannot be parsed. This returns None if string cannot be parsed.
...@@ -37,12 +48,10 @@ def parse_url(string, tag_optional=False): ...@@ -37,12 +48,10 @@ def parse_url(string, tag_optional=False):
match = URL_RE.match(string) match = URL_RE.match(string)
if not match: if not match:
return None return None
if match.group(1) is None and not tag_optional: matched_dict = match.groupdict()
if matched_dict['tag'] is None and not tag_optional:
return None return None
path = match.group(2) return matched_dict
if path.startswith(URL_VERSION_PREFIX):
return parse_guid(path[len(URL_VERSION_PREFIX):])
return parse_course_id(path)
BLOCK_RE = re.compile(r'^' + ALLOWED_ID_CHARS + r'+$', re.IGNORECASE) BLOCK_RE = re.compile(r'^' + ALLOWED_ID_CHARS + r'+$', re.IGNORECASE)
...@@ -60,34 +69,6 @@ def parse_block_ref(string): ...@@ -60,34 +69,6 @@ def parse_block_ref(string):
return None return None
GUID_RE = re.compile(
r'^(?P<version_guid>[A-F0-9]+)(' + BLOCK_PREFIX + '(?P<block>' + ALLOWED_ID_CHARS + r'+))?$',
re.IGNORECASE
)
def parse_guid(string):
"""
A version_guid is a string of hex digits (0-F).
If string is a version_guid, returns a dict with key 'version_guid' and the value,
otherwise returns None.
"""
m = GUID_RE.match(string)
if m is not None:
return m.groupdict()
else:
return None
COURSE_ID_RE = re.compile(
r'^(?P<id>' + ALLOWED_ID_CHARS + r'+)(' +
BRANCH_PREFIX + r'(?P<branch>' + ALLOWED_ID_CHARS + r'+))?(' +
VERSION_PREFIX + r'(?P<version_guid>[A-F0-9]+))?(' +
BLOCK_PREFIX + r'(?P<block>' + ALLOWED_ID_CHARS + r'+))?$', re.IGNORECASE
)
def parse_course_id(string): def parse_course_id(string):
r""" r"""
...@@ -122,7 +103,7 @@ def parse_course_id(string): ...@@ -122,7 +103,7 @@ def parse_course_id(string):
Block is optional: if missing returned_dict['block'] is None. Block is optional: if missing returned_dict['block'] is None.
Else returns None. Else returns None.
""" """
match = COURSE_ID_RE.match(string) match = URL_RE.match(string)
if not match: if not match:
return None return None
return match.groupdict() return match.groupdict()
...@@ -234,7 +234,7 @@ class SplitMongoModuleStore(ModuleStoreBase): ...@@ -234,7 +234,7 @@ class SplitMongoModuleStore(ModuleStoreBase):
version_guid = index['versions'][course_locator.branch] version_guid = index['versions'][course_locator.branch]
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, CourseLocator(course_locator, version_guid=version_guid)) raise VersionConflictError(course_locator, version_guid)
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
...@@ -1005,7 +1005,14 @@ class SplitMongoModuleStore(ModuleStoreBase): ...@@ -1005,7 +1005,14 @@ class SplitMongoModuleStore(ModuleStoreBase):
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(BlockUsageLocator(xblock.location, version_guid=new_id)) return self.get_item(
BlockUsageLocator(
course_id=xblock.location.course_id,
usage_id=xblock.location.usage_id,
branch=xblock.location.branch,
version_guid=new_id
)
)
else: else:
return xblock return xblock
...@@ -1364,10 +1371,8 @@ class SplitMongoModuleStore(ModuleStoreBase): ...@@ -1364,10 +1371,8 @@ class SplitMongoModuleStore(ModuleStoreBase):
else: else:
raise VersionConflictError( raise VersionConflictError(
locator, locator,
CourseLocator( index_entry['versions'][locator.branch]
course_id=index_entry['_id'], )
version_guid=index_entry['versions'][locator.branch],
branch=locator.branch))
def _version_structure(self, structure, user_id): def _version_structure(self, structure, user_id):
""" """
......
...@@ -247,7 +247,8 @@ class TestLocationMapper(unittest.TestCase): ...@@ -247,7 +247,8 @@ class TestLocationMapper(unittest.TestCase):
new_style_course_id = '{}.geek_dept.{}.baz_run'.format(org, course) new_style_course_id = '{}.geek_dept.{}.baz_run'.format(org, course)
prob_locator = BlockUsageLocator( prob_locator = BlockUsageLocator(
course_id=new_style_course_id, course_id=new_style_course_id,
usage_id='problem2' usage_id='problem2',
branch='published'
) )
prob_location = loc_mapper().translate_locator_to_location(prob_locator) prob_location = loc_mapper().translate_locator_to_location(prob_locator)
self.assertIsNone(prob_location, 'found entry in empty map table') self.assertIsNone(prob_location, 'found entry in empty map table')
...@@ -265,7 +266,9 @@ class TestLocationMapper(unittest.TestCase): ...@@ -265,7 +266,9 @@ class TestLocationMapper(unittest.TestCase):
# default branch # default branch
self.assertEqual(prob_location, Location('i4x', org, course, 'problem', 'abc123', None)) self.assertEqual(prob_location, Location('i4x', org, course, 'problem', 'abc123', None))
# explicit branch # explicit branch
prob_locator = BlockUsageLocator(prob_locator, branch='draft') prob_locator = BlockUsageLocator(
course_id=prob_locator.course_id, branch='draft', usage_id=prob_locator.usage_id
)
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('i4x', org, course, 'problem', 'abc123', 'draft')) self.assertEqual(prob_location, Location('i4x', org, course, 'problem', 'abc123', 'draft'))
prob_locator = BlockUsageLocator( prob_locator = BlockUsageLocator(
...@@ -276,12 +279,13 @@ class TestLocationMapper(unittest.TestCase): ...@@ -276,12 +279,13 @@ class TestLocationMapper(unittest.TestCase):
# 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(
course_id=new_style_course_id, course_id=new_style_course_id,
usage_id='chapter48f' usage_id='chapter48f',
branch='production'
) )
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('i4x', org, course, 'chapter', '48f23a10395384929234')) self.assertEqual(chap_location, Location('i4x', org, course, 'chapter', '48f23a10395384929234'))
# explicit branch # explicit branch
chap_locator = BlockUsageLocator(chap_locator, branch='draft') chap_locator.branch = 'draft'
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('i4x', org, course, 'chapter', '48f23a10395384929234')) self.assertEqual(chap_location, Location('i4x', org, course, 'chapter', '48f23a10395384929234'))
chap_locator = BlockUsageLocator( chap_locator = BlockUsageLocator(
...@@ -382,7 +386,7 @@ class TestLocationMapper(unittest.TestCase): ...@@ -382,7 +386,7 @@ class TestLocationMapper(unittest.TestCase):
self.assertEqual(new_usage_id, new_usage_id2) self.assertEqual(new_usage_id, new_usage_id2)
# it should be in the distractor now # it should be in the distractor now
new_location = loc_mapper().translate_locator_to_location( new_location = loc_mapper().translate_locator_to_location(
BlockUsageLocator(course_id=new_style_course_id, usage_id=new_usage_id2) BlockUsageLocator(course_id=new_style_course_id, usage_id=new_usage_id2, branch='published')
) )
self.assertEqual(new_location, location) self.assertEqual(new_location, location)
# add one close to the existing chapter (cause name collision) # add one close to the existing chapter (cause name collision)
...@@ -391,11 +395,15 @@ class TestLocationMapper(unittest.TestCase): ...@@ -391,11 +395,15 @@ class TestLocationMapper(unittest.TestCase):
self.assertRegexpMatches(new_usage_id, r'^chapter48f\d') self.assertRegexpMatches(new_usage_id, r'^chapter48f\d')
# retrievable from both courses # retrievable from both courses
new_location = loc_mapper().translate_locator_to_location( new_location = loc_mapper().translate_locator_to_location(
BlockUsageLocator(course_id=new_style_course_id, usage_id=new_usage_id) BlockUsageLocator(course_id=new_style_course_id, usage_id=new_usage_id, branch='published')
) )
self.assertEqual(new_location, location) self.assertEqual(new_location, location)
new_location = loc_mapper().translate_locator_to_location( new_location = loc_mapper().translate_locator_to_location(
BlockUsageLocator(course_id='{}.{}.{}'.format(org, course, 'baz_run'), usage_id=new_usage_id) BlockUsageLocator(
course_id='{}.{}.{}'.format(org, course, 'baz_run'),
usage_id=new_usage_id,
branch='published'
)
) )
self.assertEqual(new_location, location) self.assertEqual(new_location, location)
...@@ -443,11 +451,11 @@ class TestLocationMapper(unittest.TestCase): ...@@ -443,11 +451,11 @@ class TestLocationMapper(unittest.TestCase):
# change in all courses to same value # change in all courses to same value
loc_mapper().update_block_location_translator(location, 'problem1') loc_mapper().update_block_location_translator(location, 'problem1')
trans_loc = loc_mapper().translate_locator_to_location( trans_loc = loc_mapper().translate_locator_to_location(
BlockUsageLocator(course_id=new_style_course_id, usage_id='problem1') BlockUsageLocator(course_id=new_style_course_id, usage_id='problem1', branch='published')
) )
self.assertEqual(trans_loc, location) self.assertEqual(trans_loc, location)
trans_loc = loc_mapper().translate_locator_to_location( trans_loc = loc_mapper().translate_locator_to_location(
BlockUsageLocator(course_id=new_style_course_id2, usage_id='problem1') BlockUsageLocator(course_id=new_style_course_id2, usage_id='problem1', branch='published')
) )
self.assertEqual(trans_loc, location) self.assertEqual(trans_loc, location)
# try to change to overwrite used usage_id # try to change to overwrite used usage_id
...@@ -457,12 +465,12 @@ class TestLocationMapper(unittest.TestCase): ...@@ -457,12 +465,12 @@ class TestLocationMapper(unittest.TestCase):
# just change the one course # just change the one course
loc_mapper().update_block_location_translator(location, 'chapter2', '{}/{}/{}'.format(org, course, 'baz_run')) loc_mapper().update_block_location_translator(location, 'chapter2', '{}/{}/{}'.format(org, course, 'baz_run'))
trans_loc = loc_mapper().translate_locator_to_location( trans_loc = loc_mapper().translate_locator_to_location(
BlockUsageLocator(course_id=new_style_course_id, usage_id='chapter2') BlockUsageLocator(course_id=new_style_course_id, usage_id='chapter2', branch='published')
) )
self.assertEqual(trans_loc.name, '48f23a10395384929234') self.assertEqual(trans_loc.name, '48f23a10395384929234')
# but this still points to the old # but this still points to the old
trans_loc = loc_mapper().translate_locator_to_location( trans_loc = loc_mapper().translate_locator_to_location(
BlockUsageLocator(course_id=new_style_course_id2, usage_id='chapter2') BlockUsageLocator(course_id=new_style_course_id2, usage_id='chapter2', branch='published')
) )
self.assertEqual(trans_loc.name, '1') self.assertEqual(trans_loc.name, '1')
...@@ -496,10 +504,10 @@ class TestLocationMapper(unittest.TestCase): ...@@ -496,10 +504,10 @@ class TestLocationMapper(unittest.TestCase):
# delete from all courses # delete from all courses
loc_mapper().delete_block_location_translator(location) loc_mapper().delete_block_location_translator(location)
self.assertIsNone(loc_mapper().translate_locator_to_location( self.assertIsNone(loc_mapper().translate_locator_to_location(
BlockUsageLocator(course_id=new_style_course_id, usage_id='problem1') BlockUsageLocator(course_id=new_style_course_id, usage_id='problem1', branch='published')
)) ))
self.assertIsNone(loc_mapper().translate_locator_to_location( self.assertIsNone(loc_mapper().translate_locator_to_location(
BlockUsageLocator(course_id=new_style_course_id2, usage_id='problem2') BlockUsageLocator(course_id=new_style_course_id2, usage_id='problem2', branch='published')
)) ))
# delete from one course # delete from one course
location = location.replace(name='abc123') location = location.replace(name='abc123')
......
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