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
0ded6699
Commit
0ded6699
authored
Oct 28, 2014
by
Braden MacDonald
Committed by
E. Kolpakov
Dec 04, 2014
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Split mongo support for libraries and Library XBlock
parent
97d357cd
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
552 additions
and
47 deletions
+552
-47
common/djangoapps/xmodule_django/models.py
+2
-2
common/lib/xmodule/setup.py
+4
-1
common/lib/xmodule/xmodule/library_root_xblock.py
+92
-0
common/lib/xmodule/xmodule/modulestore/__init__.py
+1
-0
common/lib/xmodule/xmodule/modulestore/mixed.py
+65
-0
common/lib/xmodule/xmodule/modulestore/mongo/base.py
+3
-1
common/lib/xmodule/xmodule/modulestore/split_mongo/caching_descriptor_system.py
+6
-4
common/lib/xmodule/xmodule/modulestore/split_mongo/split.py
+0
-0
common/lib/xmodule/xmodule/modulestore/split_mongo/split_draft.py
+19
-6
common/lib/xmodule/xmodule/modulestore/tests/factories.py
+30
-0
common/lib/xmodule/xmodule/modulestore/tests/test_libraries.py
+207
-0
common/lib/xmodule/xmodule/modulestore/tests/test_mixed_modulestore.py
+1
-32
common/lib/xmodule/xmodule/modulestore/tests/test_mongo.py
+11
-0
common/lib/xmodule/xmodule/modulestore/tests/utils.py
+110
-0
requirements/edx/github.txt
+1
-1
No files found.
common/djangoapps/xmodule_django/models.py
View file @
0ded6699
...
...
@@ -68,7 +68,7 @@ def _strip_value(value, lookup='exact'):
class
CourseKeyField
(
models
.
CharField
):
description
=
"A
SlashSeparated
CourseKey object, saved to the DB in the form of a string"
description
=
"A CourseKey object, saved to the DB in the form of a string"
__metaclass__
=
models
.
SubfieldBase
...
...
@@ -84,7 +84,7 @@ class CourseKeyField(models.CharField):
return
None
if
isinstance
(
value
,
basestring
):
return
SlashSeparatedCourseKey
.
from_deprecated
_string
(
value
)
return
CourseKey
.
from
_string
(
value
)
else
:
return
value
...
...
common/lib/xmodule/setup.py
View file @
0ded6699
...
...
@@ -44,6 +44,9 @@ XMODULES = [
"crowdsource_hinter = xmodule.crowdsource_hinter:CrowdsourceHinterDescriptor"
,
"lti = xmodule.lti_module:LTIDescriptor"
,
]
XBLOCKS
=
[
"library = xmodule.library_root_xblock:LibraryRoot"
,
]
setup
(
name
=
"XModule"
,
...
...
@@ -64,7 +67,7 @@ setup(
# See http://guide.python-distribute.org/creation.html#entry-points
# for a description of entry_points
entry_points
=
{
'xblock.v1'
:
XMODULES
,
'xblock.v1'
:
XMODULES
+
XBLOCKS
,
'xmodule.v1'
:
XMODULES
,
'console_scripts'
:
[
'xmodule_assets = xmodule.static_content:main'
,
...
...
common/lib/xmodule/xmodule/library_root_xblock.py
0 → 100644
View file @
0ded6699
"""
'library' XBlock (LibraryRoot)
"""
import
logging
from
.studio_editable
import
StudioEditableModule
from
xblock.core
import
XBlock
from
xblock.fields
import
Scope
,
String
,
List
from
xblock.fragment
import
Fragment
log
=
logging
.
getLogger
(
__name__
)
# Make '_' a no-op so we can scrape strings
_
=
lambda
text
:
text
class
LibraryRoot
(
XBlock
):
"""
The LibraryRoot is the root XBlock of a content library. All other blocks in
the library are its children. It contains metadata such as the library's
display_name.
"""
display_name
=
String
(
help
=
_
(
"Enter the name of the library as it should appear in Studio."
),
default
=
"Library"
,
display_name
=
_
(
"Library Display Name"
),
scope
=
Scope
.
settings
)
advanced_modules
=
List
(
display_name
=
_
(
"Advanced Module List"
),
help
=
_
(
"Enter the names of the advanced components to use in your library."
),
scope
=
Scope
.
settings
)
has_children
=
True
has_author_view
=
True
def
__unicode__
(
self
):
return
u"Library: {}"
.
format
(
self
.
display_name
)
def
__str__
(
self
):
return
unicode
(
self
)
.
encode
(
'utf-8'
)
def
author_view
(
self
,
context
):
"""
Renders the Studio preview view, which supports drag and drop.
"""
fragment
=
Fragment
()
contents
=
[]
for
child_key
in
self
.
children
:
# pylint: disable=E1101
context
[
'reorderable_items'
]
.
add
(
child_key
)
child
=
self
.
runtime
.
get_block
(
child_key
)
rendered_child
=
self
.
runtime
.
render_child
(
child
,
StudioEditableModule
.
get_preview_view_name
(
child
),
context
)
fragment
.
add_frag_resources
(
rendered_child
)
contents
.
append
({
'id'
:
unicode
(
child_key
),
'content'
:
rendered_child
.
content
,
})
fragment
.
add_content
(
self
.
runtime
.
render_template
(
"studio_render_children_view.html"
,
{
'items'
:
contents
,
'xblock_context'
:
context
,
'can_add'
:
True
,
'can_reorder'
:
True
,
}))
return
fragment
@property
def
display_org_with_default
(
self
):
"""
Org display names are not implemented. This just provides API compatibility with CourseDescriptor.
Always returns the raw 'org' field from the key.
"""
return
self
.
scope_ids
.
usage_id
.
course_key
.
org
@property
def
display_number_with_default
(
self
):
"""
Display numbers are not implemented. This just provides API compatibility with CourseDescriptor.
Always returns the raw 'library' field from the key.
"""
return
self
.
scope_ids
.
usage_id
.
course_key
.
library
@classmethod
def
parse_xml
(
cls
,
xml_data
,
system
,
id_generator
,
**
kwargs
):
""" XML support not yet implemented. """
raise
NotImplementedError
def
add_xml_to_node
(
self
,
resource_fs
):
""" XML support not yet implemented. """
raise
NotImplementedError
common/lib/xmodule/xmodule/modulestore/__init__.py
View file @
0ded6699
...
...
@@ -82,6 +82,7 @@ class ModuleStoreEnum(object):
"""
draft
=
'draft-branch'
published
=
'published-branch'
library
=
'library'
class
UserID
(
object
):
"""
...
...
common/lib/xmodule/xmodule/modulestore/mixed.py
View file @
0ded6699
...
...
@@ -13,6 +13,7 @@ from contracts import contract, new_contract
from
opaque_keys
import
InvalidKeyError
from
opaque_keys.edx.keys
import
CourseKey
,
AssetKey
from
opaque_keys.edx.locator
import
LibraryLocator
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
from
xmodule.assetstore
import
AssetMetadata
...
...
@@ -25,6 +26,7 @@ from .split_migrator import SplitMigrator
new_contract
(
'CourseKey'
,
CourseKey
)
new_contract
(
'AssetKey'
,
AssetKey
)
new_contract
(
'AssetMetadata'
,
AssetMetadata
)
new_contract
(
'LibraryLocator'
,
LibraryLocator
)
log
=
logging
.
getLogger
(
__name__
)
...
...
@@ -259,6 +261,23 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
courses
[
course_id
]
=
course
return
courses
.
values
()
@strip_key
def
get_libraries
(
self
,
**
kwargs
):
"""
Returns a list containing the top level XBlock of the libraries (LibraryRoot) in this modulestore.
"""
libraries
=
{}
for
store
in
self
.
modulestores
:
if
not
hasattr
(
store
,
'get_libraries'
):
continue
# filter out ones which were fetched from earlier stores but locations may not be ==
for
course
in
store
.
get_libraries
(
**
kwargs
):
course_id
=
self
.
_clean_course_id_for_mapping
(
course
.
location
)
if
course_id
not
in
libraries
:
# course is indeed unique. save it in result
libraries
[
course_id
]
=
course
return
libraries
.
values
()
def
make_course_key
(
self
,
org
,
course
,
run
):
"""
Return a valid :class:`~opaque_keys.edx.keys.CourseKey` for this modulestore
...
...
@@ -291,6 +310,24 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
return
None
@strip_key
@contract
(
library_key
=
'LibraryLocator'
)
def
get_library
(
self
,
library_key
,
depth
=
0
,
**
kwargs
):
"""
returns the library block associated with the given key. If no such library exists,
it returns None
:param library_key: must be a LibraryLocator
"""
try
:
store
=
self
.
_verify_modulestore_support
(
library_key
,
'get_library'
)
return
store
.
get_library
(
library_key
,
depth
=
depth
,
**
kwargs
)
except
NotImplementedError
:
log
.
exception
(
"Modulestore configured for
%
s does not have get_library method"
,
library_key
)
return
None
except
ItemNotFoundError
:
return
None
@strip_key
def
has_course
(
self
,
course_id
,
ignore_case
=
False
,
**
kwargs
):
"""
returns the course_id of the course if it was found, else None
...
...
@@ -508,6 +545,34 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
return
course
@strip_key
def
create_library
(
self
,
org
,
library
,
user_id
,
fields
,
**
kwargs
):
"""
Creates and returns a new library.
Args:
org (str): the organization that owns the course
library (str): the code/number/name of the library
user_id: id of the user creating the course
fields (dict): Fields to set on the course at initialization - e.g. display_name
kwargs: Any optional arguments understood by a subset of modulestores to customize instantiation
Returns: a LibraryRoot
"""
# first make sure an existing course/lib doesn't already exist in the mapping
lib_key
=
LibraryLocator
(
org
=
org
,
library
=
library
)
if
lib_key
in
self
.
mappings
:
raise
DuplicateCourseError
(
lib_key
,
lib_key
)
# create the library
store
=
self
.
_verify_modulestore_support
(
None
,
'create_library'
)
library
=
store
.
create_library
(
org
,
library
,
user_id
,
fields
,
**
kwargs
)
# add new library to the mapping
self
.
mappings
[
lib_key
]
=
store
return
library
@strip_key
def
clone_course
(
self
,
source_course_id
,
dest_course_id
,
user_id
,
fields
=
None
,
**
kwargs
):
"""
See the superclass for the general documentation.
...
...
common/lib/xmodule/xmodule/modulestore/mongo/base.py
View file @
0ded6699
...
...
@@ -33,7 +33,7 @@ from importlib import import_module
from
opaque_keys.edx.keys
import
UsageKey
,
CourseKey
,
AssetKey
from
opaque_keys.edx.locations
import
Location
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
from
opaque_keys.edx.locator
import
CourseLocator
from
opaque_keys.edx.locator
import
CourseLocator
,
LibraryLocator
from
xblock.core
import
XBlock
from
xblock.exceptions
import
InvalidScopeError
...
...
@@ -875,6 +875,8 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase, Mongo
otherwise, do a case sensitive search
"""
assert
(
isinstance
(
course_key
,
CourseKey
))
if
isinstance
(
course_key
,
LibraryLocator
):
return
None
# Libraries require split mongo
course_key
=
self
.
fill_in_run
(
course_key
)
location
=
course_key
.
make_usage_key
(
'course'
,
course_key
.
run
)
if
ignore_case
:
...
...
common/lib/xmodule/xmodule/modulestore/split_mongo/caching_descriptor_system.py
View file @
0ded6699
...
...
@@ -4,7 +4,7 @@ from contracts import contract, new_contract
from
lazy
import
lazy
from
xblock.runtime
import
KvsFieldData
from
xblock.fields
import
ScopeIds
from
opaque_keys.edx.locator
import
BlockUsageLocator
,
LocalId
,
CourseLocator
,
DefinitionLocator
from
opaque_keys.edx.locator
import
BlockUsageLocator
,
LocalId
,
CourseLocator
,
LibraryLocator
,
DefinitionLocator
from
xmodule.mako_module
import
MakoDescriptorSystem
from
xmodule.error_module
import
ErrorDescriptor
from
xmodule.errortracker
import
exc_info_to_str
...
...
@@ -19,6 +19,8 @@ from xmodule.modulestore.split_mongo import BlockKey, CourseEnvelope
log
=
logging
.
getLogger
(
__name__
)
new_contract
(
'BlockUsageLocator'
,
BlockUsageLocator
)
new_contract
(
'CourseLocator'
,
CourseLocator
)
new_contract
(
'LibraryLocator'
,
LibraryLocator
)
new_contract
(
'BlockKey'
,
BlockKey
)
new_contract
(
'CourseEnvelope'
,
CourseEnvelope
)
...
...
@@ -115,7 +117,7 @@ class CachingDescriptorSystem(MakoDescriptorSystem, EditInfoRuntimeMixin):
self
.
modulestore
.
cache_block
(
course_key
,
version_guid
,
block_key
,
block
)
return
block
@contract
(
block_key
=
BlockKey
,
course_key
=
CourseLocator
)
@contract
(
block_key
=
BlockKey
,
course_key
=
"CourseLocator | LibraryLocator"
)
def
get_module_data
(
self
,
block_key
,
course_key
):
"""
Get block from module_data adding it to module_data if it's not already there but is in the structure
...
...
@@ -178,8 +180,8 @@ class CachingDescriptorSystem(MakoDescriptorSystem, EditInfoRuntimeMixin):
if
definition_id
is
None
:
definition_id
=
LocalId
()
block_locator
=
BlockUsageLocator
(
course_key
,
# Construct the Block Usage Locator:
block_locator
=
course_key
.
make_usage_key
(
block_type
=
block_key
.
type
,
block_id
=
block_key
.
id
,
)
...
...
common/lib/xmodule/xmodule/modulestore/split_mongo/split.py
View file @
0ded6699
This diff is collapsed.
Click to expand it.
common/lib/xmodule/xmodule/modulestore/split_mongo/split_draft.py
View file @
0ded6699
...
...
@@ -9,7 +9,7 @@ from xmodule.modulestore.exceptions import InsufficientSpecificationError
from
xmodule.modulestore.draft_and_published
import
(
ModuleStoreDraftAndPublished
,
DIRECT_ONLY_CATEGORIES
,
UnsupportedRevisionError
)
from
opaque_keys.edx.locator
import
CourseLocator
from
opaque_keys.edx.locator
import
CourseLocator
,
LibraryLocator
,
LibraryUsageLocator
from
xmodule.modulestore.split_mongo
import
BlockKey
from
contracts
import
contract
...
...
@@ -57,6 +57,10 @@ class DraftVersioningModuleStore(SplitMongoModuleStore, ModuleStoreDraftAndPubli
course_id
=
self
.
_map_revision_to_branch
(
course_id
)
return
super
(
DraftVersioningModuleStore
,
self
)
.
get_course
(
course_id
,
depth
=
depth
,
**
kwargs
)
def
get_library
(
self
,
library_id
,
depth
=
0
,
**
kwargs
):
library_id
=
self
.
_map_revision_to_branch
(
library_id
)
return
super
(
DraftVersioningModuleStore
,
self
)
.
get_library
(
library_id
,
depth
=
depth
,
**
kwargs
)
def
clone_course
(
self
,
source_course_id
,
dest_course_id
,
user_id
,
fields
=
None
,
revision
=
None
,
**
kwargs
):
"""
See :py:meth: xmodule.modulestore.split_mongo.split.SplitMongoModuleStore.clone_course
...
...
@@ -153,7 +157,9 @@ class DraftVersioningModuleStore(SplitMongoModuleStore, ModuleStoreDraftAndPubli
Otherwise, raises a ValueError.
"""
with
self
.
bulk_operations
(
location
.
course_key
):
if
revision
==
ModuleStoreEnum
.
RevisionOption
.
published_only
:
if
isinstance
(
location
,
LibraryUsageLocator
):
branches_to_delete
=
[
ModuleStoreEnum
.
BranchName
.
library
]
# Libraries don't yet have draft/publish support
elif
revision
==
ModuleStoreEnum
.
RevisionOption
.
published_only
:
branches_to_delete
=
[
ModuleStoreEnum
.
BranchName
.
published
]
elif
revision
==
ModuleStoreEnum
.
RevisionOption
.
all
:
branches_to_delete
=
[
ModuleStoreEnum
.
BranchName
.
published
,
ModuleStoreEnum
.
BranchName
.
draft
]
...
...
@@ -180,18 +186,25 @@ class DraftVersioningModuleStore(SplitMongoModuleStore, ModuleStoreDraftAndPubli
"""
Maps RevisionOptions to BranchNames, inserting them into the key
"""
if
isinstance
(
key
,
(
LibraryLocator
,
LibraryUsageLocator
)):
# Libraries don't yet have draft/publish support:
draft_branch
=
ModuleStoreEnum
.
BranchName
.
library
published_branch
=
ModuleStoreEnum
.
BranchName
.
library
else
:
draft_branch
=
ModuleStoreEnum
.
BranchName
.
draft
published_branch
=
ModuleStoreEnum
.
BranchName
.
published
if
revision
==
ModuleStoreEnum
.
RevisionOption
.
published_only
:
return
key
.
for_branch
(
ModuleStoreEnum
.
BranchName
.
published
)
return
key
.
for_branch
(
published_branch
)
elif
revision
==
ModuleStoreEnum
.
RevisionOption
.
draft_only
:
return
key
.
for_branch
(
ModuleStoreEnum
.
BranchName
.
draft
)
return
key
.
for_branch
(
draft_branch
)
elif
revision
is
None
:
if
key
.
branch
is
not
None
:
return
key
elif
self
.
get_branch_setting
(
key
)
==
ModuleStoreEnum
.
Branch
.
draft_preferred
:
return
key
.
for_branch
(
ModuleStoreEnum
.
BranchName
.
draft
)
return
key
.
for_branch
(
draft_branch
)
else
:
return
key
.
for_branch
(
ModuleStoreEnum
.
BranchName
.
published
)
return
key
.
for_branch
(
published_branch
)
else
:
raise
UnsupportedRevisionError
()
...
...
common/lib/xmodule/xmodule/modulestore/tests/factories.py
View file @
0ded6699
...
...
@@ -78,6 +78,36 @@ class CourseFactory(XModuleFactory):
return
new_course
class
LibraryFactory
(
XModuleFactory
):
"""
Factory for creating a content library
"""
org
=
factory
.
Sequence
(
'org{}'
.
format
)
library
=
factory
.
Sequence
(
'lib{}'
.
format
)
display_name
=
factory
.
Sequence
(
'Test Library {}'
.
format
)
# pylint: disable=unused-argument
@classmethod
def
_create
(
cls
,
target_class
,
**
kwargs
):
"""
Create a library with a unique name and key.
All class attributes (from this class and base classes) are automagically
passed in via **kwargs.
"""
# some of the kwargst actual field values, so pop those off for use separately:
org
=
kwargs
.
pop
(
'org'
)
library
=
kwargs
.
pop
(
'library'
)
store
=
kwargs
.
pop
(
'modulestore'
)
user_id
=
kwargs
.
pop
(
'user_id'
,
ModuleStoreEnum
.
UserID
.
test
)
# Pass the metadata just as field=value pairs
kwargs
.
update
(
kwargs
.
pop
(
'metadata'
,
{}))
default_store_override
=
kwargs
.
pop
(
'default_store'
,
ModuleStoreEnum
.
Type
.
split
)
with
store
.
default_store
(
default_store_override
):
new_library
=
store
.
create_library
(
org
,
library
,
user_id
,
fields
=
kwargs
)
return
new_library
class
ItemFactory
(
XModuleFactory
):
"""
Factory for XModule items.
...
...
common/lib/xmodule/xmodule/modulestore/tests/test_libraries.py
0 → 100644
View file @
0ded6699
# -*- coding: utf-8 -*-
"""
Basic unit tests related to content libraries.
Higher-level tests are in `cms/djangoapps/contentstore`.
"""
from
bson.objectid
import
ObjectId
import
ddt
from
mock
import
patch
from
opaque_keys.edx.locator
import
LibraryLocator
from
xblock.fragment
import
Fragment
from
xblock.runtime
import
Runtime
as
VanillaRuntime
from
xmodule.modulestore.exceptions
import
DuplicateCourseError
from
xmodule.modulestore.tests.factories
import
LibraryFactory
,
ItemFactory
,
check_mongo_calls
from
xmodule.modulestore.tests.utils
import
MixedSplitTestCase
from
xmodule.x_module
import
AUTHOR_VIEW
@ddt.ddt
class
TestLibraries
(
MixedSplitTestCase
):
"""
Test for libraries.
Mostly tests code found throughout split mongo, but also tests library_root_xblock.py
"""
def
test_create_library
(
self
):
"""
Test that we can create a library, and see how many mongo calls it uses to do so.
Expected mongo calls, in order:
find_one({'org': '...', 'run': 'library', 'course': '...'})
insert(definition: {'block_type': 'library', 'fields': {}})
insert_structure(bulk)
insert_course_index(bulk)
get_course_index(bulk)
"""
with
check_mongo_calls
(
2
,
3
):
LibraryFactory
.
create
(
modulestore
=
self
.
store
)
def
test_duplicate_library
(
self
):
"""
Make sure we cannot create duplicate libraries
"""
org
,
lib_code
=
(
'DuplicateX'
,
"DUP"
)
LibraryFactory
.
create
(
org
=
org
,
library
=
lib_code
,
modulestore
=
self
.
store
)
with
self
.
assertRaises
(
DuplicateCourseError
):
LibraryFactory
.
create
(
org
=
org
,
library
=
lib_code
,
modulestore
=
self
.
store
)
@ddt.data
(
"This is a test library!"
,
u"Ωμέγα Βιβλιοθήκη"
,
)
def
test_str_repr
(
self
,
name
):
"""
Test __unicode__() and __str__() methods of libraries
"""
library
=
LibraryFactory
.
create
(
metadata
=
{
"display_name"
:
name
},
modulestore
=
self
.
store
)
self
.
assertIn
(
name
,
unicode
(
library
))
if
not
isinstance
(
name
,
unicode
):
self
.
assertIn
(
name
,
str
(
library
))
def
test_display_with_default_methods
(
self
):
"""
Check that the display_x_with_default methods have been implemented, for
compatibility with courses.
"""
org
=
'TestOrgX'
lib_code
=
'LC101'
library
=
LibraryFactory
.
create
(
org
=
org
,
library
=
lib_code
,
modulestore
=
self
.
store
)
self
.
assertEqual
(
library
.
display_org_with_default
,
org
)
self
.
assertEqual
(
library
.
display_number_with_default
,
lib_code
)
def
test_block_with_children
(
self
):
"""
Test that blocks used from a library can have children.
"""
library
=
LibraryFactory
.
create
(
modulestore
=
self
.
store
)
# In the library, create a vertical block with a child:
vert_block
=
ItemFactory
.
create
(
category
=
"vertical"
,
parent_location
=
library
.
location
,
user_id
=
self
.
user_id
,
publish_item
=
False
,
modulestore
=
self
.
store
,
)
child_block
=
ItemFactory
.
create
(
category
=
"html"
,
parent_location
=
vert_block
.
location
,
user_id
=
self
.
user_id
,
publish_item
=
False
,
metadata
=
{
"data"
:
"Hello world"
,
},
modulestore
=
self
.
store
,
)
self
.
assertEqual
(
child_block
.
parent
.
replace
(
version_guid
=
None
,
branch
=
None
),
vert_block
.
location
)
def
test_update_item
(
self
):
"""
Test that update_item works for a block in a library
"""
library
=
LibraryFactory
.
create
(
modulestore
=
self
.
store
)
block
=
ItemFactory
.
create
(
category
=
"html"
,
parent_location
=
library
.
location
,
user_id
=
self
.
user_id
,
publish_item
=
False
,
metadata
=
{
"data"
:
"Hello world"
,
},
modulestore
=
self
.
store
,
)
block_key
=
block
.
location
block
.
data
=
"NEW"
old_version
=
self
.
store
.
get_item
(
block_key
,
remove_version
=
False
,
remove_branch
=
False
)
.
location
.
version_guid
self
.
store
.
update_item
(
block
,
self
.
user_id
)
# Reload block from the modulestore
block
=
self
.
store
.
get_item
(
block_key
)
self
.
assertEqual
(
block
.
data
,
"NEW"
)
self
.
assertEqual
(
block
.
location
,
block_key
)
new_version
=
self
.
store
.
get_item
(
block_key
,
remove_version
=
False
,
remove_branch
=
False
)
.
location
.
version_guid
self
.
assertNotEqual
(
old_version
,
new_version
)
def
test_delete_item
(
self
):
"""
Test to make sure delete_item() works on blocks in a library
"""
library
=
LibraryFactory
.
create
(
modulestore
=
self
.
store
)
lib_key
=
library
.
location
.
library_key
block
=
ItemFactory
.
create
(
category
=
"html"
,
parent_location
=
library
.
location
,
user_id
=
self
.
user_id
,
publish_item
=
False
,
modulestore
=
self
.
store
,
)
library
=
self
.
store
.
get_library
(
lib_key
)
self
.
assertEqual
(
len
(
library
.
children
),
1
)
self
.
store
.
delete_item
(
block
.
location
,
self
.
user_id
)
library
=
self
.
store
.
get_library
(
lib_key
)
self
.
assertEqual
(
len
(
library
.
children
),
0
)
def
test_get_library_non_existent
(
self
):
""" Test get_library() with non-existent key """
result
=
self
.
store
.
get_library
(
LibraryLocator
(
"non"
,
"existent"
))
self
.
assertEqual
(
result
,
None
)
def
test_get_libraries
(
self
):
""" Test get_libraries() """
libraries
=
[
LibraryFactory
.
create
(
modulestore
=
self
.
store
)
for
_
in
range
(
0
,
3
)]
lib_dict
=
dict
([(
lib
.
location
.
library_key
,
lib
)
for
lib
in
libraries
])
lib_list
=
self
.
store
.
get_libraries
()
self
.
assertEqual
(
len
(
lib_list
),
len
(
libraries
))
for
lib
in
lib_list
:
self
.
assertIn
(
lib
.
location
.
library_key
,
lib_dict
)
def
test_strip
(
self
):
"""
Test that library keys coming out of MixedModuleStore are stripped of
branch and version info by default.
"""
# Create a library
lib_key
=
LibraryFactory
.
create
(
modulestore
=
self
.
store
)
.
location
.
library_key
# Re-load the library from the modulestore, explicitly including version information:
lib
=
self
.
store
.
get_library
(
lib_key
)
self
.
assertEqual
(
lib
.
location
.
version_guid
,
None
)
self
.
assertEqual
(
lib
.
location
.
branch
,
None
)
self
.
assertEqual
(
lib
.
location
.
library_key
.
version_guid
,
None
)
self
.
assertEqual
(
lib
.
location
.
library_key
.
branch
,
None
)
def
test_get_lib_version
(
self
):
"""
Test that we can get version data about a library from get_library()
"""
# Create a library
lib_key
=
LibraryFactory
.
create
(
modulestore
=
self
.
store
)
.
location
.
library_key
# Re-load the library from the modulestore, explicitly including version information:
lib
=
self
.
store
.
get_library
(
lib_key
,
remove_version
=
False
,
remove_branch
=
False
)
version
=
lib
.
location
.
library_key
.
version_guid
self
.
assertIsInstance
(
version
,
ObjectId
)
@patch
(
'xmodule.modulestore.split_mongo.caching_descriptor_system.CachingDescriptorSystem.render'
,
VanillaRuntime
.
render
)
def
test_library_author_view
(
self
):
"""
Test that LibraryRoot.author_view can run and includes content from its
children.
We have to patch the runtime (module system) in order to be able to
render blocks in our test environment.
"""
library
=
LibraryFactory
.
create
(
modulestore
=
self
.
store
)
# Add one HTML block to the library:
ItemFactory
.
create
(
category
=
"html"
,
parent_location
=
library
.
location
,
user_id
=
self
.
user_id
,
publish_item
=
False
,
modulestore
=
self
.
store
,
)
library
=
self
.
store
.
get_library
(
library
.
location
.
library_key
)
context
=
{
'reorderable_items'
:
set
(),
}
# Patch the HTML block to always render "Hello world"
message
=
u"Hello world"
hello_render
=
lambda
_
,
context
:
Fragment
(
message
)
with
patch
(
'xmodule.html_module.HtmlDescriptor.author_view'
,
hello_render
,
create
=
True
):
result
=
library
.
render
(
AUTHOR_VIEW
,
context
)
self
.
assertIn
(
message
,
result
.
content
)
common/lib/xmodule/xmodule/modulestore/tests/test_mixed_modulestore.py
View file @
0ded6699
...
...
@@ -39,6 +39,7 @@ from xmodule.modulestore.mixed import MixedModuleStore
from
xmodule.modulestore.search
import
path_to_location
from
xmodule.modulestore.tests.factories
import
check_mongo_calls
,
check_exact_number_of_calls
,
\
mongo_uses_error_check
from
xmodule.modulestore.tests.utils
import
create_modulestore_instance
from
xmodule.modulestore.tests.mongo_connection
import
MONGO_PORT_NUM
,
MONGO_HOST
from
xmodule.tests
import
DATA_DIR
,
CourseComparisonTest
...
...
@@ -1967,35 +1968,3 @@ class TestMixedModuleStore(CourseComparisonTest):
with
self
.
store
.
branch_setting
(
ModuleStoreEnum
.
Branch
.
published_only
,
course_id
):
published_vertical
=
self
.
store
.
get_item
(
vertical_loc
)
self
.
assertEqual
(
draft_vertical
.
display_name
,
published_vertical
.
display_name
)
# ============================================================================================================
# General utils for not using django settings
# ============================================================================================================
def
load_function
(
path
):
"""
Load a function by name.
path is a string of the form "path.to.module.function"
returns the imported python object `function` from `path.to.module`
"""
module_path
,
_
,
name
=
path
.
rpartition
(
'.'
)
return
getattr
(
import_module
(
module_path
),
name
)
# pylint: disable=unused-argument
def
create_modulestore_instance
(
engine
,
contentstore
,
doc_store_config
,
options
,
i18n_service
=
None
,
fs_service
=
None
):
"""
This will return a new instance of a modulestore given an engine and options
"""
class_
=
load_function
(
engine
)
if
issubclass
(
class_
,
ModuleStoreDraftAndPublished
):
options
[
'branch_setting_func'
]
=
lambda
:
ModuleStoreEnum
.
Branch
.
draft_preferred
return
class_
(
doc_store_config
=
doc_store_config
,
contentstore
=
contentstore
,
**
options
)
common/lib/xmodule/xmodule/modulestore/tests/test_mongo.py
View file @
0ded6699
...
...
@@ -29,6 +29,7 @@ from xmodule.modulestore import ModuleStoreEnum
from
xmodule.modulestore.mongo
import
MongoKeyValueStore
from
xmodule.modulestore.draft
import
DraftModuleStore
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
,
AssetLocation
from
opaque_keys.edx.locator
import
LibraryLocator
from
opaque_keys.edx.keys
import
UsageKey
from
xmodule.modulestore.xml_exporter
import
export_to_xml
from
xmodule.modulestore.xml_importer
import
import_from_xml
,
perform_xlint
...
...
@@ -236,6 +237,16 @@ class TestMongoModuleStore(TestMongoModuleStoreBase):
assert_false
(
self
.
draft_store
.
has_course
(
mix_cased
))
assert_false
(
self
.
draft_store
.
has_course
(
mix_cased
,
ignore_case
=
True
))
def
test_has_course_with_library
(
self
):
"""
Test that has_course() returns False when called with a LibraryLocator.
This is required because MixedModuleStore will use has_course() to check
where a given library are stored.
"""
lib_key
=
LibraryLocator
(
"TestOrg"
,
"TestLib"
)
result
=
self
.
draft_store
.
has_course
(
lib_key
)
assert_false
(
result
)
def
test_loads
(
self
):
assert_not_none
(
self
.
draft_store
.
get_item
(
Location
(
'edX'
,
'toy'
,
'2012_Fall'
,
'course'
,
'2012_Fall'
))
...
...
common/lib/xmodule/xmodule/modulestore/tests/utils.py
0 → 100644
View file @
0ded6699
"""
Helper classes and methods for running modulestore tests without Django.
"""
from
importlib
import
import_module
from
opaque_keys.edx.keys
import
UsageKey
from
unittest
import
TestCase
from
xblock.fields
import
XBlockMixin
from
xmodule.modulestore
import
ModuleStoreEnum
from
xmodule.modulestore.draft_and_published
import
ModuleStoreDraftAndPublished
from
xmodule.modulestore.edit_info
import
EditInfoMixin
from
xmodule.modulestore.inheritance
import
InheritanceMixin
from
xmodule.modulestore.mixed
import
MixedModuleStore
from
xmodule.modulestore.tests.mongo_connection
import
MONGO_PORT_NUM
,
MONGO_HOST
from
xmodule.tests
import
DATA_DIR
def
load_function
(
path
):
"""
Load a function by name.
path is a string of the form "path.to.module.function"
returns the imported python object `function` from `path.to.module`
"""
module_path
,
_
,
name
=
path
.
rpartition
(
'.'
)
return
getattr
(
import_module
(
module_path
),
name
)
# pylint: disable=unused-argument
def
create_modulestore_instance
(
engine
,
contentstore
,
doc_store_config
,
options
,
i18n_service
=
None
,
fs_service
=
None
):
"""
This will return a new instance of a modulestore given an engine and options
"""
class_
=
load_function
(
engine
)
if
issubclass
(
class_
,
ModuleStoreDraftAndPublished
):
options
[
'branch_setting_func'
]
=
lambda
:
ModuleStoreEnum
.
Branch
.
draft_preferred
return
class_
(
doc_store_config
=
doc_store_config
,
contentstore
=
contentstore
,
**
options
)
class
LocationMixin
(
XBlockMixin
):
"""
Adds a `location` property to an :class:`XBlock` so it is more compatible
with old-style :class:`XModule` API. This is a simplified version of
:class:`XModuleMixin`.
"""
@property
def
location
(
self
):
""" Get the UsageKey of this block. """
return
self
.
scope_ids
.
usage_id
@location.setter
def
location
(
self
,
value
):
""" Set the UsageKey of this block. """
assert
isinstance
(
value
,
UsageKey
)
self
.
scope_ids
=
self
.
scope_ids
.
_replace
(
# pylint: disable=attribute-defined-outside-init,protected-access
def_id
=
value
,
usage_id
=
value
,
)
class
MixedSplitTestCase
(
TestCase
):
"""
Stripped-down version of ModuleStoreTestCase that can be used without Django
(i.e. for testing in common/lib/ ). Sets up MixedModuleStore and Split.
"""
RENDER_TEMPLATE
=
lambda
t_n
,
d
,
ctx
=
None
,
nsp
=
'main'
:
u'{}: {}, {}'
.
format
(
t_n
,
repr
(
d
),
repr
(
ctx
))
modulestore_options
=
{
'default_class'
:
'xmodule.raw_module.RawDescriptor'
,
'fs_root'
:
DATA_DIR
,
'render_template'
:
RENDER_TEMPLATE
,
'xblock_mixins'
:
(
EditInfoMixin
,
InheritanceMixin
,
LocationMixin
),
}
DOC_STORE_CONFIG
=
{
'host'
:
MONGO_HOST
,
'port'
:
MONGO_PORT_NUM
,
'db'
:
'test_mongo_libs'
,
'collection'
:
'modulestore'
,
'asset_collection'
:
'assetstore'
,
}
MIXED_OPTIONS
=
{
'stores'
:
[
{
'NAME'
:
'split'
,
'ENGINE'
:
'xmodule.modulestore.split_mongo.split_draft.DraftVersioningModuleStore'
,
'DOC_STORE_CONFIG'
:
DOC_STORE_CONFIG
,
'OPTIONS'
:
modulestore_options
},
]
}
def
setUp
(
self
):
"""
Set up requirements for testing: a user ID and a modulestore
"""
super
(
MixedSplitTestCase
,
self
)
.
setUp
()
self
.
user_id
=
ModuleStoreEnum
.
UserID
.
test
self
.
store
=
MixedModuleStore
(
None
,
create_modulestore_instance
=
create_modulestore_instance
,
mappings
=
{},
**
self
.
MIXED_OPTIONS
)
self
.
addCleanup
(
self
.
store
.
close_all_connections
)
self
.
addCleanup
(
self
.
store
.
_drop_database
)
# pylint: disable=protected-access
requirements/edx/github.txt
View file @
0ded6699
...
...
@@ -30,7 +30,7 @@
-e git+https://github.com/edx-solutions/django-splash.git@7579d052afcf474ece1239153cffe1c89935bc4f#egg=django-splash
-e git+https://github.com/edx/acid-block.git@df1a7f0cae46567c251d507b8c72168aed8ec042#egg=acid-xblock
-e git+https://github.com/edx/edx-ora2.git@release-2014-10-27T19.33#egg=edx-ora2
-e git+https://github.com/edx/opaque-keys.git@
0.1.
2#egg=opaque-keys
-e git+https://github.com/edx/opaque-keys.git@
b12401384921c075e5a4ed7aedc3bea57f56ec3
2#egg=opaque-keys
-e git+https://github.com/edx/ease.git@97de68448e5495385ba043d3091f570a699d5b5f#egg=ease
-e git+https://github.com/edx/i18n-tools.git@56f048af9b6868613c14aeae760548834c495011#egg=i18n-tools
-e git+https://github.com/edx/edx-oauth2-provider.git@0.4.0#egg=oauth2-provider
...
...
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