Commit 484c529d by Calen Pennington

Merge remote-tracking branch 'edx/opaque-keys' into opaque-keys-merge-master

parents 37b1a2f7 741cdf95
...@@ -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')
......
...@@ -118,4 +118,9 @@ def get_cached_content(location): ...@@ -118,4 +118,9 @@ def get_cached_content(location):
def del_cached_content(location): def del_cached_content(location):
cache.delete(unicode(location).encode("utf-8")) # delete content for the given location, as well as for content with run=None.
# it's possible that the content could have been cached without knowing the
# course_key - and so without having the run.
cache.delete_many(
[unicode(loc).encode("utf-8") for loc in [location, location.replace(run=None)]]
)
...@@ -9,6 +9,7 @@ from opaque_keys import InvalidKeyError ...@@ -9,6 +9,7 @@ from opaque_keys import InvalidKeyError
import bson.son import bson.son
import logging import logging
from django.db.models.query_utils import Q from django.db.models.query_utils import Q
from django.db.utils import IntegrityError
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -28,69 +29,72 @@ class Migration(DataMigration): ...@@ -28,69 +29,72 @@ class Migration(DataMigration):
loc_map_collection = loc_mapper().location_map loc_map_collection = loc_mapper().location_map
# b/c the Groups table had several entries for each course, we need to ensure we process each unique # b/c the Groups table had several entries for each course, we need to ensure we process each unique
# course only once. The below datastructures help ensure that. # course only once. The below datastructures help ensure that.
hold = {} hold = {} # key of course_id_strings with array of group objects. Should only be org scoped entries
done = set() # or deleted courses
orgs = {} orgs = {} # downcased org to last recorded normal case of the org
query = Q(name__startswith='course_creator_group') query = Q(name='course_creator_group')
for role in ['staff', 'instructor', 'beta_testers', ]: for role in ['staff', 'instructor', 'beta_testers', ]:
query = query | Q(name__startswith=role) query = query | Q(name__startswith=role)
for group in orm['auth.Group'].objects.filter(query).all(): for group in orm['auth.Group'].objects.filter(query).all():
def _migrate_users(correct_course_key, lower_org): def _migrate_users(correct_course_key, role, lower_org):
""" """
Get all the users from the old group and migrate to this course key in the new table Get all the users from the old group and migrate to this course key in the new table
""" """
for user in orm['auth.user'].objects.filter(groups=group).all(): for user in orm['auth.user'].objects.filter(groups=group).all():
entry = orm['student.courseaccessrole']( entry = orm['student.courseaccessrole'](
role=parsed_entry.group('role_id'), user=user, role=role, user=user,
org=correct_course_key.org, course_id=correct_course_key.to_deprecated_string() org=correct_course_key.org, course_id=correct_course_key
) )
try:
entry.save() entry.save()
except IntegrityError:
# already stored
pass
orgs[lower_org] = correct_course_key.org orgs[lower_org] = correct_course_key.org
done.add(correct_course_key)
# should this actually loop through all groups and log any which are not compliant? That is,
# remove the above filter
parsed_entry = self.GROUP_ENTRY_RE.match(group.name) parsed_entry = self.GROUP_ENTRY_RE.match(group.name)
if parsed_entry.group('role_id') == 'course_creator_group': role = parsed_entry.group('role_id')
if role == 'course_creator_group':
for user in orm['auth.user'].objects.filter(groups=group).all(): for user in orm['auth.user'].objects.filter(groups=group).all():
entry = orm['student.courseaccessrole'](role=parsed_entry.group('role_id'), user=user) entry = orm['student.courseaccessrole'](role=role, user=user)
entry.save() entry.save()
else: else:
course_id_string = parsed_entry.group('course_id_string') course_id_string = parsed_entry.group('course_id_string')
try: try:
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id_string) course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id_string)
if course_key not in done: # course_key is the downcased version, get the normal cased one. loc_mapper() has no
# is the downcased version, get the normal cased one. loc_mapper() has no
# methods taking downcased SSCK; so, need to do it manually here # methods taking downcased SSCK; so, need to do it manually here
correct_course_key = self._map_downcased_ssck(course_key, loc_map_collection, done) correct_course_key = self._map_downcased_ssck(course_key, loc_map_collection)
_migrate_users(correct_course_key, course_key.org) if correct_course_key is not None:
done.add(course_key) _migrate_users(correct_course_key, role, course_key.org)
except InvalidKeyError: except InvalidKeyError:
entry = loc_map_collection.find_one({ entry = loc_map_collection.find_one({
'course_id': re.compile(r'^{}$'.format(course_id_string), re.IGNORECASE) 'course_id': re.compile(r'^{}$'.format(course_id_string), re.IGNORECASE)
}) })
if entry is None: if entry is None:
# not a course_id as far as we can tell hold.setdefault(course_id_string, []).append(group)
if course_id_string not in done:
hold[course_id_string] = group
else: else:
correct_course_key = self._cache_done_return_ssck(entry, done) correct_course_key = SlashSeparatedCourseKey(*entry['_id'].values())
_migrate_users(correct_course_key, entry['lower_id']['org']) _migrate_users(correct_course_key, role, entry['lower_id']['org'])
# see if any in hold ere missed above # see if any in hold were missed above
for not_ssck, group in hold.iteritems(): for held_auth_scope, groups in hold.iteritems():
if not_ssck not in done: # orgs indexed by downcased org
if not_ssck in orgs: held_auth_scope = held_auth_scope.lower()
if held_auth_scope in orgs:
for group in groups:
role = self.GROUP_ENTRY_RE.match(group.name).group('role_id')
# they have org permission # they have org permission
for user in orm['auth.user'].objects.filter(groups=group).all(): for user in orm['auth.user'].objects.filter(groups=group).all():
entry = orm['student.courseaccessrole']( entry = orm['student.courseaccessrole'](
role=parsed_entry.group('role_id'), user=user, role=role,
org=orgs[not_ssck], user=user,
org=orgs[held_auth_scope],
) )
entry.save() entry.save()
else: else:
# should this just log or really make an effort to do the conversion? # don't silently skip unexpected roles
log.warn("Didn't convert role %s", group.name) log.warn("Didn't convert roles %s", [group.name for group in groups])
def backwards(self, orm): def backwards(self, orm):
"Write your backwards methods here." "Write your backwards methods here."
...@@ -98,10 +102,9 @@ class Migration(DataMigration): ...@@ -98,10 +102,9 @@ class Migration(DataMigration):
# the semantic of backwards should be other than perhaps clearing the table. # the semantic of backwards should be other than perhaps clearing the table.
orm['student.courseaccessrole'].objects.all().delete() orm['student.courseaccessrole'].objects.all().delete()
def _map_downcased_ssck(self, downcased_ssck, loc_map_collection, done): def _map_downcased_ssck(self, downcased_ssck, loc_map_collection):
""" """
Get the normal cased version of this downcased slash sep course key and add Get the normal cased version of this downcased slash sep course key
the lowercased locator form to done map
""" """
# given the regex, the son may be an overkill # given the regex, the son may be an overkill
course_son = bson.son.SON([ course_son = bson.son.SON([
...@@ -111,25 +114,11 @@ class Migration(DataMigration): ...@@ -111,25 +114,11 @@ class Migration(DataMigration):
]) ])
entry = loc_map_collection.find_one(course_son) entry = loc_map_collection.find_one(course_son)
if entry: if entry:
return self._cache_done_return_ssck(entry, done) idpart = entry['_id']
return SlashSeparatedCourseKey(idpart['org'], idpart['course'], idpart['name'])
else: else:
return None return None
def _cache_done_return_ssck(self, entry, done):
"""
Add all the various formats which auth may use to the done set and return the ssck for the entry
"""
# cache that the dotted form is done too
if 'lower_course_id' in entry:
done.add(entry['lower_course_id'])
elif 'course_id' in entry:
done.add(entry['course_id'].lower())
elif 'lower_org' in entry:
done.add('{}.{}'.format(entry['lower_org'], entry['lower_offering']))
else:
done.add('{}.{}'.format(entry['org'].lower(), entry['offering'].lower()))
return SlashSeparatedCourseKey(*entry['_id'].values())
models = { models = {
'auth.group': { 'auth.group': {
......
...@@ -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']: If it can be parsed as a org + offering, returns a dict
parse['version_guid'] = cls.as_object_id(parse['version_guid']) with key 'id' and optional keys 'branch' and 'version_guid'.
"""
return parse 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)
if not parse:
raise InvalidKeyError(cls, block_ref) 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 = BlockUsageLocator.from_string(testurn) testobj = UsageKey.from_string(testurn)
self.check_block_locn_fields(testobj, self.check_block_locn_fields(
testobj,
org=expected_org, org=expected_org,
offering=expected_offering, offering=expected_offering,
branch=expected_branch, branch=expected_branch,
block=expected_block_ref) block_type='problem',
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:
......
...@@ -37,7 +37,7 @@ class ControllerQueryService(GradingService): ...@@ -37,7 +37,7 @@ class ControllerQueryService(GradingService):
def check_combined_notifications(self, course_id, student_id, user_is_staff, last_time_viewed): def check_combined_notifications(self, course_id, student_id, user_is_staff, last_time_viewed):
params = { params = {
'student_id': student_id, 'student_id': student_id,
'course_id': course_id, 'course_id': course_id.to_deprecated_string(),
'user_is_staff': user_is_staff, 'user_is_staff': user_is_staff,
'last_time_viewed': last_time_viewed, 'last_time_viewed': last_time_viewed,
} }
......
...@@ -85,7 +85,7 @@ class PeerGradingService(GradingService): ...@@ -85,7 +85,7 @@ class PeerGradingService(GradingService):
return result return result
def get_problem_list(self, course_id, grader_id): def get_problem_list(self, course_id, grader_id):
params = {'course_id': course_id, 'student_id': grader_id} params = {'course_id': course_id.to_deprecated_string(), 'student_id': grader_id}
result = self.get(self.get_problem_list_url, params) result = self.get(self.get_problem_list_url, params)
if 'problem_list' in result: if 'problem_list' in result:
...@@ -100,7 +100,7 @@ class PeerGradingService(GradingService): ...@@ -100,7 +100,7 @@ class PeerGradingService(GradingService):
return result return result
def get_notifications(self, course_id, grader_id): def get_notifications(self, course_id, grader_id):
params = {'course_id': course_id, 'student_id': grader_id} params = {'course_id': course_id.to_deprecated_string(), 'student_id': grader_id}
result = self.get(self.get_notifications_url, params) result = self.get(self.get_notifications_url, params)
self._record_result( self._record_result(
'get_notifications', 'get_notifications',
......
...@@ -182,7 +182,7 @@ def _create_comment(request, course_key, thread_id=None, parent_id=None): ...@@ -182,7 +182,7 @@ def _create_comment(request, course_key, thread_id=None, parent_id=None):
'anonymous': anonymous, 'anonymous': anonymous,
'anonymous_to_peers': anonymous_to_peers, 'anonymous_to_peers': anonymous_to_peers,
'user_id': request.user.id, 'user_id': request.user.id,
'course_id': course_key, 'course_id': course_key.to_deprecated_string(),
'thread_id': thread_id, 'thread_id': thread_id,
'parent_id': parent_id, 'parent_id': parent_id,
}) })
......
...@@ -285,7 +285,7 @@ class UserProfileTestCase(ModuleStoreTestCase): ...@@ -285,7 +285,7 @@ class UserProfileTestCase(ModuleStoreTestCase):
StringEndsWithMatcher('/users/{}/active_threads'.format(self.profiled_user.id)), StringEndsWithMatcher('/users/{}/active_threads'.format(self.profiled_user.id)),
data=None, data=None,
params=PartialDictMatcher({ params=PartialDictMatcher({
"course_id": self.course.id, "course_id": self.course.id.to_deprecated_string(),
"page": params.get("page", 1), "page": params.get("page", 1),
"per_page": views.THREADS_PER_PAGE "per_page": views.THREADS_PER_PAGE
}), }),
......
...@@ -9,7 +9,6 @@ from django.conf import settings ...@@ -9,7 +9,6 @@ from django.conf import settings
from django.http import HttpResponse, Http404 from django.http import HttpResponse, Http404
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from xmodule.course_module import CourseDescriptor
from xmodule.modulestore.locations import SlashSeparatedCourseKey from xmodule.modulestore.locations import SlashSeparatedCourseKey
from xmodule.open_ended_grading_classes.grading_service_module import GradingService, GradingServiceError from xmodule.open_ended_grading_classes.grading_service_module import GradingService, GradingServiceError
from xmodule.modulestore.django import ModuleI18nService from xmodule.modulestore.django import ModuleI18nService
...@@ -116,7 +115,7 @@ class StaffGradingService(GradingService): ...@@ -116,7 +115,7 @@ class StaffGradingService(GradingService):
Raises: Raises:
GradingServiceError: something went wrong with the connection. GradingServiceError: something went wrong with the connection.
""" """
params = {'course_id': course_id, 'grader_id': grader_id} params = {'course_id': course_id.to_deprecated_string(), 'grader_id': grader_id}
result = self.get(self.get_problem_list_url, params) result = self.get(self.get_problem_list_url, params)
tags = [u'course_id:{}'.format(course_id)] tags = [u'course_id:{}'.format(course_id)]
self._record_result('get_problem_list', result, tags) self._record_result('get_problem_list', result, tags)
...@@ -148,7 +147,7 @@ class StaffGradingService(GradingService): ...@@ -148,7 +147,7 @@ class StaffGradingService(GradingService):
self.get( self.get(
self.get_next_url, self.get_next_url,
params={ params={
'location': location, 'location': location.to_deprecated_string(),
'grader_id': grader_id 'grader_id': grader_id
} }
) )
...@@ -170,7 +169,7 @@ class StaffGradingService(GradingService): ...@@ -170,7 +169,7 @@ class StaffGradingService(GradingService):
Raises: Raises:
GradingServiceError if there's a problem connecting. GradingServiceError if there's a problem connecting.
""" """
data = {'course_id': course_id, data = {'course_id': course_id.to_deprecated_string(),
'submission_id': submission_id, 'submission_id': submission_id,
'score': score, 'score': score,
'feedback': feedback, 'feedback': feedback,
...@@ -186,7 +185,7 @@ class StaffGradingService(GradingService): ...@@ -186,7 +185,7 @@ class StaffGradingService(GradingService):
return result return result
def get_notifications(self, course_id): def get_notifications(self, course_id):
params = {'course_id': course_id} params = {'course_id': course_id.to_deprecated_string()}
result = self.get(self.get_notifications_url, params) result = self.get(self.get_notifications_url, params)
tags = [ tags = [
u'course_id:{}'.format(course_id), u'course_id:{}'.format(course_id),
...@@ -274,7 +273,7 @@ def get_next(request, course_id): ...@@ -274,7 +273,7 @@ def get_next(request, course_id):
', '.join(missing))) ', '.join(missing)))
grader_id = unique_id_for_user(request.user) grader_id = unique_id_for_user(request.user)
p = request.POST p = request.POST
location = p['location'] location = course_key.make_usage_key_from_deprecated_string(p['location'])
return HttpResponse(json.dumps(_get_next(course_key, grader_id, location)), return HttpResponse(json.dumps(_get_next(course_key, grader_id, location)),
mimetype="application/json") mimetype="application/json")
...@@ -400,7 +399,7 @@ def save_grade(request, course_id): ...@@ -400,7 +399,7 @@ def save_grade(request, course_id):
grader_id = unique_id_for_user(request.user) grader_id = unique_id_for_user(request.user)
location = p['location'] location = course_key.make_usage_key_from_deprecated_string(p['location'])
try: try:
result = staff_grading_service().save_grade(course_key, result = staff_grading_service().save_grade(course_key,
......
...@@ -109,13 +109,13 @@ class TestStaffGradingService(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -109,13 +109,13 @@ class TestStaffGradingService(ModuleStoreTestCase, LoginEnrollmentTestCase):
self.student = 'view@test.com' self.student = 'view@test.com'
self.instructor = 'view2@test.com' self.instructor = 'view2@test.com'
self.password = 'foo' self.password = 'foo'
self.location = 'TestLocation'
self.create_account('u1', self.student, self.password) self.create_account('u1', self.student, self.password)
self.create_account('u2', self.instructor, self.password) self.create_account('u2', self.instructor, self.password)
self.activate_user(self.student) self.activate_user(self.student)
self.activate_user(self.instructor) self.activate_user(self.instructor)
self.course_id = SlashSeparatedCourseKey("edX", "toy", "2012_Fall") self.course_id = SlashSeparatedCourseKey("edX", "toy", "2012_Fall")
self.location_string = self.course_id.make_usage_key('html', 'TestLocation').to_deprecated_string()
self.toy = modulestore().get_course(self.course_id) self.toy = modulestore().get_course(self.course_id)
make_instructor(self.toy, self.instructor) make_instructor(self.toy, self.instructor)
...@@ -140,7 +140,7 @@ class TestStaffGradingService(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -140,7 +140,7 @@ class TestStaffGradingService(ModuleStoreTestCase, LoginEnrollmentTestCase):
self.login(self.instructor, self.password) self.login(self.instructor, self.password)
url = reverse('staff_grading_get_next', kwargs={'course_id': self.course_id.to_deprecated_string()}) url = reverse('staff_grading_get_next', kwargs={'course_id': self.course_id.to_deprecated_string()})
data = {'location': self.location} data = {'location': self.location_string}
response = check_for_post_code(self, 200, url, data) response = check_for_post_code(self, 200, url, data)
...@@ -165,7 +165,7 @@ class TestStaffGradingService(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -165,7 +165,7 @@ class TestStaffGradingService(ModuleStoreTestCase, LoginEnrollmentTestCase):
data = {'score': '12', data = {'score': '12',
'feedback': 'great!', 'feedback': 'great!',
'submission_id': '123', 'submission_id': '123',
'location': self.location, 'location': self.location_string,
'submission_flagged': "true", 'submission_flagged': "true",
'rubric_scores[]': ['1', '2']} 'rubric_scores[]': ['1', '2']}
if skip: if skip:
...@@ -227,7 +227,7 @@ class TestStaffGradingService(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -227,7 +227,7 @@ class TestStaffGradingService(ModuleStoreTestCase, LoginEnrollmentTestCase):
'score': '12', 'score': '12',
'feedback': '', 'feedback': '',
'submission_id': '123', 'submission_id': '123',
'location': self.location, 'location': self.location_string,
'submission_flagged': "false", 'submission_flagged': "false",
'rubric_scores[]': ['1', '2'] 'rubric_scores[]': ['1', '2']
} }
...@@ -262,13 +262,13 @@ class TestPeerGradingService(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -262,13 +262,13 @@ class TestPeerGradingService(ModuleStoreTestCase, LoginEnrollmentTestCase):
self.student = 'view@test.com' self.student = 'view@test.com'
self.instructor = 'view2@test.com' self.instructor = 'view2@test.com'
self.password = 'foo' self.password = 'foo'
self.location = 'TestLocation'
self.create_account('u1', self.student, self.password) self.create_account('u1', self.student, self.password)
self.create_account('u2', self.instructor, self.password) self.create_account('u2', self.instructor, self.password)
self.activate_user(self.student) self.activate_user(self.student)
self.activate_user(self.instructor) self.activate_user(self.instructor)
self.course_id = SlashSeparatedCourseKey("edX", "toy", "2012_Fall") self.course_id = SlashSeparatedCourseKey("edX", "toy", "2012_Fall")
self.location_string = self.course_id.make_usage_key('html', 'TestLocation').to_deprecated_string()
self.toy = modulestore().get_course(self.course_id) self.toy = modulestore().get_course(self.course_id)
location = "i4x://edX/toy/peergrading/init" location = "i4x://edX/toy/peergrading/init"
field_data = DictFieldData({'data': "<peergrading/>", 'location': location, 'category':'peergrading'}) field_data = DictFieldData({'data': "<peergrading/>", 'location': location, 'category':'peergrading'})
...@@ -292,7 +292,7 @@ class TestPeerGradingService(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -292,7 +292,7 @@ class TestPeerGradingService(ModuleStoreTestCase, LoginEnrollmentTestCase):
self.logout() self.logout()
def test_get_next_submission_success(self): def test_get_next_submission_success(self):
data = {'location': self.location} data = {'location': self.location_string}
response = self.peer_module.get_next_submission(data) response = self.peer_module.get_next_submission(data)
content = response content = response
...@@ -312,7 +312,7 @@ class TestPeerGradingService(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -312,7 +312,7 @@ class TestPeerGradingService(ModuleStoreTestCase, LoginEnrollmentTestCase):
def test_save_grade_success(self): def test_save_grade_success(self):
data = { data = {
'rubric_scores[]': [0, 0], 'rubric_scores[]': [0, 0],
'location': self.location, 'location': self.location_string,
'submission_id': 1, 'submission_id': 1,
'submission_key': 'fake key', 'submission_key': 'fake key',
'score': 2, 'score': 2,
...@@ -342,7 +342,7 @@ class TestPeerGradingService(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -342,7 +342,7 @@ class TestPeerGradingService(ModuleStoreTestCase, LoginEnrollmentTestCase):
self.assertTrue(d['error'].find('Missing required keys:') > -1) self.assertTrue(d['error'].find('Missing required keys:') > -1)
def test_is_calibrated_success(self): def test_is_calibrated_success(self):
data = {'location': self.location} data = {'location': self.location_string}
response = self.peer_module.is_student_calibrated(data) response = self.peer_module.is_student_calibrated(data)
self.assertTrue(response['success']) self.assertTrue(response['success'])
...@@ -355,7 +355,7 @@ class TestPeerGradingService(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -355,7 +355,7 @@ class TestPeerGradingService(ModuleStoreTestCase, LoginEnrollmentTestCase):
self.assertFalse('calibrated' in response) self.assertFalse('calibrated' in response)
def test_show_calibration_essay_success(self): def test_show_calibration_essay_success(self):
data = {'location': self.location} data = {'location': self.location_string}
response = self.peer_module.show_calibration_essay(data) response = self.peer_module.show_calibration_essay(data)
...@@ -376,7 +376,7 @@ class TestPeerGradingService(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -376,7 +376,7 @@ class TestPeerGradingService(ModuleStoreTestCase, LoginEnrollmentTestCase):
def test_save_calibration_essay_success(self): def test_save_calibration_essay_success(self):
data = { data = {
'rubric_scores[]': [0, 0], 'rubric_scores[]': [0, 0],
'location': self.location, 'location': self.location_string,
'submission_id': 1, 'submission_id': 1,
'submission_key': 'fake key', 'submission_key': 'fake key',
'score': 2, 'score': 2,
...@@ -410,7 +410,7 @@ class TestPeerGradingService(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -410,7 +410,7 @@ class TestPeerGradingService(ModuleStoreTestCase, LoginEnrollmentTestCase):
""" """
data = { data = {
'rubric_scores[]': [0, 0], 'rubric_scores[]': [0, 0],
'location': self.location, 'location': self.location_string,
'submission_id': 1, 'submission_id': 1,
'submission_key': 'fake key', 'submission_key': 'fake key',
'score': 2, 'score': 2,
......
import logging import logging
from django.conf import settings
from django.views.decorators.cache import cache_control from django.views.decorators.cache import cache_control
from edxmako.shortcuts import render_to_response from edxmako.shortcuts import render_to_response
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from student.models import unique_id_for_user
from courseware.courses import get_course_with_access from courseware.courses import get_course_with_access
from xmodule.open_ended_grading_classes.grading_service_module import GradingServiceError from xmodule.open_ended_grading_classes.grading_service_module import GradingServiceError
...@@ -20,11 +18,11 @@ from xmodule.modulestore import SlashSeparatedCourseKey ...@@ -20,11 +18,11 @@ from xmodule.modulestore import SlashSeparatedCourseKey
from xmodule.modulestore.exceptions import NoPathToItem from xmodule.modulestore.exceptions import NoPathToItem
from django.http import HttpResponse, Http404, HttpResponseRedirect from django.http import HttpResponse, Http404, HttpResponseRedirect
from edxmako.shortcuts import render_to_string
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from open_ended_grading.utils import (STAFF_ERROR_MESSAGE, STUDENT_ERROR_MESSAGE, from open_ended_grading.utils import (
StudentProblemList, generate_problem_url, create_controller_query_service) STAFF_ERROR_MESSAGE, StudentProblemList, generate_problem_url, create_controller_query_service
)
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -68,9 +66,10 @@ def staff_grading(request, course_id): ...@@ -68,9 +66,10 @@ def staff_grading(request, course_id):
""" """
Show the instructor grading interface. Show the instructor grading interface.
""" """
course = get_course_with_access(request.user, 'staff', course_id) course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
course = get_course_with_access(request.user, 'staff', course_key)
ajax_url = _reverse_with_slash('staff_grading', course_id) ajax_url = _reverse_with_slash('staff_grading', course_key)
return render_to_response('instructor/staff_grading.html', { return render_to_response('instructor/staff_grading.html', {
'course': course, 'course': course,
...@@ -118,9 +117,9 @@ def peer_grading(request, course_id): ...@@ -118,9 +117,9 @@ def peer_grading(request, course_id):
When a student clicks on the "peer grading" button in the open ended interface, link them to a peer grading When a student clicks on the "peer grading" button in the open ended interface, link them to a peer grading
xmodule in the course. xmodule in the course.
''' '''
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
#Get the current course #Get the current course
course = get_course_with_access(request.user, 'load', course_id) course = get_course_with_access(request.user, 'load', course_key)
found_module, problem_url = find_peer_grading_module(course) found_module, problem_url = find_peer_grading_module(course)
if not found_module: if not found_module:
...@@ -187,13 +186,11 @@ def flagged_problem_list(request, course_id): ...@@ -187,13 +186,11 @@ def flagged_problem_list(request, course_id):
''' '''
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
course = get_course_with_access(request.user, 'staff', course_key) course = get_course_with_access(request.user, 'staff', course_key)
student_id = unique_id_for_user(request.user)
# call problem list service # call problem list service
success = False success = False
error_text = "" error_text = ""
problem_list = [] problem_list = []
base_course_url = reverse('courses')
# Make a service that can query edX ORA. # Make a service that can query edX ORA.
controller_qs = create_controller_query_service() controller_qs = create_controller_query_service()
......
...@@ -86,7 +86,7 @@ class User(models.Model): ...@@ -86,7 +86,7 @@ class User(models.Model):
if not self.course_id: if not self.course_id:
raise CommentClientRequestError("Must provide course_id when retrieving active threads for the user") raise CommentClientRequestError("Must provide course_id when retrieving active threads for the user")
url = _url_for_user_active_threads(self.id) url = _url_for_user_active_threads(self.id)
params = {'course_id': self.course_id} params = {'course_id': self.course_id.to_deprecated_string()}
params = merge_dict(params, query_params) params = merge_dict(params, query_params)
response = perform_request( response = perform_request(
'get', 'get',
...@@ -102,7 +102,7 @@ class User(models.Model): ...@@ -102,7 +102,7 @@ class User(models.Model):
if not self.course_id: if not self.course_id:
raise CommentClientRequestError("Must provide course_id when retrieving subscribed threads for the user") raise CommentClientRequestError("Must provide course_id when retrieving subscribed threads for the user")
url = _url_for_user_subscribed_threads(self.id) url = _url_for_user_subscribed_threads(self.id)
params = {'course_id': self.course_id} params = {'course_id': self.course_id.to_deprecated_string()}
params = merge_dict(params, query_params) params = merge_dict(params, query_params)
response = perform_request( response = perform_request(
'get', 'get',
...@@ -118,7 +118,7 @@ class User(models.Model): ...@@ -118,7 +118,7 @@ class User(models.Model):
url = self.url(action='get', params=self.attributes) url = self.url(action='get', params=self.attributes)
retrieve_params = self.default_retrieve_params retrieve_params = self.default_retrieve_params
if self.attributes.get('course_id'): if self.attributes.get('course_id'):
retrieve_params['course_id'] = self.course_id retrieve_params['course_id'] = self.course_id.to_deprecated_string()
try: try:
response = perform_request( response = perform_request(
'get', 'get',
......
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