Merge remote-tracking branch 'edx/opaque-keys' into opaque-keys-merge-master
This commit is contained in:
@@ -12,7 +12,6 @@ from xmodule.html_module import HtmlDescriptor
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
|
||||
|
||||
@unittest.skip("Not fixing split until we land opaque-keys 0.9")
|
||||
class TemplateTests(unittest.TestCase):
|
||||
"""
|
||||
Test finding and using the templates (boilerplates) for xblocks.
|
||||
@@ -55,25 +54,25 @@ class TemplateTests(unittest.TestCase):
|
||||
|
||||
def test_factories(self):
|
||||
test_course = persistent_factories.PersistentCourseFactory.create(
|
||||
course_id='testx.tempcourse', org='testx',
|
||||
offering='tempcourse', org='testx',
|
||||
display_name='fun test course', user_id='testbot'
|
||||
)
|
||||
self.assertIsInstance(test_course, CourseDescriptor)
|
||||
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['_id'], 'testx.tempcourse')
|
||||
self.assertEqual(index_info['offering'], 'tempcourse')
|
||||
|
||||
test_chapter = persistent_factories.ItemFactory.create(display_name='chapter 1',
|
||||
parent_location=test_course.location)
|
||||
self.assertIsInstance(test_chapter, SequenceDescriptor)
|
||||
# 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)
|
||||
|
||||
with self.assertRaises(DuplicateCourseError):
|
||||
persistent_factories.PersistentCourseFactory.create(
|
||||
course_id='testx.tempcourse', org='testx',
|
||||
offering='tempcourse', org='testx',
|
||||
display_name='fun test course', user_id='testbot'
|
||||
)
|
||||
|
||||
@@ -82,7 +81,7 @@ class TemplateTests(unittest.TestCase):
|
||||
Test create_xblock to create non persisted xblocks
|
||||
"""
|
||||
test_course = persistent_factories.PersistentCourseFactory.create(
|
||||
course_id='testx.tempcourse', org='testx',
|
||||
offering='tempcourse', org='testx',
|
||||
display_name='fun test course', user_id='testbot'
|
||||
)
|
||||
|
||||
@@ -109,7 +108,7 @@ class TemplateTests(unittest.TestCase):
|
||||
try saving temporary xblocks
|
||||
"""
|
||||
test_course = persistent_factories.PersistentCourseFactory.create(
|
||||
course_id='testx.tempcourse', org='testx',
|
||||
offering='tempcourse', org='testx',
|
||||
display_name='fun test course', user_id='testbot'
|
||||
)
|
||||
test_chapter = modulestore('split').create_xblock(
|
||||
@@ -148,7 +147,7 @@ class TemplateTests(unittest.TestCase):
|
||||
|
||||
def test_delete_course(self):
|
||||
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',
|
||||
user_id='testbot')
|
||||
persistent_factories.ItemFactory.create(display_name='chapter 1',
|
||||
@@ -171,7 +170,7 @@ class TemplateTests(unittest.TestCase):
|
||||
Test get_block_generations
|
||||
"""
|
||||
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',
|
||||
user_id='testbot'
|
||||
)
|
||||
@@ -193,7 +192,9 @@ class TemplateTests(unittest.TestCase):
|
||||
|
||||
second_problem = persistent_factories.ItemFactory.create(
|
||||
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',
|
||||
data="<problem></problem>"
|
||||
)
|
||||
|
||||
@@ -7,7 +7,7 @@ import lxml
|
||||
from contentstore.tests.utils import CourseTestCase
|
||||
from contentstore.utils import reverse_course_url
|
||||
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
|
||||
from xmodule.modulestore import parsers
|
||||
from xmodule.modulestore.locator import Locator
|
||||
|
||||
|
||||
class TestCourseIndex(CourseTestCase):
|
||||
@@ -38,7 +38,7 @@ class TestCourseIndex(CourseTestCase):
|
||||
for link in course_link_eles:
|
||||
self.assertRegexpMatches(
|
||||
link.get("href"),
|
||||
'course/slashes:{0}'.format(parsers.ALLOWED_ID_CHARS)
|
||||
'course/slashes:{0}'.format(Locator.ALLOWED_ID_CHARS)
|
||||
)
|
||||
# now test that url
|
||||
outline_response = authed_client.get(link.get("href"), {}, HTTP_ACCEPT='text/html')
|
||||
|
||||
@@ -118,4 +118,9 @@ def get_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
|
||||
import bson.son
|
||||
import logging
|
||||
from django.db.models.query_utils import Q
|
||||
from django.db.utils import IntegrityError
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -28,69 +29,72 @@ class Migration(DataMigration):
|
||||
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
|
||||
# course only once. The below datastructures help ensure that.
|
||||
hold = {}
|
||||
done = set()
|
||||
orgs = {}
|
||||
query = Q(name__startswith='course_creator_group')
|
||||
hold = {} # key of course_id_strings with array of group objects. Should only be org scoped entries
|
||||
# or deleted courses
|
||||
orgs = {} # downcased org to last recorded normal case of the org
|
||||
query = Q(name='course_creator_group')
|
||||
for role in ['staff', 'instructor', 'beta_testers', ]:
|
||||
query = query | Q(name__startswith=role)
|
||||
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
|
||||
"""
|
||||
for user in orm['auth.user'].objects.filter(groups=group).all():
|
||||
entry = orm['student.courseaccessrole'](
|
||||
role=parsed_entry.group('role_id'), user=user,
|
||||
org=correct_course_key.org, course_id=correct_course_key.to_deprecated_string()
|
||||
role=role, user=user,
|
||||
org=correct_course_key.org, course_id=correct_course_key
|
||||
)
|
||||
entry.save()
|
||||
try:
|
||||
entry.save()
|
||||
except IntegrityError:
|
||||
# already stored
|
||||
pass
|
||||
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)
|
||||
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():
|
||||
entry = orm['student.courseaccessrole'](role=parsed_entry.group('role_id'), user=user)
|
||||
entry = orm['student.courseaccessrole'](role=role, user=user)
|
||||
entry.save()
|
||||
else:
|
||||
course_id_string = parsed_entry.group('course_id_string')
|
||||
try:
|
||||
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id_string)
|
||||
if course_key not in done:
|
||||
# is the downcased version, get the normal cased one. loc_mapper() has no
|
||||
# methods taking downcased SSCK; so, need to do it manually here
|
||||
correct_course_key = self._map_downcased_ssck(course_key, loc_map_collection, done)
|
||||
_migrate_users(correct_course_key, course_key.org)
|
||||
done.add(course_key)
|
||||
# course_key is the downcased version, get the normal cased one. loc_mapper() has no
|
||||
# methods taking downcased SSCK; so, need to do it manually here
|
||||
correct_course_key = self._map_downcased_ssck(course_key, loc_map_collection)
|
||||
if correct_course_key is not None:
|
||||
_migrate_users(correct_course_key, role, course_key.org)
|
||||
except InvalidKeyError:
|
||||
entry = loc_map_collection.find_one({
|
||||
'course_id': re.compile(r'^{}$'.format(course_id_string), re.IGNORECASE)
|
||||
})
|
||||
if entry is None:
|
||||
# not a course_id as far as we can tell
|
||||
if course_id_string not in done:
|
||||
hold[course_id_string] = group
|
||||
hold.setdefault(course_id_string, []).append(group)
|
||||
else:
|
||||
correct_course_key = self._cache_done_return_ssck(entry, done)
|
||||
_migrate_users(correct_course_key, entry['lower_id']['org'])
|
||||
correct_course_key = SlashSeparatedCourseKey(*entry['_id'].values())
|
||||
_migrate_users(correct_course_key, role, entry['lower_id']['org'])
|
||||
|
||||
# see if any in hold ere missed above
|
||||
for not_ssck, group in hold.iteritems():
|
||||
if not_ssck not in done:
|
||||
if not_ssck in orgs:
|
||||
# see if any in hold were missed above
|
||||
for held_auth_scope, groups in hold.iteritems():
|
||||
# orgs indexed by downcased org
|
||||
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
|
||||
for user in orm['auth.user'].objects.filter(groups=group).all():
|
||||
entry = orm['student.courseaccessrole'](
|
||||
role=parsed_entry.group('role_id'), user=user,
|
||||
org=orgs[not_ssck],
|
||||
role=role,
|
||||
user=user,
|
||||
org=orgs[held_auth_scope],
|
||||
)
|
||||
entry.save()
|
||||
else:
|
||||
# should this just log or really make an effort to do the conversion?
|
||||
log.warn("Didn't convert role %s", group.name)
|
||||
else:
|
||||
# don't silently skip unexpected roles
|
||||
log.warn("Didn't convert roles %s", [group.name for group in groups])
|
||||
|
||||
def backwards(self, orm):
|
||||
"Write your backwards methods here."
|
||||
@@ -98,10 +102,9 @@ class Migration(DataMigration):
|
||||
# the semantic of backwards should be other than perhaps clearing the table.
|
||||
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
|
||||
the lowercased locator form to done map
|
||||
Get the normal cased version of this downcased slash sep course key
|
||||
"""
|
||||
# given the regex, the son may be an overkill
|
||||
course_son = bson.son.SON([
|
||||
@@ -111,25 +114,11 @@ class Migration(DataMigration):
|
||||
])
|
||||
entry = loc_map_collection.find_one(course_son)
|
||||
if entry:
|
||||
return self._cache_done_return_ssck(entry, done)
|
||||
idpart = entry['_id']
|
||||
return SlashSeparatedCourseKey(idpart['org'], idpart['course'], idpart['name'])
|
||||
else:
|
||||
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 = {
|
||||
'auth.group': {
|
||||
|
||||
@@ -79,5 +79,8 @@ setup(
|
||||
'asset-location = xmodule.modulestore.locations:AssetLocation',
|
||||
'edx = xmodule.modulestore.locator:BlockUsageLocator',
|
||||
],
|
||||
'definition_key': [
|
||||
'defx = xmodule.modulestore.locator:DefinitionLocator',
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
@@ -58,7 +58,7 @@ class DefinitionKey(OpaqueKey):
|
||||
KEY_TYPE = 'definition_key'
|
||||
__slots__ = ()
|
||||
|
||||
@abstractmethod
|
||||
@abstractproperty
|
||||
def block_type(self):
|
||||
"""
|
||||
The XBlock type of this definition.
|
||||
@@ -125,6 +125,10 @@ class UsageKey(CourseObjectMixin, OpaqueKey):
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def block_type(self):
|
||||
return self.category
|
||||
|
||||
|
||||
class OpaqueKeyReader(IdReader):
|
||||
"""
|
||||
|
||||
@@ -150,6 +150,7 @@ class LocMapperStore(object):
|
||||
entry = self._migrate_if_necessary([entry])[0]
|
||||
|
||||
block_id = entry['block_map'].get(self.encode_key_for_mongo(location.name))
|
||||
category = location.category
|
||||
if block_id is None:
|
||||
if add_entry_if_missing:
|
||||
block_id = self._add_to_block_map(
|
||||
@@ -159,14 +160,15 @@ class LocMapperStore(object):
|
||||
raise ItemNotFoundError(location)
|
||||
else:
|
||||
# jump_to_id uses a None category.
|
||||
if location.category is None:
|
||||
if category is None:
|
||||
if len(block_id) == 1:
|
||||
# unique match (most common case)
|
||||
category = block_id.keys()[0]
|
||||
block_id = block_id.values()[0]
|
||||
else:
|
||||
raise InvalidLocationError()
|
||||
elif location.category in block_id:
|
||||
block_id = block_id[location.category]
|
||||
elif category in block_id:
|
||||
block_id = block_id[category]
|
||||
elif add_entry_if_missing:
|
||||
block_id = self._add_to_block_map(location, course_son, entry['block_map'])
|
||||
else:
|
||||
@@ -179,10 +181,12 @@ class LocMapperStore(object):
|
||||
)
|
||||
published_usage = BlockUsageLocator(
|
||||
prod_course_locator,
|
||||
block_type=category,
|
||||
block_id=block_id
|
||||
)
|
||||
draft_usage = BlockUsageLocator(
|
||||
prod_course_locator.for_branch(entry['draft_branch']),
|
||||
block_type=category,
|
||||
block_id=block_id
|
||||
)
|
||||
if published:
|
||||
@@ -285,6 +289,7 @@ class LocMapperStore(object):
|
||||
org=entry[entry_org], offering=entry[entry_offering],
|
||||
branch=entry['prod_branch']
|
||||
),
|
||||
block_type=category,
|
||||
block_id=block_id
|
||||
)
|
||||
draft_locator = BlockUsageLocator(
|
||||
@@ -292,6 +297,7 @@ class LocMapperStore(object):
|
||||
org=entry[entry_org], offering=entry[entry_offering],
|
||||
branch=entry['draft_branch']
|
||||
),
|
||||
block_type=category,
|
||||
block_id=block_id
|
||||
)
|
||||
self._cache_location_map_entry(location, published_locator, draft_locator)
|
||||
|
||||
@@ -13,15 +13,7 @@ from bson.errors import InvalidId
|
||||
|
||||
from opaque_keys import OpaqueKey, InvalidKeyError
|
||||
|
||||
from xmodule.modulestore.keys import CourseKey, UsageKey
|
||||
|
||||
from xmodule.modulestore.parsers import (
|
||||
parse_url,
|
||||
parse_block_ref,
|
||||
BRANCH_PREFIX,
|
||||
BLOCK_PREFIX,
|
||||
VERSION_PREFIX,
|
||||
ALLOWED_ID_RE)
|
||||
from xmodule.modulestore.keys import CourseKey, UsageKey, DefinitionKey
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -45,14 +37,10 @@ class Locator(OpaqueKey):
|
||||
Locator is an abstract base class: do not instantiate
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def url(self):
|
||||
"""
|
||||
Return a string containing the URL for this location. Raises
|
||||
InvalidKeyError if the instance doesn't have a
|
||||
complete enough specification to generate a url
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
BLOCK_TYPE_PREFIX = r"type"
|
||||
# Prefix for the version portion of a locator URL, when it is preceded by a course ID
|
||||
VERSION_PREFIX = r"version"
|
||||
ALLOWED_ID_CHARS = r'[\w\-~.:]'
|
||||
|
||||
def __str__(self):
|
||||
'''
|
||||
@@ -86,35 +74,42 @@ class BlockLocatorBase(Locator):
|
||||
# Token separating org from offering
|
||||
ORG_SEPARATOR = '+'
|
||||
|
||||
def version(self):
|
||||
"""
|
||||
Returns the ObjectId referencing this specific location.
|
||||
"""
|
||||
return self.version_guid
|
||||
# Prefix for the branch portion of a locator URL
|
||||
BRANCH_PREFIX = r"branch"
|
||||
# Prefix for the block portion of a locator URL
|
||||
BLOCK_PREFIX = r"block"
|
||||
|
||||
ALLOWED_ID_RE = re.compile(r'^' + Locator.ALLOWED_ID_CHARS + '+$', re.UNICODE)
|
||||
|
||||
URL_RE_SOURCE = r"""
|
||||
((?P<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
|
||||
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.
|
||||
Raises InvalidKeyError if string cannot be parsed.
|
||||
|
||||
If it can be parsed as a version_guid with no preceding org + offering, returns a dict
|
||||
with key 'version_guid' and the value,
|
||||
|
||||
If it can be parsed as a org + offering, returns a dict
|
||||
with key 'id' and optional keys 'branch' and 'version_guid'.
|
||||
"""
|
||||
if not isinstance(url, basestring):
|
||||
raise TypeError('%s is not an instance of basestring' % url)
|
||||
|
||||
parse = parse_url(url)
|
||||
if not parse:
|
||||
raise InvalidKeyError(cls, url)
|
||||
|
||||
if parse['version_guid']:
|
||||
parse['version_guid'] = cls.as_object_id(parse['version_guid'])
|
||||
|
||||
return parse
|
||||
match = cls.URL_RE.match(string)
|
||||
if not match:
|
||||
raise InvalidKeyError(cls, string)
|
||||
return match.groupdict()
|
||||
|
||||
@property
|
||||
def package_id(self):
|
||||
@@ -130,13 +125,10 @@ class CourseLocator(BlockLocatorBase, CourseKey):
|
||||
CourseLocator(version_guid=ObjectId('519665f6223ebd6980884f2b'))
|
||||
CourseLocator(org='mit.eecs', offering='6.002x')
|
||||
CourseLocator(org='mit.eecs', offering='6002x', branch = 'published')
|
||||
CourseLocator.from_string('edx:version/519665f6223ebd6980884f2b')
|
||||
CourseLocator.from_string('version/519665f6223ebd6980884f2b')
|
||||
CourseLocator.from_string('edx:mit.eecs+6002x')
|
||||
CourseLocator.from_string('mit.eecs+6002x')
|
||||
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')
|
||||
CourseLocator.from_string('course-locator:version+519665f6223ebd6980884f2b')
|
||||
CourseLocator.from_string('course-locator:mit.eecs+6002x')
|
||||
CourseLocator.from_string('course-locator:mit.eecs+6002x+branch+published')
|
||||
CourseLocator.from_string('course-locator:mit.eecs+6002x+branch+published+version+519665f6223ebd6980884f2b')
|
||||
|
||||
Should have at least a specific org & offering (id for the course as if it were a project w/
|
||||
versions) with optional 'branch',
|
||||
@@ -163,7 +155,7 @@ class CourseLocator(BlockLocatorBase, CourseKey):
|
||||
if 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])
|
||||
|
||||
super(CourseLocator, self).__init__(
|
||||
@@ -173,31 +165,27 @@ class CourseLocator(BlockLocatorBase, CourseKey):
|
||||
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")
|
||||
|
||||
def version(self):
|
||||
"""
|
||||
Returns the ObjectId referencing this specific location.
|
||||
"""
|
||||
return self.version_guid
|
||||
|
||||
@classmethod
|
||||
def _from_string(cls, serialized):
|
||||
"""
|
||||
Return a CourseLocator parsing the given serialized string
|
||||
:param serialized: matches the string to a CourseLocator
|
||||
"""
|
||||
kwargs = 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))
|
||||
parse = cls.parse_url(serialized)
|
||||
|
||||
def is_fully_specified(self):
|
||||
"""
|
||||
Returns True if either version_guid is specified, or org+offering+branch
|
||||
are specified.
|
||||
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)
|
||||
)
|
||||
if parse['version_guid']:
|
||||
parse['version_guid'] = cls.as_object_id(parse['version_guid'])
|
||||
|
||||
return cls(**{key: parse.get(key) for key in cls.KEY_FIELDS})
|
||||
|
||||
def html_id(self):
|
||||
"""
|
||||
@@ -212,6 +200,7 @@ class CourseLocator(BlockLocatorBase, CourseKey):
|
||||
def make_usage_key(self, block_type, block_id):
|
||||
return BlockUsageLocator(
|
||||
course_key=self,
|
||||
block_type=block_type,
|
||||
block_id=block_id
|
||||
)
|
||||
|
||||
@@ -280,13 +269,13 @@ class CourseLocator(BlockLocatorBase, CourseKey):
|
||||
if self.offering:
|
||||
parts.append(unicode(self.package_id))
|
||||
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:
|
||||
parts.append(u"{prefix}{guid}".format(prefix=VERSION_PREFIX, guid=self.version_guid))
|
||||
return u"/".join(parts)
|
||||
parts.append(u"{prefix}+{guid}".format(prefix=self.VERSION_PREFIX, guid=self.version_guid))
|
||||
return u"+".join(parts)
|
||||
|
||||
|
||||
class BlockUsageLocator(BlockLocatorBase, UsageKey): # TODO implement UsageKey methods
|
||||
class BlockUsageLocator(BlockLocatorBase, UsageKey):
|
||||
"""
|
||||
Encodes a location.
|
||||
|
||||
@@ -305,12 +294,13 @@ class BlockUsageLocator(BlockLocatorBase, UsageKey): # TODO implement UsageKey
|
||||
branch : string
|
||||
"""
|
||||
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
|
||||
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
|
||||
"""
|
||||
@@ -318,7 +308,7 @@ class BlockUsageLocator(BlockLocatorBase, UsageKey): # TODO implement UsageKey
|
||||
if block_id is None:
|
||||
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
|
||||
def _from_string(cls, serialized):
|
||||
@@ -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
|
||||
"""
|
||||
course_key = CourseLocator._from_string(serialized)
|
||||
parsed_parts = parse_url(serialized)
|
||||
block_id = parsed_parts.get('block_id')
|
||||
parsed_parts = cls.parse_url(serialized)
|
||||
block_id = parsed_parts.get('block_id', None)
|
||||
if block_id is None:
|
||||
raise InvalidKeyError(cls, serialized)
|
||||
return cls(course_key, block_id)
|
||||
return cls(course_key, parsed_parts.get('block_type'), block_id)
|
||||
|
||||
def version_agnostic(self):
|
||||
"""
|
||||
@@ -342,7 +332,8 @@ class BlockUsageLocator(BlockLocatorBase, UsageKey): # TODO implement UsageKey
|
||||
"""
|
||||
return BlockUsageLocator(
|
||||
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):
|
||||
@@ -354,6 +345,7 @@ class BlockUsageLocator(BlockLocatorBase, UsageKey): # TODO implement UsageKey
|
||||
"""
|
||||
return BlockUsageLocator(
|
||||
course_key=self.course_key.course_agnostic(),
|
||||
block_type=self.block_type,
|
||||
block_id=self.block_id
|
||||
)
|
||||
|
||||
@@ -363,6 +355,17 @@ class BlockUsageLocator(BlockLocatorBase, UsageKey): # TODO implement UsageKey
|
||||
"""
|
||||
return BlockUsageLocator(
|
||||
self.course_key.for_branch(branch),
|
||||
block_type=self.block_type,
|
||||
block_id=self.block_id
|
||||
)
|
||||
|
||||
def for_version(self, version_guid):
|
||||
"""
|
||||
Return a UsageLocator for the same block in a different branch of the course.
|
||||
"""
|
||||
return BlockUsageLocator(
|
||||
self.course_key.for_version(version_guid),
|
||||
block_type=self.block_type,
|
||||
block_id=self.block_id
|
||||
)
|
||||
|
||||
@@ -370,11 +373,10 @@ class BlockUsageLocator(BlockLocatorBase, UsageKey): # TODO implement UsageKey
|
||||
def _parse_block_ref(cls, block_ref):
|
||||
if isinstance(block_ref, LocalId):
|
||||
return block_ref
|
||||
elif len(block_ref) > 0 and cls.ALLOWED_ID_RE.match(block_ref):
|
||||
return block_ref
|
||||
else:
|
||||
parse = parse_block_ref(block_ref)
|
||||
if not parse:
|
||||
raise InvalidKeyError(cls, block_ref)
|
||||
return parse.get('block_id')
|
||||
raise InvalidKeyError(cls, block_ref)
|
||||
|
||||
@property
|
||||
def definition_key(self):
|
||||
@@ -400,6 +402,9 @@ class BlockUsageLocator(BlockLocatorBase, UsageKey): # TODO implement UsageKey
|
||||
def version_guid(self):
|
||||
return self.course_key.version_guid
|
||||
|
||||
def version(self):
|
||||
return self.course_key.version_guid
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""
|
||||
@@ -411,7 +416,7 @@ class BlockUsageLocator(BlockLocatorBase, UsageKey): # TODO implement UsageKey
|
||||
return self.course_key.is_fully_specified()
|
||||
|
||||
@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
|
||||
:param course_locator: may be a BlockUsageLocator in the same snapshot
|
||||
@@ -420,6 +425,7 @@ class BlockUsageLocator(BlockLocatorBase, UsageKey): # TODO implement UsageKey
|
||||
course_locator = course_locator.course_key
|
||||
return BlockUsageLocator(
|
||||
course_key=course_locator,
|
||||
block_type=block_type,
|
||||
block_id=block_id
|
||||
)
|
||||
|
||||
@@ -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
|
||||
:param course_key: a CourseKey object representing the new course to map into
|
||||
"""
|
||||
return BlockUsageLocator.make_relative(course_key, 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
|
||||
return BlockUsageLocator.make_relative(course_key, self.block_type, self.block_id)
|
||||
|
||||
def _to_string(self):
|
||||
"""
|
||||
Return a string representing this location.
|
||||
"""
|
||||
return u"{course_key}/{BLOCK_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(),
|
||||
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
|
||||
)
|
||||
|
||||
@@ -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
|
||||
(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).
|
||||
"""
|
||||
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):
|
||||
super(DefinitionLocator, self).__init__(definition_id)
|
||||
super(DefinitionLocator, self).__init__(definition_id=definition_id, block_type=block_type)
|
||||
elif isinstance(definition_id, basestring):
|
||||
regex_match = self.URL_RE.match(definition_id)
|
||||
if regex_match is not None:
|
||||
super(DefinitionLocator, self).__init__(self.as_object_id(regex_match.group(1)))
|
||||
else:
|
||||
super(DefinitionLocator, self).__init__(self.as_object_id(definition_id))
|
||||
else:
|
||||
super(DefinitionLocator, self).__init__(self.as_object_id(definition_id))
|
||||
try:
|
||||
definition_id = self.as_object_id(definition_id)
|
||||
except ValueError:
|
||||
raise InvalidKeyError(self, definition_id)
|
||||
super(DefinitionLocator, self).__init__(definition_id=definition_id, block_type=block_type)
|
||||
elif isinstance(definition_id, ObjectId):
|
||||
super(DefinitionLocator, self).__init__(definition_id=definition_id, block_type=block_type)
|
||||
|
||||
def _to_string(self):
|
||||
'''
|
||||
Return a string representing this location.
|
||||
unicode(self) returns something like this: "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.
|
||||
url(self) returns something like this: 'defx:version/519665f6223ebd6980884f2b'
|
||||
Return a DefinitionLocator parsing the given serialized string
|
||||
: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):
|
||||
"""
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
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):
|
||||
offering=course_entry_override.get('offering'),
|
||||
branch=course_entry_override.get('branch'),
|
||||
),
|
||||
block_type=json_data.get('category'),
|
||||
block_id=block_id,
|
||||
)
|
||||
|
||||
@@ -131,6 +132,7 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
|
||||
self,
|
||||
BlockUsageLocator(
|
||||
CourseLocator(version_guid=course_entry_override['structure']['_id']),
|
||||
block_type='error',
|
||||
block_id=block_id
|
||||
),
|
||||
error_msg=exc_info_to_str(sys.exc_info())
|
||||
|
||||
@@ -8,14 +8,14 @@ class DefinitionLazyLoader(object):
|
||||
object doesn't force access during init but waits until client wants the
|
||||
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
|
||||
:param modulestore: the pymongo db connection with the definitions
|
||||
:param definition_locator: the id of the record in the above to fetch
|
||||
"""
|
||||
self.modulestore = modulestore
|
||||
self.definition_locator = DefinitionLocator(definition_id)
|
||||
self.definition_locator = DefinitionLocator(block_type, definition_id)
|
||||
|
||||
def fetch(self):
|
||||
"""
|
||||
|
||||
@@ -152,7 +152,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
|
||||
|
||||
if lazy:
|
||||
for block in new_module_data.itervalues():
|
||||
block['definition'] = DefinitionLazyLoader(self, block['definition'])
|
||||
block['definition'] = DefinitionLazyLoader(self, block['category'], block['definition'])
|
||||
else:
|
||||
# Load all descendants by id
|
||||
descendent_definitions = self.db_connection.find_matching_definitions({
|
||||
@@ -242,11 +242,6 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
|
||||
|
||||
: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:
|
||||
# use the course id
|
||||
index = self.db_connection.get_course_index(course_locator)
|
||||
@@ -258,6 +253,8 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
|
||||
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
|
||||
raise VersionConflictError(course_locator, version_guid)
|
||||
elif course_locator.version_guid is None:
|
||||
raise InsufficientSpecificationError(course_locator)
|
||||
else:
|
||||
# TODO should this raise an exception if branch was provided?
|
||||
version_guid = course_locator.version_guid
|
||||
@@ -322,9 +319,6 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
|
||||
def get_course(self, course_id, depth=None):
|
||||
'''
|
||||
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))
|
||||
course_entry = self._lookup_course(course_id)
|
||||
@@ -458,6 +452,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
|
||||
return [
|
||||
BlockUsageLocator.make_relative(
|
||||
locator,
|
||||
block_type=course['structure']['blocks'][parent_id].get('category'),
|
||||
block_id=LocMapperStore.decode_key_from_mongo(parent_id),
|
||||
)
|
||||
for parent_id in items
|
||||
@@ -471,12 +466,13 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
|
||||
course = self._lookup_course(course_key)
|
||||
items = {LocMapperStore.decode_key_from_mongo(block_id) for block_id in course['structure']['blocks'].keys()}
|
||||
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', []))
|
||||
if block_data['category'] in detached_categories:
|
||||
items.discard(LocMapperStore.decode_key_from_mongo(block_id))
|
||||
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
|
||||
]
|
||||
|
||||
@@ -613,11 +609,11 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
|
||||
# convert the results value sets to locators
|
||||
for k, versions in result.iteritems():
|
||||
result[k] = [
|
||||
BlockUsageLocator(CourseLocator(version_guid=version), block_id=block_id)
|
||||
block_locator.for_version(version)
|
||||
for version in versions
|
||||
]
|
||||
return VersionTree(
|
||||
BlockUsageLocator(CourseLocator(version_guid=possible_roots[0]), block_id=block_id),
|
||||
block_locator.for_version(possible_roots[0]),
|
||||
result
|
||||
)
|
||||
|
||||
@@ -650,7 +646,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
|
||||
'schema_version': self.SCHEMA_VERSION,
|
||||
}
|
||||
self.db_connection.insert_definition(document)
|
||||
definition_locator = DefinitionLocator(new_id)
|
||||
definition_locator = DefinitionLocator(category, new_id)
|
||||
return definition_locator
|
||||
|
||||
def update_definition_from_data(self, definition_locator, new_def_data, user_id):
|
||||
@@ -685,7 +681,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
|
||||
old_definition['edit_info']['previous_version'] = definition_locator.definition_id
|
||||
old_definition['schema_version'] = self.SCHEMA_VERSION
|
||||
self.db_connection.insert_definition(old_definition)
|
||||
return DefinitionLocator(old_definition['_id']), True
|
||||
return DefinitionLocator(old_definition['category'], old_definition['_id']), True
|
||||
else:
|
||||
return definition_locator, False
|
||||
|
||||
@@ -829,11 +825,13 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
|
||||
self._update_head(index_entry, course_or_parent_locator.branch, new_id)
|
||||
item_loc = BlockUsageLocator(
|
||||
course_or_parent_locator.version_agnostic(),
|
||||
block_type=category,
|
||||
block_id=new_block_id,
|
||||
)
|
||||
else:
|
||||
item_loc = BlockUsageLocator(
|
||||
CourseLocator(version_guid=new_id),
|
||||
block_type=category,
|
||||
block_id=new_block_id,
|
||||
)
|
||||
|
||||
@@ -1029,7 +1027,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
|
||||
course_key = CourseLocator(version_guid=new_id)
|
||||
|
||||
# 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)
|
||||
else:
|
||||
# nothing changed, just return the one sent in
|
||||
@@ -1101,18 +1099,14 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
|
||||
self._update_head(index_entry, xblock.location.branch, new_id)
|
||||
|
||||
# fetch and return the new item--fetching is unnecessary but a good qc step
|
||||
return self.get_item(
|
||||
BlockUsageLocator(
|
||||
xblock.location.course_key.for_version(new_id),
|
||||
block_id=xblock.location.block_id,
|
||||
)
|
||||
)
|
||||
return self.get_item(xblock.location.for_version(new_id))
|
||||
else:
|
||||
return xblock
|
||||
|
||||
def _persist_subdag(self, xblock, user_id, structure_blocks, new_id):
|
||||
# persist the definition if persisted != passed
|
||||
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):
|
||||
xblock.definition_locator = self.create_definition_from_data(
|
||||
new_def_data, xblock.category, user_id)
|
||||
@@ -1134,9 +1128,6 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
|
||||
else:
|
||||
is_new = False
|
||||
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 = []
|
||||
if xblock.has_children:
|
||||
@@ -1147,6 +1138,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
|
||||
children.append(child_block.location.block_id)
|
||||
else:
|
||||
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)
|
||||
if not is_new and not is_updated:
|
||||
@@ -1419,9 +1411,9 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
|
||||
if isinstance(definition, DefinitionLazyLoader):
|
||||
return definition.definition_locator
|
||||
elif '_id' not in definition:
|
||||
return DefinitionLocator(LocalId())
|
||||
return DefinitionLocator(definition.get('category'), LocalId())
|
||||
else:
|
||||
return DefinitionLocator(definition['_id'])
|
||||
return DefinitionLocator(definition['category'], definition['_id'])
|
||||
|
||||
def get_modulestore_type(self, course_id):
|
||||
"""
|
||||
|
||||
@@ -32,14 +32,14 @@ class PersistentCourseFactory(SplitFactory):
|
||||
|
||||
# pylint: disable=W0613
|
||||
@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):
|
||||
|
||||
modulestore = kwargs.pop('modulestore')
|
||||
root_block_id = kwargs.pop('root_block_id', 'course')
|
||||
# Write the data to the mongo datastore
|
||||
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
|
||||
)
|
||||
|
||||
|
||||
@@ -267,6 +267,7 @@ class TestLocationMapper(LocMapperSetupSansDjango):
|
||||
)
|
||||
prob_locator = BlockUsageLocator(
|
||||
prob_course_key,
|
||||
block_type='problem',
|
||||
block_id='problem2',
|
||||
)
|
||||
prob_location = loc_mapper().translate_locator_to_location(prob_locator)
|
||||
@@ -289,20 +290,21 @@ class TestLocationMapper(LocMapperSetupSansDjango):
|
||||
prob_location = loc_mapper().translate_locator_to_location(prob_locator, get_course=True)
|
||||
self.assertEqual(prob_location, SlashSeparatedCourseKey(org, course, run))
|
||||
# explicit branch
|
||||
prob_locator = BlockUsageLocator(
|
||||
prob_course_key.for_branch('draft'), block_id=prob_locator.block_id
|
||||
)
|
||||
prob_locator = prob_locator.for_branch('draft')
|
||||
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
|
||||
# with old mongo/draft modulestores.
|
||||
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)
|
||||
self.assertEqual(prob_location, Location(org, course, run, 'problem', 'abc123', None))
|
||||
# same for chapter except chapter cannot be draft in old system
|
||||
chap_locator = BlockUsageLocator(
|
||||
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)
|
||||
self.assertEqual(chap_location, Location(org, course, run, 'chapter', '48f23a10395384929234'))
|
||||
@@ -311,7 +313,7 @@ class TestLocationMapper(LocMapperSetupSansDjango):
|
||||
chap_location = loc_mapper().translate_locator_to_location(chap_locator)
|
||||
self.assertEqual(chap_location, Location(org, course, run, 'chapter', '48f23a10395384929234'))
|
||||
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)
|
||||
self.assertEqual(chap_location, Location(org, course, run, 'chapter', '48f23a10395384929234'))
|
||||
@@ -319,7 +321,7 @@ class TestLocationMapper(LocMapperSetupSansDjango):
|
||||
# look for non-existent problem
|
||||
prob_locator2 = BlockUsageLocator(
|
||||
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)
|
||||
self.assertIsNone(prob_location, 'Found non-existent problem')
|
||||
|
||||
@@ -7,8 +7,8 @@ import random
|
||||
from bson.objectid import ObjectId
|
||||
from opaque_keys import InvalidKeyError
|
||||
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 xmodule.modulestore.keys import UsageKey, CourseKey, DefinitionKey
|
||||
|
||||
|
||||
@ddt
|
||||
@@ -40,7 +40,7 @@ class LocatorTest(TestCase):
|
||||
testobj_1 = CourseLocator(version_guid=test_id_1)
|
||||
self.check_course_locn_fields(testobj_1, version_guid=test_id_1)
|
||||
self.assertEqual(str(testobj_1.version_guid), test_id_1_loc)
|
||||
self.assertEqual(testobj_1._to_string(), 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_id_2_loc = '519665f6223ebd6980884f2b'
|
||||
@@ -48,24 +48,24 @@ class LocatorTest(TestCase):
|
||||
testobj_2 = CourseLocator(version_guid=test_id_2)
|
||||
self.check_course_locn_fields(testobj_2, version_guid=test_id_2)
|
||||
self.assertEqual(str(testobj_2.version_guid), test_id_2_loc)
|
||||
self.assertEqual(testobj_2._to_string(), VERSION_PREFIX + test_id_2_loc)
|
||||
self.assertEqual(testobj_2._to_string(), u'+'.join((testobj_2.VERSION_PREFIX, test_id_2_loc)))
|
||||
|
||||
@data(
|
||||
' mit.eecs',
|
||||
'mit.eecs ',
|
||||
VERSION_PREFIX + 'mit.eecs',
|
||||
BLOCK_PREFIX + 'black/mit.eecs',
|
||||
CourseLocator.VERSION_PREFIX + '+mit.eecs',
|
||||
BlockUsageLocator.BLOCK_PREFIX + '+black+mit.eecs',
|
||||
'mit.ee cs',
|
||||
'mit.ee,cs',
|
||||
'mit.ee/cs',
|
||||
'mit.ee+cs',
|
||||
'mit.ee&cs',
|
||||
'mit.ee()cs',
|
||||
BRANCH_PREFIX + 'this',
|
||||
'mit.eecs/' + BRANCH_PREFIX,
|
||||
'mit.eecs/' + BRANCH_PREFIX + 'this/' + BRANCH_PREFIX + 'that',
|
||||
'mit.eecs/' + BRANCH_PREFIX + 'this/' + BRANCH_PREFIX,
|
||||
'mit.eecs/' + BRANCH_PREFIX + 'this ',
|
||||
'mit.eecs/' + BRANCH_PREFIX + 'th%is ',
|
||||
CourseLocator.BRANCH_PREFIX + '+this',
|
||||
'mit.eecs+' + CourseLocator.BRANCH_PREFIX,
|
||||
'mit.eecs+' + CourseLocator.BRANCH_PREFIX + '+this+' + CourseLocator.BRANCH_PREFIX + '+that',
|
||||
'mit.eecs+' + CourseLocator.BRANCH_PREFIX + '+this+' + CourseLocator.BRANCH_PREFIX,
|
||||
'mit.eecs+' + CourseLocator.BRANCH_PREFIX + '+this ',
|
||||
'mit.eecs+' + CourseLocator.BRANCH_PREFIX + '+th%is ',
|
||||
)
|
||||
def test_course_constructor_bad_package_id(self, bad_id):
|
||||
"""
|
||||
@@ -78,18 +78,20 @@ class LocatorTest(TestCase):
|
||||
CourseLocator(org='test', offering=bad_id)
|
||||
|
||||
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')
|
||||
def test_course_constructor_bad_url(self, bad_url):
|
||||
with self.assertRaises(InvalidKeyError):
|
||||
CourseLocator.from_string(bad_url)
|
||||
CourseKey.from_string(bad_url)
|
||||
|
||||
def test_course_constructor_url(self):
|
||||
# Test parsing a url when it starts with a version ID and there is also a block ID.
|
||||
# This hits the parsers parse_guid method.
|
||||
test_id_loc = '519665f6223ebd6980884f2b'
|
||||
testobj = 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(
|
||||
testobj,
|
||||
version_guid=ObjectId(test_id_loc)
|
||||
@@ -97,7 +99,9 @@ class LocatorTest(TestCase):
|
||||
|
||||
def test_course_constructor_url_package_id_and_version_guid(self):
|
||||
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(
|
||||
testobj,
|
||||
org='mit.eecs',
|
||||
@@ -109,8 +113,8 @@ class LocatorTest(TestCase):
|
||||
test_id_loc = '519665f6223ebd6980884f2b'
|
||||
org = 'mit.eecs'
|
||||
offering = '~6002x'
|
||||
testobj = CourseLocator.from_string('course-locator:{}+{}/{}draft-1/{}{}'.format(
|
||||
org, offering, BRANCH_PREFIX, VERSION_PREFIX, test_id_loc
|
||||
testobj = CourseKey.from_string('course-locator:{}+{}+{}+draft-1+{}+{}'.format(
|
||||
org, offering, CourseLocator.BRANCH_PREFIX, CourseLocator.VERSION_PREFIX, test_id_loc
|
||||
))
|
||||
self.check_course_locn_fields(
|
||||
testobj,
|
||||
@@ -126,15 +130,13 @@ class LocatorTest(TestCase):
|
||||
testurn = '{}+{}'.format(org, offering)
|
||||
testobj = CourseLocator(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)
|
||||
|
||||
def test_course_constructor_package_id_separate_branch(self):
|
||||
org = 'mit.eecs'
|
||||
offering = '6002x'
|
||||
testurn = '{}+{}'.format(org, offering)
|
||||
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)
|
||||
self.check_course_locn_fields(
|
||||
testobj,
|
||||
@@ -142,7 +144,6 @@ class LocatorTest(TestCase):
|
||||
offering=offering,
|
||||
branch=test_branch,
|
||||
)
|
||||
self.assertEqual(testobj.package_id, testurn)
|
||||
self.assertEqual(testobj.branch, test_branch)
|
||||
self.assertEqual(testobj._to_string(), expected_urn)
|
||||
|
||||
@@ -151,17 +152,21 @@ class LocatorTest(TestCase):
|
||||
expected_offering = '6002x'
|
||||
expected_branch = 'published'
|
||||
expected_block_ref = 'HW3'
|
||||
testurn = 'edx:{}+{}/{}{}/{}{}'.format(
|
||||
expected_org, expected_offering, BRANCH_PREFIX, expected_branch, BLOCK_PREFIX, 'HW3'
|
||||
testurn = 'edx:{}+{}+{}+{}+{}+{}+{}+{}'.format(
|
||||
expected_org, expected_offering, CourseLocator.BRANCH_PREFIX, expected_branch,
|
||||
BlockUsageLocator.BLOCK_TYPE_PREFIX, 'problem', BlockUsageLocator.BLOCK_PREFIX, 'HW3'
|
||||
)
|
||||
testobj = UsageKey.from_string(testurn)
|
||||
self.check_block_locn_fields(
|
||||
testobj,
|
||||
org=expected_org,
|
||||
offering=expected_offering,
|
||||
branch=expected_branch,
|
||||
block_type='problem',
|
||||
block=expected_block_ref
|
||||
)
|
||||
testobj = BlockUsageLocator.from_string(testurn)
|
||||
self.check_block_locn_fields(testobj,
|
||||
org=expected_org,
|
||||
offering=expected_offering,
|
||||
branch=expected_branch,
|
||||
block=expected_block_ref)
|
||||
self.assertEqual(unicode(testobj), testurn)
|
||||
testobj = BlockUsageLocator(testobj.course_key.for_version(ObjectId()), testobj.block_id)
|
||||
testobj = testobj.for_version(ObjectId())
|
||||
agnostic = testobj.version_agnostic()
|
||||
self.assertIsNone(agnostic.version_guid)
|
||||
self.check_block_locn_fields(agnostic,
|
||||
@@ -172,13 +177,16 @@ class LocatorTest(TestCase):
|
||||
|
||||
def test_block_constructor_url_version_prefix(self):
|
||||
test_id_loc = '519665f6223ebd6980884f2b'
|
||||
testobj = BlockUsageLocator.from_string(
|
||||
'edx:mit.eecs+6002x/{}{}/{}lab2'.format(VERSION_PREFIX, test_id_loc, BLOCK_PREFIX)
|
||||
testobj = UsageKey.from_string(
|
||||
'edx:mit.eecs+6002x+{}+{}+{}+problem+{}+lab2'.format(
|
||||
CourseLocator.VERSION_PREFIX, test_id_loc, BlockUsageLocator.BLOCK_TYPE_PREFIX, BlockUsageLocator.BLOCK_PREFIX
|
||||
)
|
||||
)
|
||||
self.check_block_locn_fields(
|
||||
testobj,
|
||||
org='mit.eecs',
|
||||
offering='6002x',
|
||||
block_type='problem',
|
||||
block='lab2',
|
||||
version_guid=ObjectId(test_id_loc)
|
||||
)
|
||||
@@ -195,9 +203,10 @@ class LocatorTest(TestCase):
|
||||
|
||||
def test_block_constructor_url_kitchen_sink(self):
|
||||
test_id_loc = '519665f6223ebd6980884f2b'
|
||||
testobj = BlockUsageLocator.from_string(
|
||||
'edx:mit.eecs+6002x/{}draft/{}{}/{}lab2'.format(
|
||||
BRANCH_PREFIX, VERSION_PREFIX, test_id_loc, BLOCK_PREFIX
|
||||
testobj = UsageKey.from_string(
|
||||
'edx:mit.eecs+6002x+{}+draft+{}+{}+{}+problem+{}+lab2'.format(
|
||||
CourseLocator.BRANCH_PREFIX, CourseLocator.VERSION_PREFIX, test_id_loc,
|
||||
BlockUsageLocator.BLOCK_TYPE_PREFIX, BlockUsageLocator.BLOCK_PREFIX
|
||||
)
|
||||
)
|
||||
self.check_block_locn_fields(
|
||||
@@ -219,6 +228,7 @@ class LocatorTest(TestCase):
|
||||
block_id = 'problem:with-colon~2'
|
||||
testobj = BlockUsageLocator(
|
||||
CourseLocator(org=org, offering=offering, branch=branch),
|
||||
block_type='problem',
|
||||
block_id=block_id
|
||||
)
|
||||
self.check_block_locn_fields(
|
||||
@@ -234,54 +244,32 @@ class LocatorTest(TestCase):
|
||||
branch = 'foo'
|
||||
baseobj = CourseLocator(org=org, offering=offering, branch=branch)
|
||||
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(
|
||||
testobj, org=org, offering=offering, branch=branch, block=block_id
|
||||
)
|
||||
block_id = 'completely_different'
|
||||
testobj = BlockUsageLocator.make_relative(testobj, block_id)
|
||||
testobj = BlockUsageLocator.make_relative(testobj, 'problem', block_id)
|
||||
self.check_block_locn_fields(
|
||||
testobj, org=org, offering=offering, branch=branch, block=block_id
|
||||
)
|
||||
|
||||
def test_repr(self):
|
||||
testurn = 'edx:mit.eecs+6002x/' + BRANCH_PREFIX + 'published/' + BLOCK_PREFIX + 'HW3'
|
||||
testobj = BlockUsageLocator.from_string(testurn)
|
||||
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/', '')
|
||||
testurn = u'edx:mit.eecs+6002x+{}+published+{}+problem+{}+HW3'.format(
|
||||
CourseLocator.BRANCH_PREFIX, BlockUsageLocator.BLOCK_TYPE_PREFIX, BlockUsageLocator.BLOCK_PREFIX
|
||||
)
|
||||
testobj = UsageKey.from_string(testurn)
|
||||
self.assertEqual("BlockUsageLocator(CourseLocator(u'mit.eecs', u'6002x', u'published', None), u'problem', u'HW3')", repr(testobj))
|
||||
|
||||
def test_description_locator_url(self):
|
||||
object_id = '{:024x}'.format(random.randrange(16 ** 24))
|
||||
definition_locator = DefinitionLocator(object_id)
|
||||
self.assertEqual('defx:' + VERSION_PREFIX + object_id, unicode(definition_locator))
|
||||
definition_locator = DefinitionLocator('html', object_id)
|
||||
self.assertEqual('defx:{}+{}+html'.format(object_id, DefinitionLocator.BLOCK_TYPE_PREFIX), unicode(definition_locator))
|
||||
self.assertEqual(definition_locator, DefinitionKey.from_string(unicode(definition_locator)))
|
||||
|
||||
def test_description_locator_version(self):
|
||||
object_id = '{:024x}'.format(random.randrange(16 ** 24))
|
||||
definition_locator = DefinitionLocator(object_id)
|
||||
definition_locator = DefinitionLocator('html', object_id)
|
||||
self.assertEqual(object_id, str(definition_locator.version()))
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
@@ -298,10 +286,12 @@ class LocatorTest(TestCase):
|
||||
self.assertEqual(testobj.branch, branch)
|
||||
|
||||
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
|
||||
"""
|
||||
self.check_course_locn_fields(testobj, version_guid, org, offering,
|
||||
branch)
|
||||
if block_type is not None:
|
||||
self.assertEqual(testobj.block_type, block_type)
|
||||
self.assertEqual(testobj.block_id, block)
|
||||
|
||||
@@ -442,7 +442,7 @@ class SplitModuleTest(unittest.TestCase):
|
||||
Sets up the initial data into the db
|
||||
'''
|
||||
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_spec['org'], course_spec['offering'], course_spec['user_id'],
|
||||
fields=course_spec['fields'],
|
||||
@@ -454,7 +454,8 @@ class SplitModuleTest(unittest.TestCase):
|
||||
if course.location.block_id == block_id:
|
||||
block = course
|
||||
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)
|
||||
for key, value in fields.iteritems():
|
||||
setattr(block, key, value)
|
||||
@@ -466,7 +467,7 @@ class SplitModuleTest(unittest.TestCase):
|
||||
elif spec['parent'] == course.location.block_id:
|
||||
parent = course
|
||||
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)
|
||||
block_id = LocalId(spec['id'])
|
||||
child = split_store.create_xblock(
|
||||
@@ -477,6 +478,7 @@ class SplitModuleTest(unittest.TestCase):
|
||||
# publish "testx.wonderful"
|
||||
to_publish = BlockUsageLocator(
|
||||
CourseLocator(org="testx", offering="wonderful", branch="draft"),
|
||||
block_type='course',
|
||||
block_id="head23456"
|
||||
)
|
||||
destination = CourseLocator(org="testx", offering="wonderful", branch="published")
|
||||
@@ -676,12 +678,12 @@ class SplitModuleItemTests(SplitModuleTest):
|
||||
course = modulestore().get_course(course_locator)
|
||||
previous_version = course.previous_version
|
||||
# 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(
|
||||
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(
|
||||
modulestore().has_item(locator),
|
||||
)
|
||||
@@ -689,6 +691,7 @@ class SplitModuleItemTests(SplitModuleTest):
|
||||
modulestore().has_item(
|
||||
BlockUsageLocator(
|
||||
locator.course_key.for_branch('published'),
|
||||
block_type=locator.block_type,
|
||||
block_id=locator.block_id
|
||||
)
|
||||
),
|
||||
@@ -696,7 +699,7 @@ class SplitModuleItemTests(SplitModuleTest):
|
||||
)
|
||||
|
||||
# not a course obj
|
||||
locator = BlockUsageLocator(course_locator, block_id='chapter1')
|
||||
locator = BlockUsageLocator(course_locator, block_type='chapter', block_id='chapter1')
|
||||
self.assertTrue(
|
||||
modulestore().has_item(locator),
|
||||
"couldn't find chapter1"
|
||||
@@ -705,26 +708,25 @@ class SplitModuleItemTests(SplitModuleTest):
|
||||
# in published course
|
||||
locator = BlockUsageLocator(
|
||||
CourseLocator(org="testx", offering="wonderful", branch='draft'),
|
||||
block_type="course",
|
||||
block_id="head23456"
|
||||
)
|
||||
self.assertTrue(
|
||||
modulestore().has_item(
|
||||
BlockUsageLocator(locator.course_key.for_branch("published"), block_id=locator.block_id)
|
||||
)
|
||||
modulestore().has_item(locator.for_branch("published"))
|
||||
)
|
||||
locator = locator.for_branch('published')
|
||||
self.assertTrue(modulestore().has_item(locator), "couldn't find in published")
|
||||
|
||||
def test_negative_has_item(self):
|
||||
# negative tests--not found
|
||||
# no such course or block
|
||||
locator = BlockUsageLocator(
|
||||
CourseLocator(org="foo", offering="doesnotexist", branch='draft'),
|
||||
block_type="course",
|
||||
block_id="head23456"
|
||||
)
|
||||
self.assertFalse(modulestore().has_item(locator))
|
||||
locator = BlockUsageLocator(
|
||||
CourseLocator(org="testx", offering="wonderful", branch='draft'),
|
||||
block_type="vertical",
|
||||
block_id="doesnotexist"
|
||||
)
|
||||
self.assertFalse(modulestore().has_item(locator))
|
||||
@@ -738,7 +740,7 @@ class SplitModuleItemTests(SplitModuleTest):
|
||||
previous_version = course.previous_version
|
||||
|
||||
# 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)
|
||||
self.assertIsInstance(block, CourseDescriptor)
|
||||
self.assertIsInstance(modulestore().get_item(locator), CourseDescriptor)
|
||||
@@ -759,36 +761,27 @@ class SplitModuleItemTests(SplitModuleTest):
|
||||
block.grade_cutoffs, {"Pass": 0.45},
|
||||
)
|
||||
|
||||
locator = BlockUsageLocator(hero_locator, block_id='head12345')
|
||||
verify_greek_hero(modulestore().get_item(locator))
|
||||
verify_greek_hero(modulestore().get_item(course.location))
|
||||
|
||||
# try to look up other branches
|
||||
with self.assertRaises(ItemNotFoundError):
|
||||
modulestore().get_item(
|
||||
BlockUsageLocator(
|
||||
hero_locator.for_branch("published"),
|
||||
block_id=locator.block_id,
|
||||
)
|
||||
)
|
||||
self.assertIsInstance(
|
||||
modulestore().get_item(locator),
|
||||
CourseDescriptor
|
||||
)
|
||||
modulestore().get_item(course.location.for_branch("published"))
|
||||
|
||||
def test_get_non_root(self):
|
||||
# not a course obj
|
||||
locator = BlockUsageLocator(
|
||||
CourseLocator(org='testx', offering='GreekHero', branch='draft'), 'chapter1'
|
||||
CourseLocator(org='testx', offering='GreekHero', branch='draft'), 'chapter', 'chapter1'
|
||||
)
|
||||
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.display_name, "Hercules")
|
||||
self.assertEqual(block.edited_by, "testassist@edx.org")
|
||||
|
||||
# in published course
|
||||
locator = BlockUsageLocator(
|
||||
CourseLocator(org='testx', offering='wonderful', branch='published'), 'head23456'
|
||||
CourseLocator(org='testx', offering='wonderful', branch='published'), 'course', 'head23456'
|
||||
)
|
||||
self.assertIsInstance(
|
||||
modulestore().get_item(locator),
|
||||
@@ -798,12 +791,12 @@ class SplitModuleItemTests(SplitModuleTest):
|
||||
# negative tests--not found
|
||||
# no such course or block
|
||||
locator = BlockUsageLocator(
|
||||
CourseLocator(org='doesnotexist', offering='doesnotexist', branch='draft'), 'head23456'
|
||||
CourseLocator(org='doesnotexist', offering='doesnotexist', branch='draft'), 'course', 'head23456'
|
||||
)
|
||||
with self.assertRaises(ItemNotFoundError):
|
||||
modulestore().get_item(locator)
|
||||
locator = BlockUsageLocator(
|
||||
CourseLocator(org='testx', offering='wonderful', branch='draft'), 'doesnotexist'
|
||||
CourseLocator(org='testx', offering='wonderful', branch='draft'), 'html', 'doesnotexist'
|
||||
)
|
||||
with self.assertRaises(ItemNotFoundError):
|
||||
modulestore().get_item(locator)
|
||||
@@ -864,7 +857,7 @@ class SplitModuleItemTests(SplitModuleTest):
|
||||
'''
|
||||
locator = BlockUsageLocator(
|
||||
CourseLocator(org='testx', offering='GreekHero', branch='draft'),
|
||||
block_id='chapter1'
|
||||
'chapter', block_id='chapter1'
|
||||
)
|
||||
parents = modulestore().get_parent_locations(locator)
|
||||
self.assertEqual(len(parents), 1)
|
||||
@@ -884,7 +877,7 @@ class SplitModuleItemTests(SplitModuleTest):
|
||||
Test the existing get_children method on xdescriptors
|
||||
"""
|
||||
locator = BlockUsageLocator(
|
||||
CourseLocator(org='testx', offering='GreekHero', branch='draft'), 'head12345'
|
||||
CourseLocator(org='testx', offering='GreekHero', branch='draft'), 'course', 'head12345'
|
||||
)
|
||||
block = modulestore().get_item(locator)
|
||||
children = block.get_children()
|
||||
@@ -952,11 +945,11 @@ class TestItemCrud(SplitModuleTest):
|
||||
self.assertIsNotNone(new_module.definition_locator)
|
||||
self.assertEqual(new_module.display_name, 'new sequential')
|
||||
# check that block does not exist in previous version
|
||||
locator = BlockUsageLocator(
|
||||
CourseLocator(version_guid=premod_course.location.version_guid),
|
||||
block_id=new_module.location.block_id
|
||||
locator = new_module.location.map_into_course(
|
||||
CourseLocator(version_guid=premod_course.location.version_guid)
|
||||
)
|
||||
self.assertRaises(ItemNotFoundError, modulestore().get_item, locator)
|
||||
with self.assertRaises(ItemNotFoundError):
|
||||
modulestore().get_item(locator)
|
||||
|
||||
def test_create_parented_item(self):
|
||||
"""
|
||||
@@ -964,12 +957,12 @@ class TestItemCrud(SplitModuleTest):
|
||||
"""
|
||||
locator = BlockUsageLocator(
|
||||
CourseLocator(org='testx', offering='GreekHero', branch='draft'),
|
||||
block_id='chapter2'
|
||||
'chapter', block_id='chapter2'
|
||||
)
|
||||
original = modulestore().get_item(locator)
|
||||
|
||||
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)
|
||||
category = 'chapter'
|
||||
@@ -992,12 +985,12 @@ class TestItemCrud(SplitModuleTest):
|
||||
"""
|
||||
locator = BlockUsageLocator(
|
||||
CourseLocator(org='testx', offering='GreekHero', branch='draft'),
|
||||
block_id='problem1'
|
||||
'problem', block_id='problem1'
|
||||
)
|
||||
original = modulestore().get_item(locator)
|
||||
|
||||
locator = BlockUsageLocator(
|
||||
CourseLocator(org='guestx', offering='contender', branch='draft'), 'head345679'
|
||||
CourseLocator(org='guestx', offering='contender', branch='draft'), 'course', 'head345679'
|
||||
)
|
||||
category = 'problem'
|
||||
new_payload = "<problem>empty</problem>"
|
||||
@@ -1031,8 +1024,8 @@ class TestItemCrud(SplitModuleTest):
|
||||
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')
|
||||
parent_locator = BlockUsageLocator(course_key, block_id="head345679")
|
||||
chapter_locator = BlockUsageLocator(course_key, block_id="foo.bar_-~:0")
|
||||
parent_locator = BlockUsageLocator(course_key, 'course', block_id="head345679")
|
||||
chapter_locator = BlockUsageLocator(course_key, 'chapter', block_id="foo.bar_-~:0")
|
||||
modulestore().create_item(
|
||||
parent_locator, 'chapter', 'anotheruser',
|
||||
block_id=chapter_locator.block_id,
|
||||
@@ -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
|
||||
# now try making that a parent of something
|
||||
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(
|
||||
chapter_locator, 'problem', 'anotheruser',
|
||||
block_id=problem_locator.block_id,
|
||||
@@ -1119,10 +1112,7 @@ class TestItemCrud(SplitModuleTest):
|
||||
)
|
||||
|
||||
# add new child to old parent in continued (leave off version_guid)
|
||||
course_module_locator = BlockUsageLocator(
|
||||
new_course.location.course_key.version_agnostic(),
|
||||
block_id=new_course.location.block_id,
|
||||
)
|
||||
course_module_locator = new_course.location.version_agnostic()
|
||||
new_ele = modulestore().create_item(
|
||||
course_module_locator, 'chapter', user,
|
||||
fields={'display_name': 'chapter 4'},
|
||||
@@ -1143,7 +1133,7 @@ class TestItemCrud(SplitModuleTest):
|
||||
"""
|
||||
locator = BlockUsageLocator(
|
||||
CourseLocator(org="testx", offering="GreekHero", branch='draft'),
|
||||
block_id="problem3_2"
|
||||
'problem', block_id="problem3_2"
|
||||
)
|
||||
problem = modulestore().get_item(locator)
|
||||
pre_def_id = problem.definition_locator.definition_id
|
||||
@@ -1160,10 +1150,7 @@ class TestItemCrud(SplitModuleTest):
|
||||
self.assertNotEqual(updated_problem.location.version_guid, pre_version_guid)
|
||||
self.assertEqual(updated_problem.max_attempts, 4)
|
||||
# refetch to ensure original didn't change
|
||||
original_location = BlockUsageLocator(
|
||||
CourseLocator(version_guid=pre_version_guid),
|
||||
block_id=problem.location.block_id
|
||||
)
|
||||
original_location = problem.location.map_into_course(CourseLocator(version_guid=pre_version_guid))
|
||||
problem = modulestore().get_item(original_location)
|
||||
self.assertNotEqual(problem.max_attempts, 4, "original changed")
|
||||
|
||||
@@ -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
|
||||
"""
|
||||
locator = BlockUsageLocator(
|
||||
CourseLocator(org='testx', offering='GreekHero', branch='draft'), 'chapter3'
|
||||
CourseLocator(org='testx', offering='GreekHero', branch='draft'), 'chapter', 'chapter3'
|
||||
)
|
||||
block = modulestore().get_item(locator)
|
||||
pre_def_id = block.definition_locator.definition_id
|
||||
@@ -1206,7 +1193,7 @@ class TestItemCrud(SplitModuleTest):
|
||||
test updating an item's definition: ensure it gets versioned as well as the course getting versioned
|
||||
"""
|
||||
locator = BlockUsageLocator(
|
||||
CourseLocator(org='testx', offering='GreekHero', branch='draft'), 'head12345'
|
||||
CourseLocator(org='testx', offering='GreekHero', branch='draft'), 'course', 'head12345'
|
||||
)
|
||||
block = modulestore().get_item(locator)
|
||||
pre_def_id = block.definition_locator.definition_id
|
||||
@@ -1226,13 +1213,13 @@ class TestItemCrud(SplitModuleTest):
|
||||
"""
|
||||
locator = BlockUsageLocator(
|
||||
CourseLocator('testx', 'GreekHero', branch='draft'),
|
||||
block_id='problem1'
|
||||
'problem', block_id='problem1'
|
||||
)
|
||||
original = modulestore().get_item(locator)
|
||||
# first add 2 children to the course for the update to manipulate
|
||||
locator = BlockUsageLocator(
|
||||
CourseLocator('guestx', 'contender', branch='draft'),
|
||||
block_id="head345679"
|
||||
'course', block_id="head345679"
|
||||
)
|
||||
category = 'problem'
|
||||
new_payload = "<problem>empty</problem>"
|
||||
@@ -1282,11 +1269,7 @@ class TestItemCrud(SplitModuleTest):
|
||||
with self.assertRaises(VersionConflictError):
|
||||
modulestore().has_item(locn_to_del)
|
||||
|
||||
locator = BlockUsageLocator(
|
||||
CourseLocator(version_guid=locn_to_del.version_guid),
|
||||
block_id=locn_to_del.block_id
|
||||
)
|
||||
self.assertTrue(modulestore().has_item(locator))
|
||||
self.assertTrue(modulestore().has_item(locn_to_del.course_agnostic()))
|
||||
self.assertNotEqual(new_course_loc.version_guid, course.location.version_guid)
|
||||
|
||||
# delete a subtree
|
||||
@@ -1301,22 +1284,9 @@ class TestItemCrud(SplitModuleTest):
|
||||
if node:
|
||||
node_loc = node.location
|
||||
self.assertFalse(
|
||||
modulestore().has_item(
|
||||
BlockUsageLocator(
|
||||
CourseLocator(
|
||||
org=node_loc.org,
|
||||
offering=node_loc.offering,
|
||||
branch=node_loc.branch,
|
||||
),
|
||||
block_id=node_loc.block_id
|
||||
)
|
||||
)
|
||||
modulestore().has_item(node_loc.version_agnostic())
|
||||
)
|
||||
locator = BlockUsageLocator(
|
||||
CourseLocator(version_guid=node.location.version_guid),
|
||||
block_id=node.location.block_id
|
||||
)
|
||||
self.assertTrue(modulestore().has_item(locator))
|
||||
self.assertTrue(modulestore().has_item(node_loc.course_agnostic()))
|
||||
if node.has_children:
|
||||
for sub in node.get_children():
|
||||
check_subtree(sub)
|
||||
@@ -1327,10 +1297,7 @@ class TestItemCrud(SplitModuleTest):
|
||||
Create a course we can delete
|
||||
"""
|
||||
course = modulestore().create_course('nihilx', 'deletion', 'deleting_user')
|
||||
root = BlockUsageLocator(
|
||||
course.id.version_agnostic().for_branch('draft'),
|
||||
block_id=course.location.block_id,
|
||||
)
|
||||
root = course.location.version_agnostic().for_branch('draft')
|
||||
for _ in range(4):
|
||||
self.create_subtree_for_deletion(root, ['chapter', 'vertical', 'problem'])
|
||||
return modulestore().get_item(root)
|
||||
@@ -1342,7 +1309,7 @@ class TestItemCrud(SplitModuleTest):
|
||||
if not category_queue:
|
||||
return
|
||||
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):
|
||||
self.create_subtree_for_deletion(node_loc, category_queue[1:])
|
||||
|
||||
@@ -1523,13 +1490,13 @@ class TestInheritance(SplitModuleTest):
|
||||
# Note, not testing value where defined (course) b/c there's no
|
||||
# defined accessor for it on CourseDescriptor.
|
||||
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)
|
||||
# inherited
|
||||
self.assertEqual(node.graceperiod, datetime.timedelta(hours=2))
|
||||
locator = BlockUsageLocator(
|
||||
CourseLocator(org='testx', offering='GreekHero', branch='draft'), 'problem1'
|
||||
CourseLocator(org='testx', offering='GreekHero', branch='draft'), 'problem', 'problem1'
|
||||
)
|
||||
node = modulestore().get_item(locator)
|
||||
# overridden
|
||||
@@ -1560,19 +1527,19 @@ class TestPublish(SplitModuleTest):
|
||||
)
|
||||
# add a child under chapter1
|
||||
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'},
|
||||
)
|
||||
# remove chapter1 from expected b/c its pub'd version != the source anymore since source changed
|
||||
expected.remove("chapter1")
|
||||
# check that it's not in published course
|
||||
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
|
||||
modulestore().xblock_publish(self.user, source_course, dest_course, [new_module.location.block_id], None)
|
||||
expected.append(new_module.location.block_id)
|
||||
# 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(
|
||||
modulestore().get_parent_locations(pub_module.location)[0].block_id, "chapter1"
|
||||
)
|
||||
@@ -1584,7 +1551,7 @@ class TestPublish(SplitModuleTest):
|
||||
modulestore().xblock_publish(self.user, source_course, dest_course, [new_module.location.block_id], None)
|
||||
expected.append(new_module.location.block_id)
|
||||
# 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(
|
||||
source_course, dest_course, expected, ["chapter2", "chapter3", "problem1", "problem3_2"]
|
||||
)
|
||||
@@ -1617,11 +1584,11 @@ class TestPublish(SplitModuleTest):
|
||||
expected = ["head12345", "chapter1", "chapter3", "problem1", "problem3_2"]
|
||||
self._check_course(source_course, dest_course, expected, ["chapter2"])
|
||||
# now move problem1 and delete problem3_2
|
||||
chapter1 = modulestore().get_item(BlockUsageLocator.make_relative(source_course, "chapter1"))
|
||||
chapter3 = modulestore().get_item(BlockUsageLocator.make_relative(source_course, "chapter3"))
|
||||
chapter1 = modulestore().get_item(source_course.make_usage_key("chapter", "chapter1"))
|
||||
chapter3 = modulestore().get_item(source_course.make_usage_key("chapter", "chapter3"))
|
||||
chapter1.children.append("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"])
|
||||
expected = ["head12345", "chapter1", "chapter3", "problem1"]
|
||||
self._check_course(source_course, dest_course, expected, ["chapter2", "problem3_2"])
|
||||
@@ -1633,8 +1600,9 @@ class TestPublish(SplitModuleTest):
|
||||
history_info = modulestore().get_course_history_info(dest_course_loc)
|
||||
self.assertEqual(history_info['edited_by'], self.user)
|
||||
for expected in expected_blocks:
|
||||
source = modulestore().get_item(BlockUsageLocator.make_relative(source_course_loc, expected))
|
||||
pub_copy = modulestore().get_item(BlockUsageLocator.make_relative(dest_course_loc, expected))
|
||||
# since block_type has no impact on identity, we can just provide an empty string
|
||||
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
|
||||
self.assertEqual(source.category, pub_copy.category)
|
||||
self.assertEqual(source.update_version, pub_copy.update_version)
|
||||
@@ -1649,7 +1617,7 @@ class TestPublish(SplitModuleTest):
|
||||
self.assertEqual(field.read_from(source), field.read_from(pub_copy))
|
||||
for unexp in unexpected_blocks:
|
||||
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):
|
||||
"""
|
||||
|
||||
@@ -105,6 +105,7 @@ class SplitWMongoCourseBoostrapper(unittest.TestCase):
|
||||
# create pointer for split
|
||||
course_or_parent_locator = BlockUsageLocator(
|
||||
course_key=self.split_course_key,
|
||||
block_type=parent_category,
|
||||
block_id=parent_name
|
||||
)
|
||||
else:
|
||||
|
||||
@@ -37,7 +37,7 @@ class ControllerQueryService(GradingService):
|
||||
def check_combined_notifications(self, course_id, student_id, user_is_staff, last_time_viewed):
|
||||
params = {
|
||||
'student_id': student_id,
|
||||
'course_id': course_id,
|
||||
'course_id': course_id.to_deprecated_string(),
|
||||
'user_is_staff': user_is_staff,
|
||||
'last_time_viewed': last_time_viewed,
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ class PeerGradingService(GradingService):
|
||||
return result
|
||||
|
||||
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)
|
||||
|
||||
if 'problem_list' in result:
|
||||
@@ -100,7 +100,7 @@ class PeerGradingService(GradingService):
|
||||
return result
|
||||
|
||||
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)
|
||||
self._record_result(
|
||||
'get_notifications',
|
||||
|
||||
@@ -182,7 +182,7 @@ def _create_comment(request, course_key, thread_id=None, parent_id=None):
|
||||
'anonymous': anonymous,
|
||||
'anonymous_to_peers': anonymous_to_peers,
|
||||
'user_id': request.user.id,
|
||||
'course_id': course_key,
|
||||
'course_id': course_key.to_deprecated_string(),
|
||||
'thread_id': thread_id,
|
||||
'parent_id': parent_id,
|
||||
})
|
||||
|
||||
@@ -285,7 +285,7 @@ class UserProfileTestCase(ModuleStoreTestCase):
|
||||
StringEndsWithMatcher('/users/{}/active_threads'.format(self.profiled_user.id)),
|
||||
data=None,
|
||||
params=PartialDictMatcher({
|
||||
"course_id": self.course.id,
|
||||
"course_id": self.course.id.to_deprecated_string(),
|
||||
"page": params.get("page", 1),
|
||||
"per_page": views.THREADS_PER_PAGE
|
||||
}),
|
||||
|
||||
@@ -9,7 +9,6 @@ from django.conf import settings
|
||||
from django.http import HttpResponse, Http404
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from xmodule.course_module import CourseDescriptor
|
||||
from xmodule.modulestore.locations import SlashSeparatedCourseKey
|
||||
from xmodule.open_ended_grading_classes.grading_service_module import GradingService, GradingServiceError
|
||||
from xmodule.modulestore.django import ModuleI18nService
|
||||
@@ -116,7 +115,7 @@ class StaffGradingService(GradingService):
|
||||
Raises:
|
||||
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)
|
||||
tags = [u'course_id:{}'.format(course_id)]
|
||||
self._record_result('get_problem_list', result, tags)
|
||||
@@ -148,7 +147,7 @@ class StaffGradingService(GradingService):
|
||||
self.get(
|
||||
self.get_next_url,
|
||||
params={
|
||||
'location': location,
|
||||
'location': location.to_deprecated_string(),
|
||||
'grader_id': grader_id
|
||||
}
|
||||
)
|
||||
@@ -170,7 +169,7 @@ class StaffGradingService(GradingService):
|
||||
Raises:
|
||||
GradingServiceError if there's a problem connecting.
|
||||
"""
|
||||
data = {'course_id': course_id,
|
||||
data = {'course_id': course_id.to_deprecated_string(),
|
||||
'submission_id': submission_id,
|
||||
'score': score,
|
||||
'feedback': feedback,
|
||||
@@ -186,7 +185,7 @@ class StaffGradingService(GradingService):
|
||||
return result
|
||||
|
||||
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)
|
||||
tags = [
|
||||
u'course_id:{}'.format(course_id),
|
||||
@@ -274,7 +273,7 @@ def get_next(request, course_id):
|
||||
', '.join(missing)))
|
||||
grader_id = unique_id_for_user(request.user)
|
||||
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)),
|
||||
mimetype="application/json")
|
||||
@@ -400,7 +399,7 @@ def save_grade(request, course_id):
|
||||
|
||||
grader_id = unique_id_for_user(request.user)
|
||||
|
||||
location = p['location']
|
||||
location = course_key.make_usage_key_from_deprecated_string(p['location'])
|
||||
|
||||
try:
|
||||
result = staff_grading_service().save_grade(course_key,
|
||||
|
||||
@@ -109,13 +109,13 @@ class TestStaffGradingService(ModuleStoreTestCase, LoginEnrollmentTestCase):
|
||||
self.student = 'view@test.com'
|
||||
self.instructor = 'view2@test.com'
|
||||
self.password = 'foo'
|
||||
self.location = 'TestLocation'
|
||||
self.create_account('u1', self.student, self.password)
|
||||
self.create_account('u2', self.instructor, self.password)
|
||||
self.activate_user(self.student)
|
||||
self.activate_user(self.instructor)
|
||||
|
||||
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)
|
||||
|
||||
make_instructor(self.toy, self.instructor)
|
||||
@@ -140,7 +140,7 @@ class TestStaffGradingService(ModuleStoreTestCase, LoginEnrollmentTestCase):
|
||||
self.login(self.instructor, self.password)
|
||||
|
||||
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)
|
||||
|
||||
@@ -165,7 +165,7 @@ class TestStaffGradingService(ModuleStoreTestCase, LoginEnrollmentTestCase):
|
||||
data = {'score': '12',
|
||||
'feedback': 'great!',
|
||||
'submission_id': '123',
|
||||
'location': self.location,
|
||||
'location': self.location_string,
|
||||
'submission_flagged': "true",
|
||||
'rubric_scores[]': ['1', '2']}
|
||||
if skip:
|
||||
@@ -227,7 +227,7 @@ class TestStaffGradingService(ModuleStoreTestCase, LoginEnrollmentTestCase):
|
||||
'score': '12',
|
||||
'feedback': '',
|
||||
'submission_id': '123',
|
||||
'location': self.location,
|
||||
'location': self.location_string,
|
||||
'submission_flagged': "false",
|
||||
'rubric_scores[]': ['1', '2']
|
||||
}
|
||||
@@ -262,13 +262,13 @@ class TestPeerGradingService(ModuleStoreTestCase, LoginEnrollmentTestCase):
|
||||
self.student = 'view@test.com'
|
||||
self.instructor = 'view2@test.com'
|
||||
self.password = 'foo'
|
||||
self.location = 'TestLocation'
|
||||
self.create_account('u1', self.student, self.password)
|
||||
self.create_account('u2', self.instructor, self.password)
|
||||
self.activate_user(self.student)
|
||||
self.activate_user(self.instructor)
|
||||
|
||||
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)
|
||||
location = "i4x://edX/toy/peergrading/init"
|
||||
field_data = DictFieldData({'data': "<peergrading/>", 'location': location, 'category':'peergrading'})
|
||||
@@ -292,7 +292,7 @@ class TestPeerGradingService(ModuleStoreTestCase, LoginEnrollmentTestCase):
|
||||
self.logout()
|
||||
|
||||
def test_get_next_submission_success(self):
|
||||
data = {'location': self.location}
|
||||
data = {'location': self.location_string}
|
||||
|
||||
response = self.peer_module.get_next_submission(data)
|
||||
content = response
|
||||
@@ -312,7 +312,7 @@ class TestPeerGradingService(ModuleStoreTestCase, LoginEnrollmentTestCase):
|
||||
def test_save_grade_success(self):
|
||||
data = {
|
||||
'rubric_scores[]': [0, 0],
|
||||
'location': self.location,
|
||||
'location': self.location_string,
|
||||
'submission_id': 1,
|
||||
'submission_key': 'fake key',
|
||||
'score': 2,
|
||||
@@ -342,7 +342,7 @@ class TestPeerGradingService(ModuleStoreTestCase, LoginEnrollmentTestCase):
|
||||
self.assertTrue(d['error'].find('Missing required keys:') > -1)
|
||||
|
||||
def test_is_calibrated_success(self):
|
||||
data = {'location': self.location}
|
||||
data = {'location': self.location_string}
|
||||
response = self.peer_module.is_student_calibrated(data)
|
||||
|
||||
self.assertTrue(response['success'])
|
||||
@@ -355,7 +355,7 @@ class TestPeerGradingService(ModuleStoreTestCase, LoginEnrollmentTestCase):
|
||||
self.assertFalse('calibrated' in response)
|
||||
|
||||
def test_show_calibration_essay_success(self):
|
||||
data = {'location': self.location}
|
||||
data = {'location': self.location_string}
|
||||
|
||||
response = self.peer_module.show_calibration_essay(data)
|
||||
|
||||
@@ -376,7 +376,7 @@ class TestPeerGradingService(ModuleStoreTestCase, LoginEnrollmentTestCase):
|
||||
def test_save_calibration_essay_success(self):
|
||||
data = {
|
||||
'rubric_scores[]': [0, 0],
|
||||
'location': self.location,
|
||||
'location': self.location_string,
|
||||
'submission_id': 1,
|
||||
'submission_key': 'fake key',
|
||||
'score': 2,
|
||||
@@ -410,7 +410,7 @@ class TestPeerGradingService(ModuleStoreTestCase, LoginEnrollmentTestCase):
|
||||
"""
|
||||
data = {
|
||||
'rubric_scores[]': [0, 0],
|
||||
'location': self.location,
|
||||
'location': self.location_string,
|
||||
'submission_id': 1,
|
||||
'submission_key': 'fake key',
|
||||
'score': 2,
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import logging
|
||||
|
||||
from django.conf import settings
|
||||
from django.views.decorators.cache import cache_control
|
||||
from edxmako.shortcuts import render_to_response
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
from student.models import unique_id_for_user
|
||||
from courseware.courses import get_course_with_access
|
||||
|
||||
from xmodule.open_ended_grading_classes.grading_service_module import GradingServiceError
|
||||
@@ -20,11 +18,11 @@ from xmodule.modulestore import SlashSeparatedCourseKey
|
||||
from xmodule.modulestore.exceptions import NoPathToItem
|
||||
|
||||
from django.http import HttpResponse, Http404, HttpResponseRedirect
|
||||
from edxmako.shortcuts import render_to_string
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from open_ended_grading.utils import (STAFF_ERROR_MESSAGE, STUDENT_ERROR_MESSAGE,
|
||||
StudentProblemList, generate_problem_url, create_controller_query_service)
|
||||
from open_ended_grading.utils import (
|
||||
STAFF_ERROR_MESSAGE, StudentProblemList, generate_problem_url, create_controller_query_service
|
||||
)
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -68,9 +66,10 @@ def staff_grading(request, course_id):
|
||||
"""
|
||||
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', {
|
||||
'course': course,
|
||||
@@ -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
|
||||
xmodule in the course.
|
||||
'''
|
||||
|
||||
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
|
||||
#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)
|
||||
if not found_module:
|
||||
@@ -187,13 +186,11 @@ def flagged_problem_list(request, course_id):
|
||||
'''
|
||||
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
|
||||
course = get_course_with_access(request.user, 'staff', course_key)
|
||||
student_id = unique_id_for_user(request.user)
|
||||
|
||||
# call problem list service
|
||||
success = False
|
||||
error_text = ""
|
||||
problem_list = []
|
||||
base_course_url = reverse('courses')
|
||||
|
||||
# Make a service that can query edX ORA.
|
||||
controller_qs = create_controller_query_service()
|
||||
|
||||
@@ -86,7 +86,7 @@ class User(models.Model):
|
||||
if not self.course_id:
|
||||
raise CommentClientRequestError("Must provide course_id when retrieving active threads for the user")
|
||||
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)
|
||||
response = perform_request(
|
||||
'get',
|
||||
@@ -102,7 +102,7 @@ class User(models.Model):
|
||||
if not self.course_id:
|
||||
raise CommentClientRequestError("Must provide course_id when retrieving subscribed threads for the user")
|
||||
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)
|
||||
response = perform_request(
|
||||
'get',
|
||||
@@ -118,7 +118,7 @@ class User(models.Model):
|
||||
url = self.url(action='get', params=self.attributes)
|
||||
retrieve_params = self.default_retrieve_params
|
||||
if self.attributes.get('course_id'):
|
||||
retrieve_params['course_id'] = self.course_id
|
||||
retrieve_params['course_id'] = self.course_id.to_deprecated_string()
|
||||
try:
|
||||
response = perform_request(
|
||||
'get',
|
||||
|
||||
Reference in New Issue
Block a user