Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
c00f80d4
Commit
c00f80d4
authored
Jun 16, 2014
by
Calen Pennington
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Remove locator.py (now sourced from the external opaque_keys library)
[LMS-2757]
parent
b60b7c7e
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
0 additions
and
824 deletions
+0
-824
common/lib/xmodule/xmodule/modulestore/locator.py
+0
-527
common/lib/xmodule/xmodule/modulestore/tests/test_locators.py
+0
-297
No files found.
common/lib/xmodule/xmodule/modulestore/locator.py
deleted
100644 → 0
View file @
b60b7c7e
"""
Identifier for course resources.
"""
from
__future__
import
absolute_import
import
logging
import
inspect
import
re
from
abc
import
abstractmethod
from
bson.objectid
import
ObjectId
from
bson.errors
import
InvalidId
from
opaque_keys
import
OpaqueKey
,
InvalidKeyError
from
opaque_keys.edx.keys
import
CourseKey
,
UsageKey
,
DefinitionKey
log
=
logging
.
getLogger
(
__name__
)
class
LocalId
(
object
):
"""
Class for local ids for non-persisted xblocks (which can have hardcoded block_ids if necessary)
"""
def
__init__
(
self
,
block_id
=
None
):
self
.
block_id
=
block_id
super
(
LocalId
,
self
)
.
__init__
()
def
__str__
(
self
):
return
"localid_{}"
.
format
(
self
.
block_id
or
id
(
self
))
class
Locator
(
OpaqueKey
):
"""
A locator is like a URL, it refers to a course resource.
Locator is an abstract base class: do not instantiate
"""
BLOCK_TYPE_PREFIX
=
r"type"
# Prefix for the version portion of a locator URL, when it is preceded by a course ID
VERSION_PREFIX
=
r"version"
ALLOWED_ID_CHARS
=
r'[\w\-~.:]'
def
__str__
(
self
):
'''
str(self) returns something like this: "mit.eecs.6002x"
'''
return
unicode
(
self
)
.
encode
(
'utf-8'
)
@abstractmethod
def
version
(
self
):
"""
Returns the ObjectId referencing this specific location.
Raises InvalidKeyError if the instance
doesn't have a complete enough specification.
"""
raise
NotImplementedError
()
@classmethod
def
as_object_id
(
cls
,
value
):
"""
Attempts to cast value as a bson.objectid.ObjectId.
If cast fails, raises ValueError
"""
try
:
return
ObjectId
(
value
)
except
InvalidId
:
raise
ValueError
(
'"
%
s" is not a valid version_guid'
%
value
)
class
BlockLocatorBase
(
Locator
):
# Token separating org from offering
ORG_SEPARATOR
=
'+'
# Prefix for the branch portion of a locator URL
BRANCH_PREFIX
=
r"branch"
# Prefix for the block portion of a locator URL
BLOCK_PREFIX
=
r"block"
ALLOWED_ID_RE
=
re
.
compile
(
r'^'
+
Locator
.
ALLOWED_ID_CHARS
+
'+$'
,
re
.
UNICODE
)
URL_RE_SOURCE
=
r"""
((?P<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
)
@classmethod
def
parse_url
(
cls
,
string
):
"""
Raises InvalidKeyError if string cannot be parsed.
If it can be parsed as a version_guid with no preceding org + offering, returns a dict
with key 'version_guid' and the value,
If it can be parsed as a org + offering, returns a dict
with key 'id' and optional keys 'branch' and 'version_guid'.
"""
match
=
cls
.
URL_RE
.
match
(
string
)
if
not
match
:
raise
InvalidKeyError
(
cls
,
string
)
return
match
.
groupdict
()
class
CourseLocator
(
BlockLocatorBase
,
CourseKey
):
"""
Examples of valid CourseLocator specifications:
CourseLocator(version_guid=ObjectId('519665f6223ebd6980884f2b'))
CourseLocator(org='mit.eecs', offering='6.002x')
CourseLocator(org='mit.eecs', offering='6002x', branch = 'published')
CourseLocator.from_string('course-locator:version+519665f6223ebd6980884f2b')
CourseLocator.from_string('course-locator:mit.eecs+6002x')
CourseLocator.from_string('course-locator:mit.eecs+6002x+branch+published')
CourseLocator.from_string('course-locator:mit.eecs+6002x+branch+published+version+519665f6223ebd6980884f2b')
Should have at least a specific org & offering (id for the course as if it were a project w/
versions) with optional 'branch',
or version_guid (which points to a specific version). Can contain both in which case
the persistence layer may raise exceptions if the given version != the current such version
of the course.
"""
CANONICAL_NAMESPACE
=
'course-locator'
KEY_FIELDS
=
(
'org'
,
'offering'
,
'branch'
,
'version_guid'
)
# stubs to fake out the abstractproperty class instrospection and allow treatment as attrs in instances
org
=
None
offering
=
None
def
__init__
(
self
,
org
=
None
,
offering
=
None
,
branch
=
None
,
version_guid
=
None
):
"""
Construct a CourseLocator
Args:
version_guid (string or ObjectId): optional unique id for the version
org, offering (string): the standard definition. Optional only if version_guid given
branch (string): the branch such as 'draft', 'published', 'staged', 'beta'
"""
if
version_guid
:
version_guid
=
self
.
as_object_id
(
version_guid
)
if
not
all
(
field
is
None
or
self
.
ALLOWED_ID_RE
.
match
(
field
)
for
field
in
[
org
,
offering
,
branch
]):
raise
InvalidKeyError
(
self
.
__class__
,
[
org
,
offering
,
branch
])
super
(
CourseLocator
,
self
)
.
__init__
(
org
=
org
,
offering
=
offering
,
branch
=
branch
,
version_guid
=
version_guid
)
if
self
.
version_guid
is
None
and
(
self
.
org
is
None
or
self
.
offering
is
None
):
raise
InvalidKeyError
(
self
.
__class__
,
"Either version_guid or org and offering should be set"
)
def
version
(
self
):
"""
Returns the ObjectId referencing this specific location.
"""
return
self
.
version_guid
@classmethod
def
_from_string
(
cls
,
serialized
):
"""
Return a CourseLocator parsing the given serialized string
:param serialized: matches the string to a CourseLocator
"""
parse
=
cls
.
parse_url
(
serialized
)
if
parse
[
'version_guid'
]:
parse
[
'version_guid'
]
=
cls
.
as_object_id
(
parse
[
'version_guid'
])
return
cls
(
**
{
key
:
parse
.
get
(
key
)
for
key
in
cls
.
KEY_FIELDS
})
def
html_id
(
self
):
"""
Generate a discussion group id based on course
To make compatible with old Location object functionality. I don't believe this behavior fits at this
place, but I have no way to override. We should clearly define the purpose and restrictions of this
(e.g., I'm assuming periods are fine).
"""
return
unicode
(
self
)
def
make_usage_key
(
self
,
block_type
,
block_id
):
return
BlockUsageLocator
(
course_key
=
self
,
block_type
=
block_type
,
block_id
=
block_id
)
def
make_asset_key
(
self
,
asset_type
,
path
):
raise
NotImplementedError
()
def
version_agnostic
(
self
):
"""
We don't care if the locator's version is not the current head; so, avoid version conflict
by reducing info.
Returns a copy of itself without any version info.
:raises: ValueError if the block locator has no org & offering
"""
return
CourseLocator
(
org
=
self
.
org
,
offering
=
self
.
offering
,
branch
=
self
.
branch
,
version_guid
=
None
)
def
course_agnostic
(
self
):
"""
We only care about the locator's version not its course.
Returns a copy of itself without any course info.
:raises: ValueError if the block locator has no version_guid
"""
return
CourseLocator
(
org
=
None
,
offering
=
None
,
branch
=
None
,
version_guid
=
self
.
version_guid
)
def
for_branch
(
self
,
branch
):
"""
Return a new CourseLocator for another branch of the same course (also version agnostic)
"""
if
self
.
org
is
None
:
raise
InvalidKeyError
(
self
.
__class__
,
"Branches must have full course ids not just versions"
)
return
CourseLocator
(
org
=
self
.
org
,
offering
=
self
.
offering
,
branch
=
branch
,
version_guid
=
None
)
def
for_version
(
self
,
version_guid
):
"""
Return a new CourseLocator for another version of the same course and branch. Usually used
when the head is updated (and thus the course x branch now points to this version)
"""
return
CourseLocator
(
org
=
self
.
org
,
offering
=
self
.
offering
,
branch
=
self
.
branch
,
version_guid
=
version_guid
)
def
_to_string
(
self
):
"""
Return a string representing this location.
"""
parts
=
[]
if
self
.
offering
:
parts
.
extend
([
self
.
org
,
self
.
offering
])
if
self
.
branch
:
parts
.
append
(
u"{prefix}+{branch}"
.
format
(
prefix
=
self
.
BRANCH_PREFIX
,
branch
=
self
.
branch
))
if
self
.
version_guid
:
parts
.
append
(
u"{prefix}+{guid}"
.
format
(
prefix
=
self
.
VERSION_PREFIX
,
guid
=
self
.
version_guid
))
return
u"+"
.
join
(
parts
)
class
BlockUsageLocator
(
BlockLocatorBase
,
UsageKey
):
"""
Encodes a location.
Locations address modules (aka blocks) which are definitions situated in a
course instance. Thus, a Location must identify the course and the occurrence of
the defined element in the course. Courses can be a version of an offering, the
current draft head, or the current production version.
Locators can contain both a version and a org + offering w/ branch. The split mongo functions
may raise errors if these conflict w/ the current db state (i.e., the course's branch !=
the version_guid)
Locations can express as urls as well as dictionaries. They consist of
package_identifier: course_guid | version_guid
block : guid
branch : string
"""
CANONICAL_NAMESPACE
=
'edx'
KEY_FIELDS
=
(
'course_key'
,
'block_type'
,
'block_id'
)
# fake out class instrospection as this is an attr in this class's instances
course_key
=
None
block_type
=
None
def
__init__
(
self
,
course_key
,
block_type
,
block_id
):
"""
Construct a BlockUsageLocator
"""
block_id
=
self
.
_parse_block_ref
(
block_id
)
if
block_id
is
None
:
raise
InvalidKeyError
(
self
.
__class__
,
"Missing block id"
)
super
(
BlockUsageLocator
,
self
)
.
__init__
(
course_key
=
course_key
,
block_type
=
block_type
,
block_id
=
block_id
)
@classmethod
def
_from_string
(
cls
,
serialized
):
"""
Requests CourseLocator to deserialize its part and then adds the local deserialization of block
"""
course_key
=
CourseLocator
.
_from_string
(
serialized
)
parsed_parts
=
cls
.
parse_url
(
serialized
)
block_id
=
parsed_parts
.
get
(
'block_id'
,
None
)
if
block_id
is
None
:
raise
InvalidKeyError
(
cls
,
serialized
)
return
cls
(
course_key
,
parsed_parts
.
get
(
'block_type'
),
block_id
)
def
version_agnostic
(
self
):
"""
We don't care if the locator's version is not the current head; so, avoid version conflict
by reducing info.
Returns a copy of itself without any version info.
:raises: ValueError if the block locator has no org and offering
"""
return
BlockUsageLocator
(
course_key
=
self
.
course_key
.
version_agnostic
(),
block_type
=
self
.
block_type
,
block_id
=
self
.
block_id
,
)
def
course_agnostic
(
self
):
"""
We only care about the locator's version not its course.
Returns a copy of itself without any course info.
:raises: ValueError if the block locator has no version_guid
"""
return
BlockUsageLocator
(
course_key
=
self
.
course_key
.
course_agnostic
(),
block_type
=
self
.
block_type
,
block_id
=
self
.
block_id
)
def
for_branch
(
self
,
branch
):
"""
Return a UsageLocator for the same block in a different branch of the course.
"""
return
BlockUsageLocator
(
self
.
course_key
.
for_branch
(
branch
),
block_type
=
self
.
block_type
,
block_id
=
self
.
block_id
)
def
for_version
(
self
,
version_guid
):
"""
Return a UsageLocator for the same block in a different branch of the course.
"""
return
BlockUsageLocator
(
self
.
course_key
.
for_version
(
version_guid
),
block_type
=
self
.
block_type
,
block_id
=
self
.
block_id
)
@classmethod
def
_parse_block_ref
(
cls
,
block_ref
):
if
isinstance
(
block_ref
,
LocalId
):
return
block_ref
elif
len
(
block_ref
)
>
0
and
cls
.
ALLOWED_ID_RE
.
match
(
block_ref
):
return
block_ref
else
:
raise
InvalidKeyError
(
cls
,
block_ref
)
@property
def
definition_key
(
self
):
raise
NotImplementedError
()
@property
def
org
(
self
):
return
self
.
course_key
.
org
@property
def
offering
(
self
):
return
self
.
course_key
.
offering
@property
def
branch
(
self
):
return
self
.
course_key
.
branch
@property
def
version_guid
(
self
):
return
self
.
course_key
.
version_guid
def
version
(
self
):
return
self
.
course_key
.
version_guid
@property
def
name
(
self
):
"""
The ambiguously named field from Location which code expects to find
"""
return
self
.
block_id
def
is_fully_specified
(
self
):
return
self
.
course_key
.
is_fully_specified
()
@classmethod
def
make_relative
(
cls
,
course_locator
,
block_type
,
block_id
):
"""
Return a new instance which has the given block_id in the given course
:param course_locator: may be a BlockUsageLocator in the same snapshot
"""
if
hasattr
(
course_locator
,
'course_key'
):
course_locator
=
course_locator
.
course_key
return
BlockUsageLocator
(
course_key
=
course_locator
,
block_type
=
block_type
,
block_id
=
block_id
)
def
map_into_course
(
self
,
course_key
):
"""
Return a new instance which has the this block_id in the given course
:param course_key: a CourseKey object representing the new course to map into
"""
return
BlockUsageLocator
.
make_relative
(
course_key
,
self
.
block_type
,
self
.
block_id
)
def
_to_string
(
self
):
"""
Return a string representing this location.
"""
return
u"{course_key}+{BLOCK_TYPE_PREFIX}+{block_type}+{BLOCK_PREFIX}+{block_id}"
.
format
(
course_key
=
self
.
course_key
.
_to_string
(),
BLOCK_TYPE_PREFIX
=
self
.
BLOCK_TYPE_PREFIX
,
block_type
=
self
.
block_type
,
BLOCK_PREFIX
=
self
.
BLOCK_PREFIX
,
block_id
=
self
.
block_id
)
def
html_id
(
self
):
"""
Generate a discussion group id based on course
To make compatible with old Location object functionality. I don't believe this behavior fits at this
place, but I have no way to override. We should clearly define the purpose and restrictions of this
(e.g., I'm assuming periods are fine).
"""
return
unicode
(
self
)
class
DefinitionLocator
(
Locator
,
DefinitionKey
):
"""
Container for how to locate a description (the course-independent content).
"""
CANONICAL_NAMESPACE
=
'defx'
KEY_FIELDS
=
(
'definition_id'
,
'block_type'
)
# override the abstractproperty
block_type
=
None
definition_id
=
None
def
__init__
(
self
,
block_type
,
definition_id
):
if
isinstance
(
definition_id
,
LocalId
):
super
(
DefinitionLocator
,
self
)
.
__init__
(
definition_id
=
definition_id
,
block_type
=
block_type
)
elif
isinstance
(
definition_id
,
basestring
):
try
:
definition_id
=
self
.
as_object_id
(
definition_id
)
except
ValueError
:
raise
InvalidKeyError
(
self
,
definition_id
)
super
(
DefinitionLocator
,
self
)
.
__init__
(
definition_id
=
definition_id
,
block_type
=
block_type
)
elif
isinstance
(
definition_id
,
ObjectId
):
super
(
DefinitionLocator
,
self
)
.
__init__
(
definition_id
=
definition_id
,
block_type
=
block_type
)
def
_to_string
(
self
):
'''
Return a string representing this location.
unicode(self) returns something like this: "519665f6223ebd6980884f2b+type+problem"
'''
return
u"{}+{}+{}"
.
format
(
unicode
(
self
.
definition_id
),
self
.
BLOCK_TYPE_PREFIX
,
self
.
block_type
)
URL_RE
=
re
.
compile
(
r"^(?P<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 DefinitionLocator parsing the given serialized string
:param serialized: matches the string to
"""
parse
=
cls
.
URL_RE
.
match
(
serialized
)
if
not
parse
:
raise
InvalidKeyError
(
cls
,
serialized
)
parse
=
parse
.
groupdict
()
if
parse
[
'definition_id'
]:
parse
[
'definition_id'
]
=
cls
.
as_object_id
(
parse
[
'definition_id'
])
return
cls
(
**
{
key
:
parse
.
get
(
key
)
for
key
in
cls
.
KEY_FIELDS
})
def
version
(
self
):
"""
Returns the ObjectId referencing this specific location.
"""
return
self
.
definition_id
class
VersionTree
(
object
):
"""
Holds trees of Locators to represent version histories.
"""
def
__init__
(
self
,
locator
,
tree_dict
=
None
):
"""
:param locator: must be version specific (Course has version_guid or definition had id)
"""
if
not
isinstance
(
locator
,
Locator
)
and
not
inspect
.
isabstract
(
locator
):
raise
TypeError
(
"locator {} must be a concrete subclass of Locator"
.
format
(
locator
))
if
not
locator
.
version
():
raise
ValueError
(
"locator must be version specific (Course has version_guid or definition had id)"
)
self
.
locator
=
locator
if
tree_dict
is
None
:
self
.
children
=
[]
else
:
self
.
children
=
[
VersionTree
(
child
,
tree_dict
)
for
child
in
tree_dict
.
get
(
locator
.
version
(),
[])]
common/lib/xmodule/xmodule/modulestore/tests/test_locators.py
deleted
100644 → 0
View file @
b60b7c7e
"""
Tests for opaque_keys.edx.locator.
"""
from
unittest
import
TestCase
import
random
from
bson.objectid
import
ObjectId
from
opaque_keys
import
InvalidKeyError
from
opaque_keys.edx.locator
import
Locator
,
CourseLocator
,
BlockUsageLocator
,
DefinitionLocator
from
ddt
import
ddt
,
data
from
opaque_keys.edx.keys
import
UsageKey
,
CourseKey
,
DefinitionKey
@ddt
class
LocatorTest
(
TestCase
):
"""
Tests for subclasses of Locator.
"""
def
test_cant_instantiate_abstract_class
(
self
):
self
.
assertRaises
(
TypeError
,
Locator
)
def
test_course_constructor_underspecified
(
self
):
with
self
.
assertRaises
(
InvalidKeyError
):
CourseLocator
()
with
self
.
assertRaises
(
InvalidKeyError
):
CourseLocator
(
branch
=
'published'
)
def
test_course_constructor_bad_version_guid
(
self
):
with
self
.
assertRaises
(
ValueError
):
CourseLocator
(
version_guid
=
"012345"
)
with
self
.
assertRaises
(
InvalidKeyError
):
CourseLocator
(
version_guid
=
None
)
def
test_course_constructor_version_guid
(
self
):
# generate a random location
test_id_1
=
ObjectId
()
test_id_1_loc
=
str
(
test_id_1
)
testobj_1
=
CourseLocator
(
version_guid
=
test_id_1
)
self
.
check_course_locn_fields
(
testobj_1
,
version_guid
=
test_id_1
)
self
.
assertEqual
(
str
(
testobj_1
.
version_guid
),
test_id_1_loc
)
self
.
assertEqual
(
testobj_1
.
_to_string
(),
u'+'
.
join
((
testobj_1
.
VERSION_PREFIX
,
test_id_1_loc
)))
# Test using a given string
test_id_2_loc
=
'519665f6223ebd6980884f2b'
test_id_2
=
ObjectId
(
test_id_2_loc
)
testobj_2
=
CourseLocator
(
version_guid
=
test_id_2
)
self
.
check_course_locn_fields
(
testobj_2
,
version_guid
=
test_id_2
)
self
.
assertEqual
(
str
(
testobj_2
.
version_guid
),
test_id_2_loc
)
self
.
assertEqual
(
testobj_2
.
_to_string
(),
u'+'
.
join
((
testobj_2
.
VERSION_PREFIX
,
test_id_2_loc
)))
@data
(
' mit.eecs'
,
'mit.eecs '
,
CourseLocator
.
VERSION_PREFIX
+
'+mit.eecs'
,
BlockUsageLocator
.
BLOCK_PREFIX
+
'+black+mit.eecs'
,
'mit.ee cs'
,
'mit.ee,cs'
,
'mit.ee+cs'
,
'mit.ee&cs'
,
'mit.ee()cs'
,
CourseLocator
.
BRANCH_PREFIX
+
'+this'
,
'mit.eecs+'
+
CourseLocator
.
BRANCH_PREFIX
,
'mit.eecs+'
+
CourseLocator
.
BRANCH_PREFIX
+
'+this+'
+
CourseLocator
.
BRANCH_PREFIX
+
'+that'
,
'mit.eecs+'
+
CourseLocator
.
BRANCH_PREFIX
+
'+this+'
+
CourseLocator
.
BRANCH_PREFIX
,
'mit.eecs+'
+
CourseLocator
.
BRANCH_PREFIX
+
'+this '
,
'mit.eecs+'
+
CourseLocator
.
BRANCH_PREFIX
+
'+th
%
is '
,
)
def
test_course_constructor_bad_package_id
(
self
,
bad_id
):
"""
Test all sorts of badly-formed package_ids (and urls with those package_ids)
"""
with
self
.
assertRaises
(
InvalidKeyError
):
CourseLocator
(
org
=
bad_id
,
offering
=
'test'
)
with
self
.
assertRaises
(
InvalidKeyError
):
CourseLocator
(
org
=
'test'
,
offering
=
bad_id
)
with
self
.
assertRaises
(
InvalidKeyError
):
CourseKey
.
from_string
(
'course-locator:test+{}'
.
format
(
bad_id
))
@data
(
'course-locator:'
,
'course-locator:/mit.eecs'
,
'http:mit.eecs'
,
'course-locator//mit.eecs'
)
def
test_course_constructor_bad_url
(
self
,
bad_url
):
with
self
.
assertRaises
(
InvalidKeyError
):
CourseKey
.
from_string
(
bad_url
)
def
test_course_constructor_url
(
self
):
# Test parsing a url when it starts with a version ID and there is also a block ID.
# This hits the parsers parse_guid method.
test_id_loc
=
'519665f6223ebd6980884f2b'
testobj
=
CourseKey
.
from_string
(
"course-locator:{}+{}+{}+hw3"
.
format
(
CourseLocator
.
VERSION_PREFIX
,
test_id_loc
,
CourseLocator
.
BLOCK_PREFIX
))
self
.
check_course_locn_fields
(
testobj
,
version_guid
=
ObjectId
(
test_id_loc
)
)
def
test_course_constructor_url_package_id_and_version_guid
(
self
):
test_id_loc
=
'519665f6223ebd6980884f2b'
testobj
=
CourseKey
.
from_string
(
'course-locator:mit.eecs+honors.6002x+{}+{}'
.
format
(
CourseLocator
.
VERSION_PREFIX
,
test_id_loc
)
)
self
.
check_course_locn_fields
(
testobj
,
org
=
'mit.eecs'
,
offering
=
'honors.6002x'
,
version_guid
=
ObjectId
(
test_id_loc
)
)
def
test_course_constructor_url_package_id_branch_and_version_guid
(
self
):
test_id_loc
=
'519665f6223ebd6980884f2b'
org
=
'mit.eecs'
offering
=
'~6002x'
testobj
=
CourseKey
.
from_string
(
'course-locator:{}+{}+{}+draft-1+{}+{}'
.
format
(
org
,
offering
,
CourseLocator
.
BRANCH_PREFIX
,
CourseLocator
.
VERSION_PREFIX
,
test_id_loc
))
self
.
check_course_locn_fields
(
testobj
,
org
=
org
,
offering
=
offering
,
branch
=
'draft-1'
,
version_guid
=
ObjectId
(
test_id_loc
)
)
def
test_course_constructor_package_id_no_branch
(
self
):
org
=
'mit.eecs'
offering
=
'6002x'
testurn
=
'{}+{}'
.
format
(
org
,
offering
)
testobj
=
CourseLocator
(
org
=
org
,
offering
=
offering
)
self
.
check_course_locn_fields
(
testobj
,
org
=
org
,
offering
=
offering
)
self
.
assertEqual
(
testobj
.
_to_string
(),
testurn
)
def
test_course_constructor_package_id_separate_branch
(
self
):
org
=
'mit.eecs'
offering
=
'6002x'
test_branch
=
'published'
expected_urn
=
'{}+{}+{}+{}'
.
format
(
org
,
offering
,
CourseLocator
.
BRANCH_PREFIX
,
test_branch
)
testobj
=
CourseLocator
(
org
=
org
,
offering
=
offering
,
branch
=
test_branch
)
self
.
check_course_locn_fields
(
testobj
,
org
=
org
,
offering
=
offering
,
branch
=
test_branch
,
)
self
.
assertEqual
(
testobj
.
branch
,
test_branch
)
self
.
assertEqual
(
testobj
.
_to_string
(),
expected_urn
)
def
test_block_constructor
(
self
):
expected_org
=
'mit.eecs'
expected_offering
=
'6002x'
expected_branch
=
'published'
expected_block_ref
=
'HW3'
testurn
=
'edx:{}+{}+{}+{}+{}+{}+{}+{}'
.
format
(
expected_org
,
expected_offering
,
CourseLocator
.
BRANCH_PREFIX
,
expected_branch
,
BlockUsageLocator
.
BLOCK_TYPE_PREFIX
,
'problem'
,
BlockUsageLocator
.
BLOCK_PREFIX
,
'HW3'
)
testobj
=
UsageKey
.
from_string
(
testurn
)
self
.
check_block_locn_fields
(
testobj
,
org
=
expected_org
,
offering
=
expected_offering
,
branch
=
expected_branch
,
block_type
=
'problem'
,
block
=
expected_block_ref
)
self
.
assertEqual
(
unicode
(
testobj
),
testurn
)
testobj
=
testobj
.
for_version
(
ObjectId
())
agnostic
=
testobj
.
version_agnostic
()
self
.
assertIsNone
(
agnostic
.
version_guid
)
self
.
check_block_locn_fields
(
agnostic
,
org
=
expected_org
,
offering
=
expected_offering
,
branch
=
expected_branch
,
block
=
expected_block_ref
)
def
test_block_constructor_url_version_prefix
(
self
):
test_id_loc
=
'519665f6223ebd6980884f2b'
testobj
=
UsageKey
.
from_string
(
'edx:mit.eecs+6002x+{}+{}+{}+problem+{}+lab2'
.
format
(
CourseLocator
.
VERSION_PREFIX
,
test_id_loc
,
BlockUsageLocator
.
BLOCK_TYPE_PREFIX
,
BlockUsageLocator
.
BLOCK_PREFIX
)
)
self
.
check_block_locn_fields
(
testobj
,
org
=
'mit.eecs'
,
offering
=
'6002x'
,
block_type
=
'problem'
,
block
=
'lab2'
,
version_guid
=
ObjectId
(
test_id_loc
)
)
agnostic
=
testobj
.
course_agnostic
()
self
.
check_block_locn_fields
(
agnostic
,
block
=
'lab2'
,
org
=
None
,
offering
=
None
,
version_guid
=
ObjectId
(
test_id_loc
)
)
self
.
assertIsNone
(
agnostic
.
offering
)
self
.
assertIsNone
(
agnostic
.
org
)
def
test_block_constructor_url_kitchen_sink
(
self
):
test_id_loc
=
'519665f6223ebd6980884f2b'
testobj
=
UsageKey
.
from_string
(
'edx:mit.eecs+6002x+{}+draft+{}+{}+{}+problem+{}+lab2'
.
format
(
CourseLocator
.
BRANCH_PREFIX
,
CourseLocator
.
VERSION_PREFIX
,
test_id_loc
,
BlockUsageLocator
.
BLOCK_TYPE_PREFIX
,
BlockUsageLocator
.
BLOCK_PREFIX
)
)
self
.
check_block_locn_fields
(
testobj
,
org
=
'mit.eecs'
,
offering
=
'6002x'
,
branch
=
'draft'
,
block
=
'lab2'
,
version_guid
=
ObjectId
(
test_id_loc
)
)
def
test_colon_name
(
self
):
"""
It seems we used to use colons in names; so, ensure they're acceptable.
"""
org
=
'mit.eecs'
offering
=
'1'
branch
=
'foo'
block_id
=
'problem:with-colon~2'
testobj
=
BlockUsageLocator
(
CourseLocator
(
org
=
org
,
offering
=
offering
,
branch
=
branch
),
block_type
=
'problem'
,
block_id
=
block_id
)
self
.
check_block_locn_fields
(
testobj
,
org
=
org
,
offering
=
offering
,
branch
=
branch
,
block
=
block_id
)
def
test_relative
(
self
):
"""
Test making a relative usage locator.
"""
org
=
'mit.eecs'
offering
=
'1'
branch
=
'foo'
baseobj
=
CourseLocator
(
org
=
org
,
offering
=
offering
,
branch
=
branch
)
block_id
=
'problem:with-colon~2'
testobj
=
BlockUsageLocator
.
make_relative
(
baseobj
,
'problem'
,
block_id
)
self
.
check_block_locn_fields
(
testobj
,
org
=
org
,
offering
=
offering
,
branch
=
branch
,
block
=
block_id
)
block_id
=
'completely_different'
testobj
=
BlockUsageLocator
.
make_relative
(
testobj
,
'problem'
,
block_id
)
self
.
check_block_locn_fields
(
testobj
,
org
=
org
,
offering
=
offering
,
branch
=
branch
,
block
=
block_id
)
def
test_repr
(
self
):
testurn
=
u'edx:mit.eecs+6002x+{}+published+{}+problem+{}+HW3'
.
format
(
CourseLocator
.
BRANCH_PREFIX
,
BlockUsageLocator
.
BLOCK_TYPE_PREFIX
,
BlockUsageLocator
.
BLOCK_PREFIX
)
testobj
=
UsageKey
.
from_string
(
testurn
)
self
.
assertEqual
(
"BlockUsageLocator(CourseLocator(u'mit.eecs', u'6002x', u'published', None), u'problem', u'HW3')"
,
repr
(
testobj
))
def
test_description_locator_url
(
self
):
object_id
=
'{:024x}'
.
format
(
random
.
randrange
(
16
**
24
))
definition_locator
=
DefinitionLocator
(
'html'
,
object_id
)
self
.
assertEqual
(
'defx:{}+{}+html'
.
format
(
object_id
,
DefinitionLocator
.
BLOCK_TYPE_PREFIX
),
unicode
(
definition_locator
))
self
.
assertEqual
(
definition_locator
,
DefinitionKey
.
from_string
(
unicode
(
definition_locator
)))
def
test_description_locator_version
(
self
):
object_id
=
'{:024x}'
.
format
(
random
.
randrange
(
16
**
24
))
definition_locator
=
DefinitionLocator
(
'html'
,
object_id
)
self
.
assertEqual
(
object_id
,
str
(
definition_locator
.
version
()))
# ------------------------------------------------------------------
# Utilities
def
check_course_locn_fields
(
self
,
testobj
,
version_guid
=
None
,
org
=
None
,
offering
=
None
,
branch
=
None
):
"""
Checks the version, org, offering, and branch in testobj
"""
self
.
assertEqual
(
testobj
.
version_guid
,
version_guid
)
self
.
assertEqual
(
testobj
.
org
,
org
)
self
.
assertEqual
(
testobj
.
offering
,
offering
)
self
.
assertEqual
(
testobj
.
branch
,
branch
)
def
check_block_locn_fields
(
self
,
testobj
,
version_guid
=
None
,
org
=
None
,
offering
=
None
,
branch
=
None
,
block_type
=
None
,
block
=
None
):
"""
Does adds a block id check over and above the check_course_locn_fields tests
"""
self
.
check_course_locn_fields
(
testobj
,
version_guid
,
org
,
offering
,
branch
)
if
block_type
is
not
None
:
self
.
assertEqual
(
testobj
.
block_type
,
block_type
)
self
.
assertEqual
(
testobj
.
block_id
,
block
)
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment