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
7f126f13
Commit
7f126f13
authored
Aug 16, 2013
by
Don Mitchell
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #624 from edx/dhm/flatten_kvs
xblock fields persist w/o breaking by scope
parents
5b1b73cb
4c286840
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
747 additions
and
643 deletions
+747
-643
cms/djangoapps/contentstore/tests/test_crud.py
+70
-19
cms/djangoapps/contentstore/views/item.py
+3
-22
common/lib/xmodule/xmodule/modulestore/split_mongo/caching_descriptor_system.py
+14
-15
common/lib/xmodule/xmodule/modulestore/split_mongo/split.py
+251
-174
common/lib/xmodule/xmodule/modulestore/split_mongo/split_mongo_kvs.py
+120
-101
common/lib/xmodule/xmodule/modulestore/tests/persistent_factories.py
+17
-49
common/lib/xmodule/xmodule/modulestore/tests/test_split_modulestore.py
+26
-27
common/lib/xmodule/xmodule/x_module.py
+33
-70
common/test/data/splitmongo_json/definitions.json
+78
-55
common/test/data/splitmongo_json/structures.json
+135
-111
No files found.
cms/djangoapps/contentstore/tests/test_crud.py
View file @
7f126f13
'''
Created on May 7, 2013
@author: dmitchell
'''
import
unittest
import
unittest
from
xmodule
import
templates
from
xmodule
import
templates
from
xmodule.modulestore.tests
import
persistent_factories
from
xmodule.modulestore.tests
import
persistent_factories
from
xmodule.course_module
import
CourseDescriptor
from
xmodule.course_module
import
CourseDescriptor
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.django
import
modulestore
from
xmodule.seq_module
import
SequenceDescriptor
from
xmodule.seq_module
import
SequenceDescriptor
from
xmodule.x_module
import
XModuleDescriptor
from
xmodule.capa_module
import
CapaDescriptor
from
xmodule.capa_module
import
CapaDescriptor
from
xmodule.modulestore.locator
import
CourseLocator
,
BlockUsageLocator
from
xmodule.modulestore.locator
import
CourseLocator
,
BlockUsageLocator
from
xmodule.modulestore.exceptions
import
ItemNotFoundError
from
xmodule.modulestore.exceptions
import
ItemNotFoundError
from
xmodule.html_module
import
HtmlDescriptor
from
xmodule.html_module
import
HtmlDescriptor
from
xmodule.modulestore
import
inheritance
from
xmodule.x_module
import
XModuleDescriptor
class
TemplateTests
(
unittest
.
TestCase
):
class
TemplateTests
(
unittest
.
TestCase
):
...
@@ -74,8 +70,8 @@ class TemplateTests(unittest.TestCase):
...
@@ -74,8 +70,8 @@ class TemplateTests(unittest.TestCase):
test_course
=
persistent_factories
.
PersistentCourseFactory
.
create
(
org
=
'testx'
,
prettyid
=
'tempcourse'
,
test_course
=
persistent_factories
.
PersistentCourseFactory
.
create
(
org
=
'testx'
,
prettyid
=
'tempcourse'
,
display_name
=
'fun test course'
,
user_id
=
'testbot'
)
display_name
=
'fun test course'
,
user_id
=
'testbot'
)
test_chapter
=
XModuleDescriptor
.
load_from_json
({
'category'
:
'chapter'
,
test_chapter
=
self
.
load_from_json
({
'category'
:
'chapter'
,
'
metadata
'
:
{
'display_name'
:
'chapter n'
}},
'
fields
'
:
{
'display_name'
:
'chapter n'
}},
test_course
.
system
,
parent_xblock
=
test_course
)
test_course
.
system
,
parent_xblock
=
test_course
)
self
.
assertIsInstance
(
test_chapter
,
SequenceDescriptor
)
self
.
assertIsInstance
(
test_chapter
,
SequenceDescriptor
)
self
.
assertEqual
(
test_chapter
.
display_name
,
'chapter n'
)
self
.
assertEqual
(
test_chapter
.
display_name
,
'chapter n'
)
...
@@ -83,8 +79,8 @@ class TemplateTests(unittest.TestCase):
...
@@ -83,8 +79,8 @@ class TemplateTests(unittest.TestCase):
# test w/ a definition (e.g., a problem)
# test w/ a definition (e.g., a problem)
test_def_content
=
'<problem>boo</problem>'
test_def_content
=
'<problem>boo</problem>'
test_problem
=
XModuleDescriptor
.
load_from_json
({
'category'
:
'problem'
,
test_problem
=
self
.
load_from_json
({
'category'
:
'problem'
,
'
definition
'
:
{
'data'
:
test_def_content
}},
'
fields
'
:
{
'data'
:
test_def_content
}},
test_course
.
system
,
parent_xblock
=
test_chapter
)
test_course
.
system
,
parent_xblock
=
test_chapter
)
self
.
assertIsInstance
(
test_problem
,
CapaDescriptor
)
self
.
assertIsInstance
(
test_problem
,
CapaDescriptor
)
self
.
assertEqual
(
test_problem
.
data
,
test_def_content
)
self
.
assertEqual
(
test_problem
.
data
,
test_def_content
)
...
@@ -98,12 +94,13 @@ class TemplateTests(unittest.TestCase):
...
@@ -98,12 +94,13 @@ class TemplateTests(unittest.TestCase):
"""
"""
test_course
=
persistent_factories
.
PersistentCourseFactory
.
create
(
org
=
'testx'
,
prettyid
=
'tempcourse'
,
test_course
=
persistent_factories
.
PersistentCourseFactory
.
create
(
org
=
'testx'
,
prettyid
=
'tempcourse'
,
display_name
=
'fun test course'
,
user_id
=
'testbot'
)
display_name
=
'fun test course'
,
user_id
=
'testbot'
)
test_chapter
=
XModuleDescriptor
.
load_from_json
({
'category'
:
'chapter'
,
test_chapter
=
self
.
load_from_json
({
'category'
:
'chapter'
,
'
metadata
'
:
{
'display_name'
:
'chapter n'
}},
'
fields
'
:
{
'display_name'
:
'chapter n'
}},
test_course
.
system
,
parent_xblock
=
test_course
)
test_course
.
system
,
parent_xblock
=
test_course
)
test_def_content
=
'<problem>boo</problem>'
test_def_content
=
'<problem>boo</problem>'
test_problem
=
XModuleDescriptor
.
load_from_json
({
'category'
:
'problem'
,
# create child
'definition'
:
{
'data'
:
test_def_content
}},
_
=
self
.
load_from_json
({
'category'
:
'problem'
,
'fields'
:
{
'data'
:
test_def_content
}},
test_course
.
system
,
parent_xblock
=
test_chapter
)
test_course
.
system
,
parent_xblock
=
test_chapter
)
# better to pass in persisted parent over the subdag so
# better to pass in persisted parent over the subdag so
# subdag gets the parent pointer (otherwise 2 ops, persist dag, update parent children,
# subdag gets the parent pointer (otherwise 2 ops, persist dag, update parent children,
...
@@ -152,15 +149,24 @@ class TemplateTests(unittest.TestCase):
...
@@ -152,15 +149,24 @@ class TemplateTests(unittest.TestCase):
parent_location
=
test_course
.
location
,
user_id
=
'testbot'
)
parent_location
=
test_course
.
location
,
user_id
=
'testbot'
)
sub
=
persistent_factories
.
ItemFactory
.
create
(
display_name
=
'subsection 1'
,
sub
=
persistent_factories
.
ItemFactory
.
create
(
display_name
=
'subsection 1'
,
parent_location
=
chapter
.
location
,
user_id
=
'testbot'
,
category
=
'vertical'
)
parent_location
=
chapter
.
location
,
user_id
=
'testbot'
,
category
=
'vertical'
)
first_problem
=
persistent_factories
.
ItemFactory
.
create
(
display_name
=
'problem 1'
,
first_problem
=
persistent_factories
.
ItemFactory
.
create
(
parent_location
=
sub
.
location
,
user_id
=
'testbot'
,
category
=
'problem'
,
data
=
"<problem></problem>"
)
display_name
=
'problem 1'
,
parent_location
=
sub
.
location
,
user_id
=
'testbot'
,
category
=
'problem'
,
data
=
"<problem></problem>"
)
first_problem
.
max_attempts
=
3
first_problem
.
max_attempts
=
3
first_problem
.
save
()
# decache the above into the kvs
updated_problem
=
modulestore
(
'split'
)
.
update_item
(
first_problem
,
'testbot'
)
updated_problem
=
modulestore
(
'split'
)
.
update_item
(
first_problem
,
'testbot'
)
updated_loc
=
modulestore
(
'split'
)
.
delete_item
(
updated_problem
.
location
,
'testbot'
)
self
.
assertIsNotNone
(
updated_problem
.
previous_version
)
self
.
assertEqual
(
updated_problem
.
previous_version
,
first_problem
.
update_version
)
self
.
assertNotEqual
(
updated_problem
.
update_version
,
first_problem
.
update_version
)
updated_loc
=
modulestore
(
'split'
)
.
delete_item
(
updated_problem
.
location
,
'testbot'
,
delete_children
=
True
)
second_problem
=
persistent_factories
.
ItemFactory
.
create
(
display_name
=
'problem 2'
,
second_problem
=
persistent_factories
.
ItemFactory
.
create
(
display_name
=
'problem 2'
,
parent_location
=
BlockUsageLocator
(
updated_loc
,
usage_id
=
sub
.
location
.
usage_id
),
parent_location
=
BlockUsageLocator
(
updated_loc
,
usage_id
=
sub
.
location
.
usage_id
),
user_id
=
'testbot'
,
category
=
'problem'
,
data
=
"<problem></problem>"
)
user_id
=
'testbot'
,
category
=
'problem'
,
data
=
"<problem></problem>"
)
# course root only updated 2x
# course root only updated 2x
version_history
=
modulestore
(
'split'
)
.
get_block_generations
(
test_course
.
location
)
version_history
=
modulestore
(
'split'
)
.
get_block_generations
(
test_course
.
location
)
...
@@ -184,3 +190,48 @@ class TemplateTests(unittest.TestCase):
...
@@ -184,3 +190,48 @@ class TemplateTests(unittest.TestCase):
version_history
=
modulestore
(
'split'
)
.
get_block_generations
(
second_problem
.
location
)
version_history
=
modulestore
(
'split'
)
.
get_block_generations
(
second_problem
.
location
)
self
.
assertNotEqual
(
version_history
.
locator
.
version_guid
,
first_problem
.
location
.
version_guid
)
self
.
assertNotEqual
(
version_history
.
locator
.
version_guid
,
first_problem
.
location
.
version_guid
)
# ================================= JSON PARSING ===========================
# These are example methods for creating xmodules in memory w/o persisting them.
# They were in x_module but since xblock is not planning to support them but will
# allow apps to use this type of thing, I put it here.
@staticmethod
def
load_from_json
(
json_data
,
system
,
default_class
=
None
,
parent_xblock
=
None
):
"""
This method instantiates the correct subclass of XModuleDescriptor based
on the contents of json_data. It does not persist it and can create one which
has no usage id.
parent_xblock is used to compute inherited metadata as well as to append the new xblock.
json_data:
- 'location' : must have this field
- 'category': the xmodule category (required or location must be a Location)
- 'metadata': a dict of locally set metadata (not inherited)
- 'children': a list of children's usage_ids w/in this course
- 'definition':
- '_id' (optional): the usage_id of this. Will generate one if not given one.
"""
class_
=
XModuleDescriptor
.
load_class
(
json_data
.
get
(
'category'
,
json_data
.
get
(
'location'
,
{})
.
get
(
'category'
)),
default_class
)
usage_id
=
json_data
.
get
(
'_id'
,
None
)
if
not
'_inherited_settings'
in
json_data
and
parent_xblock
is
not
None
:
json_data
[
'_inherited_settings'
]
=
parent_xblock
.
xblock_kvs
.
get_inherited_settings
()
.
copy
()
json_fields
=
json_data
.
get
(
'fields'
,
{})
for
field
in
inheritance
.
INHERITABLE_METADATA
:
if
field
in
json_fields
:
json_data
[
'_inherited_settings'
][
field
]
=
json_fields
[
field
]
new_block
=
system
.
xblock_from_json
(
class_
,
usage_id
,
json_data
)
if
parent_xblock
is
not
None
:
children
=
parent_xblock
.
children
children
.
append
(
new_block
)
# trigger setter method by using top level field access
parent_xblock
.
children
=
children
# decache pending children field settings (Note, truly persisting at this point would break b/c
# persistence assumes children is a list of ids not actual xblocks)
parent_xblock
.
save
()
return
new_block
cms/djangoapps/contentstore/views/item.py
View file @
7f126f13
...
@@ -58,14 +58,12 @@ def save_item(request):
...
@@ -58,14 +58,12 @@ def save_item(request):
# 'apply' the submitted metadata, so we don't end up deleting system metadata
# 'apply' the submitted metadata, so we don't end up deleting system metadata
existing_item
=
modulestore
()
.
get_item
(
item_location
)
existing_item
=
modulestore
()
.
get_item
(
item_location
)
for
metadata_key
in
request
.
POST
.
get
(
'nullout'
,
[]):
for
metadata_key
in
request
.
POST
.
get
(
'nullout'
,
[]):
# [dhm] see comment on _get_xblock_field
_get_xblock_field
(
existing_item
,
metadata_key
)
.
write_to
(
existing_item
,
None
)
_get_xblock_field
(
existing_item
,
metadata_key
)
.
write_to
(
existing_item
,
None
)
# update existing metadata with submitted metadata (which can be partial)
# update existing metadata with submitted metadata (which can be partial)
# IMPORTANT NOTE: if the client passed 'null' (None) for a piece of metadata that means 'remove it'. If
# IMPORTANT NOTE: if the client passed 'null' (None) for a piece of metadata that means 'remove it'. If
# the intent is to make it None, use the nullout field
# the intent is to make it None, use the nullout field
for
metadata_key
,
value
in
request
.
POST
.
get
(
'metadata'
,
{})
.
items
():
for
metadata_key
,
value
in
request
.
POST
.
get
(
'metadata'
,
{})
.
items
():
# [dhm] see comment on _get_xblock_field
field
=
_get_xblock_field
(
existing_item
,
metadata_key
)
field
=
_get_xblock_field
(
existing_item
,
metadata_key
)
if
value
is
None
:
if
value
is
None
:
...
@@ -82,32 +80,15 @@ def save_item(request):
...
@@ -82,32 +80,15 @@ def save_item(request):
return
JsonResponse
()
return
JsonResponse
()
# [DHM] A hack until we implement a permanent soln. Proposed perm solution is to make namespace fields also top level
# fields in xblocks rather than requiring dereference through namespace but we'll need to consider whether there are
# plausible use cases for distinct fields w/ same name in different namespaces on the same blocks.
# The idea is that consumers of the xblock, and particularly the web client, shouldn't know about our internal
# representation (namespaces as means of decorating all modules).
# Given top-level access, the calls can simply be setattr(existing_item, field, value) ...
# Really, this method should be elsewhere (e.g., xblock). We also need methods for has_value (v is_default)...
def
_get_xblock_field
(
xblock
,
field_name
):
def
_get_xblock_field
(
xblock
,
field_name
):
"""
"""
A temporary function to get the xblock field either from the xblock or one of its namespaces by name.
A temporary function to get the xblock field either from the xblock or one of its namespaces by name.
:param xblock:
:param xblock:
:param field_name:
:param field_name:
"""
"""
def
find_field
(
fields
):
for
field
in
xblock
.
iterfields
():
for
field
in
fields
:
if
field
.
name
==
field_name
:
if
field
.
name
==
field_name
:
return
field
return
field
found
=
find_field
(
xblock
.
fields
)
if
found
:
return
found
for
namespace
in
xblock
.
namespaces
:
found
=
find_field
(
getattr
(
xblock
,
namespace
)
.
fields
)
if
found
:
return
found
@login_required
@login_required
@expect_json
@expect_json
...
...
common/lib/xmodule/xmodule/modulestore/split_mongo/caching_descriptor_system.py
View file @
7f126f13
...
@@ -11,18 +11,17 @@ from .split_mongo_kvs import SplitMongoKVS, SplitMongoKVSid
...
@@ -11,18 +11,17 @@ from .split_mongo_kvs import SplitMongoKVS, SplitMongoKVSid
log
=
logging
.
getLogger
(
__name__
)
log
=
logging
.
getLogger
(
__name__
)
# TODO should this be here or w/ x_module or ???
class
CachingDescriptorSystem
(
MakoDescriptorSystem
):
class
CachingDescriptorSystem
(
MakoDescriptorSystem
):
"""
"""
A system that has a cache of a course version's json that it will use to load modules
A system that has a cache of a course version's json that it will use to load modules
from, with a backup of calling to the underlying modulestore for more data.
from, with a backup of calling to the underlying modulestore for more data.
Computes the
metadata
inheritance upon creation.
Computes the
settings (nee 'metadata')
inheritance upon creation.
"""
"""
def
__init__
(
self
,
modulestore
,
course_entry
,
module_data
,
lazy
,
def
__init__
(
self
,
modulestore
,
course_entry
,
module_data
,
lazy
,
default_class
,
error_tracker
,
render_template
):
default_class
,
error_tracker
,
render_template
):
"""
"""
Computes the
metadata
inheritance and sets up the cache.
Computes the
settings
inheritance and sets up the cache.
modulestore: the module store that can be used to retrieve additional
modulestore: the module store that can be used to retrieve additional
modules
modules
...
@@ -50,9 +49,10 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
...
@@ -50,9 +49,10 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
self
.
default_class
=
default_class
self
.
default_class
=
default_class
# TODO see if self.course_id is needed: is already in course_entry but could be > 1 value
# TODO see if self.course_id is needed: is already in course_entry but could be > 1 value
# Compute inheritance
# Compute inheritance
modulestore
.
inherit_metadata
(
course_entry
.
get
(
'blocks'
,
{}),
modulestore
.
inherit_settings
(
course_entry
.
get
(
'blocks'
,
{})
course_entry
.
get
(
'blocks'
,
{}),
.
get
(
course_entry
.
get
(
'root'
)))
course_entry
.
get
(
'blocks'
,
{})
.
get
(
course_entry
.
get
(
'root'
))
)
def
_load_item
(
self
,
usage_id
,
course_entry_override
=
None
):
def
_load_item
(
self
,
usage_id
,
course_entry_override
=
None
):
# TODO ensure all callers of system.load_item pass just the id
# TODO ensure all callers of system.load_item pass just the id
...
@@ -73,9 +73,8 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
...
@@ -73,9 +73,8 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
def
xblock_from_json
(
self
,
class_
,
usage_id
,
json_data
,
course_entry_override
=
None
):
def
xblock_from_json
(
self
,
class_
,
usage_id
,
json_data
,
course_entry_override
=
None
):
if
course_entry_override
is
None
:
if
course_entry_override
is
None
:
course_entry_override
=
self
.
course_entry
course_entry_override
=
self
.
course_entry
# most likely a lazy loader
but not
the id directly
# most likely a lazy loader
or
the id directly
definition
=
json_data
.
get
(
'definition'
,
{})
definition
=
json_data
.
get
(
'definition'
,
{})
metadata
=
json_data
.
get
(
'metadata'
,
{})
block_locator
=
BlockUsageLocator
(
block_locator
=
BlockUsageLocator
(
version_guid
=
course_entry_override
[
'_id'
],
version_guid
=
course_entry_override
[
'_id'
],
...
@@ -86,9 +85,8 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
...
@@ -86,9 +85,8 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
kvs
=
SplitMongoKVS
(
kvs
=
SplitMongoKVS
(
definition
,
definition
,
json_data
.
get
(
'children'
,
[]),
json_data
.
get
(
'fields'
,
{}),
metadata
,
json_data
.
get
(
'_inherited_settings'
),
json_data
.
get
(
'_inherited_metadata'
),
block_locator
,
block_locator
,
json_data
.
get
(
'category'
))
json_data
.
get
(
'category'
))
model_data
=
DbModel
(
kvs
,
class_
,
None
,
model_data
=
DbModel
(
kvs
,
class_
,
None
,
...
@@ -111,10 +109,11 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
...
@@ -111,10 +109,11 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
error_msg
=
exc_info_to_str
(
sys
.
exc_info
())
error_msg
=
exc_info_to_str
(
sys
.
exc_info
())
)
)
module
.
edited_by
=
json_data
.
get
(
'edited_by'
)
edit_info
=
json_data
.
get
(
'edit_info'
,
{})
module
.
edited_on
=
json_data
.
get
(
'edited_on'
)
module
.
edited_by
=
edit_info
.
get
(
'edited_by'
)
module
.
previous_version
=
json_data
.
get
(
'previous_version'
)
module
.
edited_on
=
edit_info
.
get
(
'edited_on'
)
module
.
update_version
=
json_data
.
get
(
'update_version'
)
module
.
previous_version
=
edit_info
.
get
(
'previous_version'
)
module
.
update_version
=
edit_info
.
get
(
'update_version'
)
module
.
definition_locator
=
self
.
modulestore
.
definition_locator
(
definition
)
module
.
definition_locator
=
self
.
modulestore
.
definition_locator
(
definition
)
# decache any pending field settings
# decache any pending field settings
module
.
save
()
module
.
save
()
...
...
common/lib/xmodule/xmodule/modulestore/split_mongo/split.py
View file @
7f126f13
...
@@ -16,6 +16,9 @@ from .. import ModuleStoreBase
...
@@ -16,6 +16,9 @@ from .. import ModuleStoreBase
from
..exceptions
import
ItemNotFoundError
from
..exceptions
import
ItemNotFoundError
from
.definition_lazy_loader
import
DefinitionLazyLoader
from
.definition_lazy_loader
import
DefinitionLazyLoader
from
.caching_descriptor_system
import
CachingDescriptorSystem
from
.caching_descriptor_system
import
CachingDescriptorSystem
from
xblock.core
import
Scope
from
pytz
import
UTC
import
collections
log
=
logging
.
getLogger
(
__name__
)
log
=
logging
.
getLogger
(
__name__
)
#==============================================================================
#==============================================================================
...
@@ -102,10 +105,12 @@ class SplitMongoModuleStore(ModuleStoreBase):
...
@@ -102,10 +105,12 @@ class SplitMongoModuleStore(ModuleStoreBase):
'''
'''
new_module_data
=
{}
new_module_data
=
{}
for
usage_id
in
base_usage_ids
:
for
usage_id
in
base_usage_ids
:
new_module_data
=
self
.
descendants
(
system
.
course_entry
[
'blocks'
],
new_module_data
=
self
.
descendants
(
usage_id
,
system
.
course_entry
[
'blocks'
],
depth
,
usage_id
,
new_module_data
)
depth
,
new_module_data
)
# remove any which were already in module_data (not sure if there's a better way)
# remove any which were already in module_data (not sure if there's a better way)
for
newkey
in
new_module_data
.
iterkeys
():
for
newkey
in
new_module_data
.
iterkeys
():
...
@@ -114,8 +119,7 @@ class SplitMongoModuleStore(ModuleStoreBase):
...
@@ -114,8 +119,7 @@ class SplitMongoModuleStore(ModuleStoreBase):
if
lazy
:
if
lazy
:
for
block
in
new_module_data
.
itervalues
():
for
block
in
new_module_data
.
itervalues
():
block
[
'definition'
]
=
DefinitionLazyLoader
(
self
,
block
[
'definition'
]
=
DefinitionLazyLoader
(
self
,
block
[
'definition'
])
block
[
'definition'
])
else
:
else
:
# Load all descendants by id
# Load all descendants by id
descendent_definitions
=
self
.
definitions
.
find
({
descendent_definitions
=
self
.
definitions
.
find
({
...
@@ -127,7 +131,7 @@ class SplitMongoModuleStore(ModuleStoreBase):
...
@@ -127,7 +131,7 @@ class SplitMongoModuleStore(ModuleStoreBase):
for
block
in
new_module_data
.
itervalues
():
for
block
in
new_module_data
.
itervalues
():
if
block
[
'definition'
]
in
definitions
:
if
block
[
'definition'
]
in
definitions
:
block
[
'
definition'
]
=
definitions
[
block
[
'definition'
]]
block
[
'
fields'
]
.
update
(
definitions
[
block
[
'definition'
]]
.
get
(
'fields'
))
system
.
module_data
.
update
(
new_module_data
)
system
.
module_data
.
update
(
new_module_data
)
return
system
.
module_data
return
system
.
module_data
...
@@ -317,7 +321,7 @@ class SplitMongoModuleStore(ModuleStoreBase):
...
@@ -317,7 +321,7 @@ class SplitMongoModuleStore(ModuleStoreBase):
definitions.
definitions.
Common qualifiers are category, definition (provide definition id),
Common qualifiers are category, definition (provide definition id),
metadata: {display_name ..}
, children (return
display_name, anyfieldname
, children (return
block if its children includes the one given value). If you want
block if its children includes the one given value). If you want
substring matching use {$regex: /acme.*corp/i} type syntax.
substring matching use {$regex: /acme.*corp/i} type syntax.
...
@@ -371,7 +375,7 @@ class SplitMongoModuleStore(ModuleStoreBase):
...
@@ -371,7 +375,7 @@ class SplitMongoModuleStore(ModuleStoreBase):
course
=
self
.
_lookup_course
(
locator
)
course
=
self
.
_lookup_course
(
locator
)
items
=
[]
items
=
[]
for
parent_id
,
value
in
course
[
'blocks'
]
.
iteritems
():
for
parent_id
,
value
in
course
[
'blocks'
]
.
iteritems
():
for
child_id
in
value
[
'
children'
]
:
for
child_id
in
value
[
'
fields'
]
.
get
(
'children'
,
[])
:
if
locator
.
usage_id
==
child_id
:
if
locator
.
usage_id
==
child_id
:
items
.
append
(
BlockUsageLocator
(
url
=
locator
.
as_course_locator
(),
usage_id
=
parent_id
))
items
.
append
(
BlockUsageLocator
(
url
=
locator
.
as_course_locator
(),
usage_id
=
parent_id
))
return
items
return
items
...
@@ -427,11 +431,7 @@ class SplitMongoModuleStore(ModuleStoreBase):
...
@@ -427,11 +431,7 @@ class SplitMongoModuleStore(ModuleStoreBase):
definition
=
self
.
definitions
.
find_one
({
'_id'
:
definition_locator
.
definition_id
})
definition
=
self
.
definitions
.
find_one
({
'_id'
:
definition_locator
.
definition_id
})
if
definition
is
None
:
if
definition
is
None
:
return
None
return
None
return
{
'original_version'
:
definition
[
'original_version'
],
return
definition
[
'edit_info'
]
'previous_version'
:
definition
[
'previous_version'
],
'edited_by'
:
definition
[
'edited_by'
],
'edited_on'
:
definition
[
'edited_on'
]
}
def
get_course_successors
(
self
,
course_locator
,
version_history_depth
=
1
):
def
get_course_successors
(
self
,
course_locator
,
version_history_depth
=
1
):
'''
'''
...
@@ -471,29 +471,29 @@ class SplitMongoModuleStore(ModuleStoreBase):
...
@@ -471,29 +471,29 @@ class SplitMongoModuleStore(ModuleStoreBase):
Find the history of this block. Return as a VersionTree of each place the block changed (except
Find the history of this block. Return as a VersionTree of each place the block changed (except
deletion).
deletion).
The block's history tracks its explicit changes
; so, changes in descendants won't be reflected
The block's history tracks its explicit changes
but not the changes in its children.
as new iterations.
'''
'''
block_locator
=
block_locator
.
version_agnostic
()
block_locator
=
block_locator
.
version_agnostic
()
course_struct
=
self
.
_lookup_course
(
block_locator
)
course_struct
=
self
.
_lookup_course
(
block_locator
)
usage_id
=
block_locator
.
usage_id
usage_id
=
block_locator
.
usage_id
update_version_field
=
'blocks.{}.update_version'
.
format
(
usage_id
)
update_version_field
=
'blocks.{}.
edit_info.
update_version'
.
format
(
usage_id
)
all_versions_with_block
=
self
.
structures
.
find
({
'original_version'
:
course_struct
[
'original_version'
],
all_versions_with_block
=
self
.
structures
.
find
({
'original_version'
:
course_struct
[
'original_version'
],
update_version_field
:
{
'$exists'
:
True
}})
update_version_field
:
{
'$exists'
:
True
}})
# find (all) root versions and build map previous: [successors]
# find (all) root versions and build map previous: [successors]
possible_roots
=
[]
possible_roots
=
[]
result
=
{}
result
=
{}
for
version
in
all_versions_with_block
:
for
version
in
all_versions_with_block
:
if
version
[
'_id'
]
==
version
[
'blocks'
][
usage_id
][
'update_version'
]:
if
version
[
'_id'
]
==
version
[
'blocks'
][
usage_id
][
'
edit_info'
][
'
update_version'
]:
if
version
[
'blocks'
][
usage_id
]
.
get
(
'previous_version'
)
is
None
:
if
version
[
'blocks'
][
usage_id
]
[
'edit_info'
]
.
get
(
'previous_version'
)
is
None
:
possible_roots
.
append
(
version
[
'blocks'
][
usage_id
][
'update_version'
])
possible_roots
.
append
(
version
[
'blocks'
][
usage_id
][
'
edit_info'
][
'
update_version'
])
else
:
else
:
result
.
setdefault
(
version
[
'blocks'
][
usage_id
][
'previous_version'
],
set
())
.
add
(
result
.
setdefault
(
version
[
'blocks'
][
usage_id
][
'
edit_info'
][
'
previous_version'
],
set
())
.
add
(
version
[
'blocks'
][
usage_id
][
'update_version'
])
version
[
'blocks'
][
usage_id
][
'
edit_info'
][
'
update_version'
])
# more than one possible_root means usage was added and deleted > 1x.
# more than one possible_root means usage was added and deleted > 1x.
if
len
(
possible_roots
)
>
1
:
if
len
(
possible_roots
)
>
1
:
# find the history segment including block_locator's version
# find the history segment including block_locator's version
element_to_find
=
course_struct
[
'blocks'
][
usage_id
][
'update_version'
]
element_to_find
=
course_struct
[
'blocks'
][
usage_id
][
'
edit_info'
][
'
update_version'
]
if
element_to_find
in
possible_roots
:
if
element_to_find
in
possible_roots
:
possible_roots
=
[
element_to_find
]
possible_roots
=
[
element_to_find
]
for
possibility
in
possible_roots
:
for
possibility
in
possible_roots
:
...
@@ -513,7 +513,7 @@ class SplitMongoModuleStore(ModuleStoreBase):
...
@@ -513,7 +513,7 @@ class SplitMongoModuleStore(ModuleStoreBase):
Find the version_history_depth next versions of this definition. Return as a VersionTree
Find the version_history_depth next versions of this definition. Return as a VersionTree
'''
'''
# TODO implement
# TODO implement
pass
raise
NotImplementedError
()
def
create_definition_from_data
(
self
,
new_def_data
,
category
,
user_id
):
def
create_definition_from_data
(
self
,
new_def_data
,
category
,
user_id
):
"""
"""
...
@@ -522,16 +522,21 @@ class SplitMongoModuleStore(ModuleStoreBase):
...
@@ -522,16 +522,21 @@ class SplitMongoModuleStore(ModuleStoreBase):
:param user_id: request.user object
:param user_id: request.user object
"""
"""
document
=
{
"category"
:
category
,
new_def_data
=
self
.
_filter_special_fields
(
new_def_data
)
"data"
:
new_def_data
,
document
=
{
"edited_by"
:
user_id
,
"category"
:
category
,
"edited_on"
:
datetime
.
datetime
.
utcnow
(),
"fields"
:
new_def_data
,
"previous_version"
:
None
,
"edit_info"
:
{
"original_version"
:
None
}
"edited_by"
:
user_id
,
"edited_on"
:
datetime
.
datetime
.
now
(
UTC
),
"previous_version"
:
None
,
"original_version"
:
None
}
}
new_id
=
self
.
definitions
.
insert
(
document
)
new_id
=
self
.
definitions
.
insert
(
document
)
definition_locator
=
DescriptionLocator
(
new_id
)
definition_locator
=
DescriptionLocator
(
new_id
)
document
[
'original_version'
]
=
new_id
document
[
'
edit_info'
][
'
original_version'
]
=
new_id
self
.
definitions
.
update
({
'_id'
:
new_id
},
{
'$set'
:
{
"original_version"
:
new_id
}})
self
.
definitions
.
update
({
'_id'
:
new_id
},
{
'$set'
:
{
"
edit_info.
original_version"
:
new_id
}})
return
definition_locator
return
definition_locator
def
update_definition_from_data
(
self
,
definition_locator
,
new_def_data
,
user_id
):
def
update_definition_from_data
(
self
,
definition_locator
,
new_def_data
,
user_id
):
...
@@ -541,16 +546,14 @@ class SplitMongoModuleStore(ModuleStoreBase):
...
@@ -541,16 +546,14 @@ class SplitMongoModuleStore(ModuleStoreBase):
:param user_id: request.user
:param user_id: request.user
"""
"""
new_def_data
=
self
.
_filter_special_fields
(
new_def_data
)
def
needs_saved
():
def
needs_saved
():
if
isinstance
(
new_def_data
,
dict
):
for
key
,
value
in
new_def_data
.
iteritems
():
for
key
,
value
in
new_def_data
.
iteritems
():
if
key
not
in
old_definition
[
'fields'
]
or
value
!=
old_definition
[
'fields'
][
key
]:
if
key
not
in
old_definition
[
'data'
]
or
value
!=
old_definition
[
'data'
][
key
]:
return
True
return
True
for
key
,
value
in
old_definition
.
get
(
'fields'
,
{})
.
iteritems
():
for
key
,
value
in
old_definition
[
'data'
]
.
iteritems
():
if
key
not
in
new_def_data
:
if
key
not
in
new_def_data
:
return
True
return
True
else
:
return
new_def_data
!=
old_definition
[
'data'
]
# if this looks in cache rather than fresh fetches, then it will probably not detect
# if this looks in cache rather than fresh fetches, then it will probably not detect
# actual change b/c the descriptor and cache probably point to the same objects
# actual change b/c the descriptor and cache probably point to the same objects
...
@@ -560,10 +563,10 @@ class SplitMongoModuleStore(ModuleStoreBase):
...
@@ -560,10 +563,10 @@ class SplitMongoModuleStore(ModuleStoreBase):
del
old_definition
[
'_id'
]
del
old_definition
[
'_id'
]
if
needs_saved
():
if
needs_saved
():
old_definition
[
'
data
'
]
=
new_def_data
old_definition
[
'
fields
'
]
=
new_def_data
old_definition
[
'edited_by'
]
=
user_id
old_definition
[
'edit
_info'
][
'edit
ed_by'
]
=
user_id
old_definition
[
'edit
ed_on'
]
=
datetime
.
datetime
.
utcnow
(
)
old_definition
[
'edit
_info'
][
'edited_on'
]
=
datetime
.
datetime
.
now
(
UTC
)
old_definition
[
'previous_version'
]
=
definition_locator
.
definition_id
old_definition
[
'
edit_info'
][
'
previous_version'
]
=
definition_locator
.
definition_id
new_id
=
self
.
definitions
.
insert
(
old_definition
)
new_id
=
self
.
definitions
.
insert
(
old_definition
)
return
DescriptionLocator
(
new_id
),
True
return
DescriptionLocator
(
new_id
),
True
else
:
else
:
...
@@ -605,11 +608,11 @@ class SplitMongoModuleStore(ModuleStoreBase):
...
@@ -605,11 +608,11 @@ class SplitMongoModuleStore(ModuleStoreBase):
else
:
else
:
return
id_root
return
id_root
# TODO
I would love to write this to take a real descriptor and persist it BUT descriptors, kvs, and dbmodel
# TODO
Should I rewrite this to take a new xblock instance rather than to construct it? That is, require the
#
all assume locators are set and unique! Having this take the model contents piecemeal breaks the separation
#
caller to use XModuleDescriptor.load_from_json thus reducing similar code and making the object creation and
#
of model from persistence layer
#
validation behavior a responsibility of the model layer rather than the persistence layer.
def
create_item
(
self
,
course_or_parent_locator
,
category
,
user_id
,
definition_locator
=
None
,
new_def_data
=
None
,
def
create_item
(
self
,
course_or_parent_locator
,
category
,
user_id
,
definition_locator
=
None
,
fields
=
None
,
metadata
=
None
,
force
=
False
):
force
=
False
):
"""
"""
Add a descriptor to persistence as the last child of the optional parent_location or just as an element
Add a descriptor to persistence as the last child of the optional parent_location or just as an element
of the course (if no parent provided). Return the resulting post saved version with populated locators.
of the course (if no parent provided). Return the resulting post saved version with populated locators.
...
@@ -624,9 +627,10 @@ class SplitMongoModuleStore(ModuleStoreBase):
...
@@ -624,9 +627,10 @@ class SplitMongoModuleStore(ModuleStoreBase):
The incoming definition_locator should either be None to indicate this is a brand new definition or
The incoming definition_locator should either be None to indicate this is a brand new definition or
a pointer to the existing definition to which this block should point or from which this was derived.
a pointer to the existing definition to which this block should point or from which this was derived.
If new_def_data is None, then definition_locator must have a value meaning that this block points
If fields does not contain any Scope.content, then definition_locator must have a value meaning that this
to the existing definition. If new_def_data is not None and definition_location is not None, then
block points
new_def_data is assumed to be a new payload for definition_location.
to the existing definition. If fields contains Scope.content and definition_locator is not None, then
the Scope.content fields are assumed to be a new payload for definition_locator.
Creates a new version of the course structure, creates and inserts the new block, makes the block point
Creates a new version of the course structure, creates and inserts the new block, makes the block point
to the definition which may be new or a new version of an existing or an existing.
to the definition which may be new or a new version of an existing or an existing.
...
@@ -645,6 +649,8 @@ class SplitMongoModuleStore(ModuleStoreBase):
...
@@ -645,6 +649,8 @@ class SplitMongoModuleStore(ModuleStoreBase):
index_entry
=
self
.
_get_index_if_valid
(
course_or_parent_locator
,
force
)
index_entry
=
self
.
_get_index_if_valid
(
course_or_parent_locator
,
force
)
structure
=
self
.
_lookup_course
(
course_or_parent_locator
)
structure
=
self
.
_lookup_course
(
course_or_parent_locator
)
partitioned_fields
=
self
.
_partition_fields_by_scope
(
category
,
fields
)
new_def_data
=
partitioned_fields
.
get
(
Scope
.
content
,
{})
# persist the definition if persisted != passed
# persist the definition if persisted != passed
if
(
definition_locator
is
None
or
definition_locator
.
definition_id
is
None
):
if
(
definition_locator
is
None
or
definition_locator
.
definition_id
is
None
):
definition_locator
=
self
.
create_definition_from_data
(
new_def_data
,
category
,
user_id
)
definition_locator
=
self
.
create_definition_from_data
(
new_def_data
,
category
,
user_id
)
...
@@ -655,23 +661,27 @@ class SplitMongoModuleStore(ModuleStoreBase):
...
@@ -655,23 +661,27 @@ class SplitMongoModuleStore(ModuleStoreBase):
new_structure
=
self
.
_version_structure
(
structure
,
user_id
)
new_structure
=
self
.
_version_structure
(
structure
,
user_id
)
# generate an id
# generate an id
new_usage_id
=
self
.
_generate_usage_id
(
new_structure
[
'blocks'
],
category
)
new_usage_id
=
self
.
_generate_usage_id
(
new_structure
[
'blocks'
],
category
)
update_version_keys
=
[
'blocks.{}.update_version'
.
format
(
new_usage_id
)]
update_version_keys
=
[
'blocks.{}.
edit_info.
update_version'
.
format
(
new_usage_id
)]
if
isinstance
(
course_or_parent_locator
,
BlockUsageLocator
)
and
course_or_parent_locator
.
usage_id
is
not
None
:
if
isinstance
(
course_or_parent_locator
,
BlockUsageLocator
)
and
course_or_parent_locator
.
usage_id
is
not
None
:
parent
=
new_structure
[
'blocks'
][
course_or_parent_locator
.
usage_id
]
parent
=
new_structure
[
'blocks'
][
course_or_parent_locator
.
usage_id
]
parent
[
'children'
]
.
append
(
new_usage_id
)
parent
[
'fields'
]
.
setdefault
(
'children'
,
[])
.
append
(
new_usage_id
)
parent
[
'edited_on'
]
=
datetime
.
datetime
.
utcnow
()
parent
[
'edit_info'
][
'edited_on'
]
=
datetime
.
datetime
.
now
(
UTC
)
parent
[
'edited_by'
]
=
user_id
parent
[
'edit_info'
][
'edited_by'
]
=
user_id
parent
[
'previous_version'
]
=
parent
[
'update_version'
]
parent
[
'edit_info'
][
'previous_version'
]
=
parent
[
'edit_info'
][
'update_version'
]
update_version_keys
.
append
(
'blocks.{}.update_version'
.
format
(
course_or_parent_locator
.
usage_id
))
update_version_keys
.
append
(
'blocks.{}.edit_info.update_version'
.
format
(
course_or_parent_locator
.
usage_id
))
block_fields
=
partitioned_fields
.
get
(
Scope
.
settings
,
{})
if
Scope
.
children
in
partitioned_fields
:
block_fields
.
update
(
partitioned_fields
[
Scope
.
children
])
new_structure
[
'blocks'
][
new_usage_id
]
=
{
new_structure
[
'blocks'
][
new_usage_id
]
=
{
"children"
:
[],
"category"
:
category
,
"category"
:
category
,
"definition"
:
definition_locator
.
definition_id
,
"definition"
:
definition_locator
.
definition_id
,
"metadata"
:
metadata
if
metadata
else
{},
"fields"
:
block_fields
,
'edited_on'
:
datetime
.
datetime
.
utcnow
(),
'edit_info'
:
{
'edited_by'
:
user_id
,
'edited_on'
:
datetime
.
datetime
.
now
(
UTC
),
'previous_version'
:
None
'edited_by'
:
user_id
,
'previous_version'
:
None
}
}
}
new_id
=
self
.
structures
.
insert
(
new_structure
)
new_id
=
self
.
structures
.
insert
(
new_structure
)
update_version_payload
=
{
key
:
new_id
for
key
in
update_version_keys
}
update_version_payload
=
{
key
:
new_id
for
key
in
update_version_keys
}
self
.
structures
.
update
({
'_id'
:
new_id
},
self
.
structures
.
update
({
'_id'
:
new_id
},
...
@@ -689,8 +699,9 @@ class SplitMongoModuleStore(ModuleStoreBase):
...
@@ -689,8 +699,9 @@ class SplitMongoModuleStore(ModuleStoreBase):
usage_id
=
new_usage_id
,
usage_id
=
new_usage_id
,
version_guid
=
new_id
))
version_guid
=
new_id
))
def
create_course
(
self
,
org
,
prettyid
,
user_id
,
id_root
=
None
,
metadata
=
None
,
course_data
=
None
,
def
create_course
(
master_version
=
'draft'
,
versions_dict
=
None
,
root_category
=
'course'
):
self
,
org
,
prettyid
,
user_id
,
id_root
=
None
,
fields
=
None
,
master_branch
=
'draft'
,
versions_dict
=
None
,
root_category
=
'course'
):
"""
"""
Create a new entry in the active courses index which points to an existing or new structure. Returns
Create a new entry in the active courses index which points to an existing or new structure. Returns
the course root of the resulting entry (the location has the course id)
the course root of the resulting entry (the location has the course id)
...
@@ -698,93 +709,106 @@ class SplitMongoModuleStore(ModuleStoreBase):
...
@@ -698,93 +709,106 @@ class SplitMongoModuleStore(ModuleStoreBase):
id_root: allows the caller to specify the course_id. It's a root in that, if it's already taken,
id_root: allows the caller to specify the course_id. It's a root in that, if it's already taken,
this method will append things to the root to make it unique. (defaults to org)
this method will append things to the root to make it unique. (defaults to org)
metadata: if provided, will set the metadata of the root course object in the new draft course. If both
fields: if scope.settings fields provided, will set the fields of the root course object in the
metadata and a starting version are provided, it will generate a successor version to the given version,
new course. If both
and update the metadata with any provided values (via update not setting).
settings fields and a starting version are provided (via versions_dict), it will generate a successor version
to the given version,
and update the settings fields with any provided values (via update not setting).
course_data: if provided, will update the data of the new course xblock definition to this. Like metadata,
fields (content): if scope.content fields provided, will update the fields of the new course
xblock definition to this. Like settings fields,
if provided, this will cause a new version of any given version as well as a new version of the
if provided, this will cause a new version of any given version as well as a new version of the
definition (which will point to the existing one if given a version). If not provided and given
definition (which will point to the existing one if given a version). If not provided and given
a draft_version, it will reuse the same definition as the draft course (obvious since it's reusing the draft
a version_dict, it will reuse the same definition as that version's course
course). If not provided and no draft is given, it will be empty and get the field defaults (hopefully) when
(obvious since it's reusing the
course). If not provided and no version_dict is given, it will be empty and get the field defaults
when
loaded.
loaded.
master_
version
: the tag (key) for the version name in the dict which is the 'draft' version. Not the actual
master_
branch
: the tag (key) for the version name in the dict which is the 'draft' version. Not the actual
version guid, but what to call it.
version guid, but what to call it.
versions_dict: the starting version ids where the keys are the tags such as 'draft' and 'published'
versions_dict: the starting version ids where the keys are the tags such as 'draft' and 'published'
and the values are structure guids. If provided, the new course will reuse this version (unless you also
and the values are structure guids. If provided, the new course will reuse this version (unless you also
provide any
overrides such as metadata
, see above). if not provided, will create a mostly empty course
provide any
fields overrides
, see above). if not provided, will create a mostly empty course
structure with just a category course root xblock.
structure with just a category course root xblock.
"""
"""
if
metadata
is
None
:
partitioned_fields
=
self
.
_partition_fields_by_scope
(
'course'
,
fields
)
metadata
=
{}
block_fields
=
partitioned_fields
.
setdefault
(
Scope
.
settings
,
{})
if
Scope
.
children
in
partitioned_fields
:
block_fields
.
update
(
partitioned_fields
[
Scope
.
children
])
definition_fields
=
self
.
_filter_special_fields
(
partitioned_fields
.
get
(
Scope
.
content
,
{}))
# build from inside out: definition, structure, index entry
# build from inside out: definition, structure, index entry
# if building a wholly new structure
# if building a wholly new structure
if
versions_dict
is
None
or
master_
version
not
in
versions_dict
:
if
versions_dict
is
None
or
master_
branch
not
in
versions_dict
:
# create new definition and structure
# create new definition and structure
if
course_data
is
None
:
course_data
=
{}
definition_entry
=
{
definition_entry
=
{
'category'
:
root_category
,
'category'
:
root_category
,
'data'
:
course_data
,
'fields'
:
definition_fields
,
'edited_by'
:
user_id
,
'edit_info'
:
{
'edited_on'
:
datetime
.
datetime
.
utcnow
(),
'edited_by'
:
user_id
,
'previous_version'
:
None
,
'edited_on'
:
datetime
.
datetime
.
now
(
UTC
),
'previous_version'
:
None
,
}
}
}
definition_id
=
self
.
definitions
.
insert
(
definition_entry
)
definition_id
=
self
.
definitions
.
insert
(
definition_entry
)
definition_entry
[
'original_version'
]
=
definition_id
definition_entry
[
'
edit_info'
][
'
original_version'
]
=
definition_id
self
.
definitions
.
update
({
'_id'
:
definition_id
},
{
'$set'
:
{
"original_version"
:
definition_id
}})
self
.
definitions
.
update
({
'_id'
:
definition_id
},
{
'$set'
:
{
"
edit_info.
original_version"
:
definition_id
}})
draft_structure
=
{
draft_structure
=
{
'root'
:
'course'
,
'root'
:
'course'
,
'previous_version'
:
None
,
'previous_version'
:
None
,
'edited_by'
:
user_id
,
'edited_by'
:
user_id
,
'edited_on'
:
datetime
.
datetime
.
utcnow
(
),
'edited_on'
:
datetime
.
datetime
.
now
(
UTC
),
'blocks'
:
{
'blocks'
:
{
'course'
:
{
'course'
:
{
'children'
:[],
'category'
:
'course'
,
'category'
:
'course'
,
'definition'
:
definition_id
,
'definition'
:
definition_id
,
'metadata'
:
metadata
,
'fields'
:
block_fields
,
'edited_on'
:
datetime
.
datetime
.
utcnow
(),
'edit_info'
:
{
'edited_by'
:
user_id
,
'edited_on'
:
datetime
.
datetime
.
now
(
UTC
),
'previous_version'
:
None
}}}
'edited_by'
:
user_id
,
'previous_version'
:
None
}
}
}
}
new_id
=
self
.
structures
.
insert
(
draft_structure
)
new_id
=
self
.
structures
.
insert
(
draft_structure
)
draft_structure
[
'original_version'
]
=
new_id
draft_structure
[
'original_version'
]
=
new_id
self
.
structures
.
update
({
'_id'
:
new_id
},
self
.
structures
.
update
({
'_id'
:
new_id
},
{
'$set'
:
{
"original_version"
:
new_id
,
{
'$set'
:
{
"original_version"
:
new_id
,
'blocks.course.update_version'
:
new_id
}})
'blocks.course.
edit_info.
update_version'
:
new_id
}})
if
versions_dict
is
None
:
if
versions_dict
is
None
:
versions_dict
=
{
master_
version
:
new_id
}
versions_dict
=
{
master_
branch
:
new_id
}
else
:
else
:
versions_dict
[
master_
version
]
=
new_id
versions_dict
[
master_
branch
]
=
new_id
else
:
else
:
# just get the draft_version structure
# just get the draft_version structure
draft_version
=
CourseLocator
(
version_guid
=
versions_dict
[
master_
version
])
draft_version
=
CourseLocator
(
version_guid
=
versions_dict
[
master_
branch
])
draft_structure
=
self
.
_lookup_course
(
draft_version
)
draft_structure
=
self
.
_lookup_course
(
draft_version
)
if
course_data
is
not
None
or
metadata
:
if
definition_fields
or
block_fields
:
draft_structure
=
self
.
_version_structure
(
draft_structure
,
user_id
)
draft_structure
=
self
.
_version_structure
(
draft_structure
,
user_id
)
root_block
=
draft_structure
[
'blocks'
][
draft_structure
[
'root'
]]
root_block
=
draft_structure
[
'blocks'
][
draft_structure
[
'root'
]]
if
metadata
is
not
None
:
if
block_fields
is
not
None
:
root_block
[
'
metadata'
]
.
update
(
metadata
)
root_block
[
'
fields'
]
.
update
(
block_fields
)
if
course_data
is
not
None
:
if
definition_fields
is
not
None
:
definition
=
self
.
definitions
.
find_one
({
'_id'
:
root_block
[
'definition'
]})
definition
=
self
.
definitions
.
find_one
({
'_id'
:
root_block
[
'definition'
]})
definition
[
'
data'
]
.
update
(
course_data
)
definition
[
'
fields'
]
.
update
(
definition_fields
)
definition
[
'previous_version'
]
=
definition
[
'_id'
]
definition
[
'
edit_info'
][
'
previous_version'
]
=
definition
[
'_id'
]
definition
[
'edited_by'
]
=
user_id
definition
[
'edit
_info'
][
'edit
ed_by'
]
=
user_id
definition
[
'edit
ed_on'
]
=
datetime
.
datetime
.
utcnow
(
)
definition
[
'edit
_info'
][
'edited_on'
]
=
datetime
.
datetime
.
now
(
UTC
)
del
definition
[
'_id'
]
del
definition
[
'_id'
]
root_block
[
'definition'
]
=
self
.
definitions
.
insert
(
definition
)
root_block
[
'definition'
]
=
self
.
definitions
.
insert
(
definition
)
root_block
[
'edit
ed_on'
]
=
datetime
.
datetime
.
utcnow
(
)
root_block
[
'edit
_info'
][
'edited_on'
]
=
datetime
.
datetime
.
now
(
UTC
)
root_block
[
'edited_by'
]
=
user_id
root_block
[
'edit
_info'
][
'edit
ed_by'
]
=
user_id
root_block
[
'
previous_version'
]
=
root_block
.
get
(
'update_version'
)
root_block
[
'
edit_info'
][
'previous_version'
]
=
root_block
[
'edit_info'
]
.
get
(
'update_version'
)
# insert updates the '_id' in draft_structure
# insert updates the '_id' in draft_structure
new_id
=
self
.
structures
.
insert
(
draft_structure
)
new_id
=
self
.
structures
.
insert
(
draft_structure
)
versions_dict
[
master_
version
]
=
new_id
versions_dict
[
master_
branch
]
=
new_id
self
.
structures
.
update
({
'_id'
:
new_id
},
self
.
structures
.
update
({
'_id'
:
new_id
},
{
'$set'
:
{
'blocks.{}.update_version'
.
format
(
draft_structure
[
'root'
]):
new_id
}})
{
'$set'
:
{
'blocks.{}.
edit_info.
update_version'
.
format
(
draft_structure
[
'root'
]):
new_id
}})
# create the index entry
# create the index entry
if
id_root
is
None
:
if
id_root
is
None
:
id_root
=
org
id_root
=
org
...
@@ -795,14 +819,14 @@ class SplitMongoModuleStore(ModuleStoreBase):
...
@@ -795,14 +819,14 @@ class SplitMongoModuleStore(ModuleStoreBase):
'org'
:
org
,
'org'
:
org
,
'prettyid'
:
prettyid
,
'prettyid'
:
prettyid
,
'edited_by'
:
user_id
,
'edited_by'
:
user_id
,
'edited_on'
:
datetime
.
datetime
.
utcnow
(
),
'edited_on'
:
datetime
.
datetime
.
now
(
UTC
),
'versions'
:
versions_dict
}
'versions'
:
versions_dict
}
new_id
=
self
.
course_index
.
insert
(
index_entry
)
new_id
=
self
.
course_index
.
insert
(
index_entry
)
return
self
.
get_course
(
CourseLocator
(
course_id
=
new_id
,
branch
=
master_
version
))
return
self
.
get_course
(
CourseLocator
(
course_id
=
new_id
,
branch
=
master_
branch
))
def
update_item
(
self
,
descriptor
,
user_id
,
force
=
False
):
def
update_item
(
self
,
descriptor
,
user_id
,
force
=
False
):
"""
"""
Save the descriptor's
definition, metadata, & children references (i.e., it doesn't descend the tree)
.
Save the descriptor's
fields. it doesn't descend the course dag to save the children
.
Return the new descriptor (updated location).
Return the new descriptor (updated location).
raises ItemNotFoundError if the location does not exist.
raises ItemNotFoundError if the location does not exist.
...
@@ -819,31 +843,38 @@ class SplitMongoModuleStore(ModuleStoreBase):
...
@@ -819,31 +843,38 @@ class SplitMongoModuleStore(ModuleStoreBase):
index_entry
=
self
.
_get_index_if_valid
(
descriptor
.
location
,
force
)
index_entry
=
self
.
_get_index_if_valid
(
descriptor
.
location
,
force
)
descriptor
.
definition_locator
,
is_updated
=
self
.
update_definition_from_data
(
descriptor
.
definition_locator
,
is_updated
=
self
.
update_definition_from_data
(
descriptor
.
definition_locator
,
descriptor
.
xblock_kvs
.
get_data
(
),
user_id
)
descriptor
.
definition_locator
,
descriptor
.
get_explicitly_set_fields_by_scope
(
Scope
.
content
),
user_id
)
# check children
# check children
original_entry
=
original_structure
[
'blocks'
][
descriptor
.
location
.
usage_id
]
original_entry
=
original_structure
[
'blocks'
][
descriptor
.
location
.
usage_id
]
if
(
not
is_updated
and
descriptor
.
has_children
if
(
not
is_updated
and
descriptor
.
has_children
and
not
self
.
_xblock_lists_equal
(
original_entry
[
'children'
],
descriptor
.
children
)):
and
not
self
.
_xblock_lists_equal
(
original_entry
[
'
fields'
][
'
children'
],
descriptor
.
children
)):
is_updated
=
True
is_updated
=
True
# check metadata
# check metadata
if
not
is_updated
:
if
not
is_updated
:
is_updated
=
self
.
_compare_metadata
(
descriptor
.
xblock_kvs
.
get_own_metadata
(),
original_entry
[
'metadata'
])
is_updated
=
self
.
_compare_settings
(
descriptor
.
get_explicitly_set_fields_by_scope
(
Scope
.
settings
),
original_entry
[
'fields'
]
)
# if updated, rev the structure
# if updated, rev the structure
if
is_updated
:
if
is_updated
:
new_structure
=
self
.
_version_structure
(
original_structure
,
user_id
)
new_structure
=
self
.
_version_structure
(
original_structure
,
user_id
)
block_data
=
new_structure
[
'blocks'
][
descriptor
.
location
.
usage_id
]
block_data
=
new_structure
[
'blocks'
][
descriptor
.
location
.
usage_id
]
if
descriptor
.
has_children
:
block_data
[
"children"
]
=
[
self
.
_usage_id
(
child
)
for
child
in
descriptor
.
children
]
block_data
[
"definition"
]
=
descriptor
.
definition_locator
.
definition_id
block_data
[
"definition"
]
=
descriptor
.
definition_locator
.
definition_id
block_data
[
"metadata"
]
=
descriptor
.
xblock_kvs
.
get_own_metadata
()
block_data
[
"fields"
]
=
descriptor
.
get_explicitly_set_fields_by_scope
(
Scope
.
settings
)
block_data
[
'edited_on'
]
=
datetime
.
datetime
.
utcnow
()
if
descriptor
.
has_children
:
block_data
[
'edited_by'
]
=
user_id
block_data
[
'fields'
][
"children"
]
=
[
self
.
_usage_id
(
child
)
for
child
in
descriptor
.
children
]
block_data
[
'previous_version'
]
=
block_data
[
'update_version'
]
block_data
[
'edit_info'
]
=
{
'edited_on'
:
datetime
.
datetime
.
now
(
UTC
),
'edited_by'
:
user_id
,
'previous_version'
:
block_data
[
'edit_info'
][
'update_version'
],
}
new_id
=
self
.
structures
.
insert
(
new_structure
)
new_id
=
self
.
structures
.
insert
(
new_structure
)
self
.
structures
.
update
({
'_id'
:
new_id
},
self
.
structures
.
update
(
{
'$set'
:
{
'blocks.{}.update_version'
.
format
(
descriptor
.
location
.
usage_id
):
new_id
}})
{
'_id'
:
new_id
},
{
'$set'
:
{
'blocks.{}.edit_info.update_version'
.
format
(
descriptor
.
location
.
usage_id
):
new_id
}})
# update the index entry if appropriate
# update the index entry if appropriate
if
index_entry
is
not
None
:
if
index_entry
is
not
None
:
...
@@ -869,8 +900,8 @@ class SplitMongoModuleStore(ModuleStoreBase):
...
@@ -869,8 +900,8 @@ class SplitMongoModuleStore(ModuleStoreBase):
returns the post-persisted version of the incoming xblock. Note that its children will be ids not
returns the post-persisted version of the incoming xblock. Note that its children will be ids not
objects.
objects.
:param xblock:
:param xblock:
the head of the dag
:param user_id:
:param user_id:
who's doing the change
"""
"""
# find course_index entry if applicable and structures entry
# find course_index entry if applicable and structures entry
index_entry
=
self
.
_get_index_if_valid
(
xblock
.
location
,
force
)
index_entry
=
self
.
_get_index_if_valid
(
xblock
.
location
,
force
)
...
@@ -883,7 +914,7 @@ class SplitMongoModuleStore(ModuleStoreBase):
...
@@ -883,7 +914,7 @@ class SplitMongoModuleStore(ModuleStoreBase):
new_id
=
self
.
structures
.
insert
(
new_structure
)
new_id
=
self
.
structures
.
insert
(
new_structure
)
update_command
=
{}
update_command
=
{}
for
usage_id
in
changed_blocks
:
for
usage_id
in
changed_blocks
:
update_command
[
'blocks.{}.update_version'
.
format
(
usage_id
)]
=
new_id
update_command
[
'blocks.{}.
edit_info.
update_version'
.
format
(
usage_id
)]
=
new_id
self
.
structures
.
update
({
'_id'
:
new_id
},
{
'$set'
:
update_command
})
self
.
structures
.
update
({
'_id'
:
new_id
},
{
'$set'
:
update_command
})
# update the index entry if appropriate
# update the index entry if appropriate
...
@@ -897,14 +928,14 @@ class SplitMongoModuleStore(ModuleStoreBase):
...
@@ -897,14 +928,14 @@ class SplitMongoModuleStore(ModuleStoreBase):
def
_persist_subdag
(
self
,
xblock
,
user_id
,
structure_blocks
):
def
_persist_subdag
(
self
,
xblock
,
user_id
,
structure_blocks
):
# persist the definition if persisted != passed
# persist the definition if persisted != passed
new_def_data
=
xblock
.
xblock_kvs
.
get_data
(
)
new_def_data
=
self
.
_filter_special_fields
(
xblock
.
get_explicitly_set_fields_by_scope
(
Scope
.
content
)
)
if
(
xblock
.
definition_locator
is
None
or
xblock
.
definition_locator
.
definition_id
is
None
):
if
(
xblock
.
definition_locator
is
None
or
xblock
.
definition_locator
.
definition_id
is
None
):
xblock
.
definition_locator
=
self
.
create_definition_from_data
(
new_def_data
,
xblock
.
definition_locator
=
self
.
create_definition_from_data
(
xblock
.
category
,
user_id
)
new_def_data
,
xblock
.
category
,
user_id
)
is_updated
=
True
is_updated
=
True
elif
new_def_data
is
not
None
:
elif
new_def_data
:
xblock
.
definition_locator
,
is_updated
=
self
.
update_definition_from_data
(
xblock
.
definition_locator
,
xblock
.
definition_locator
,
is_updated
=
self
.
update_definition_from_data
(
new_def_data
,
user_id
)
xblock
.
definition_locator
,
new_def_data
,
user_id
)
if
xblock
.
location
.
usage_id
is
None
:
if
xblock
.
location
.
usage_id
is
None
:
# generate an id
# generate an id
...
@@ -916,7 +947,7 @@ class SplitMongoModuleStore(ModuleStoreBase):
...
@@ -916,7 +947,7 @@ class SplitMongoModuleStore(ModuleStoreBase):
is_new
=
False
is_new
=
False
usage_id
=
xblock
.
location
.
usage_id
usage_id
=
xblock
.
location
.
usage_id
if
(
not
is_updated
and
xblock
.
has_children
if
(
not
is_updated
and
xblock
.
has_children
and
not
self
.
_xblock_lists_equal
(
structure_blocks
[
usage_id
][
'children'
],
xblock
.
children
)):
and
not
self
.
_xblock_lists_equal
(
structure_blocks
[
usage_id
][
'
fields'
][
'
children'
],
xblock
.
children
)):
is_updated
=
True
is_updated
=
True
children
=
[]
children
=
[]
...
@@ -930,41 +961,52 @@ class SplitMongoModuleStore(ModuleStoreBase):
...
@@ -930,41 +961,52 @@ class SplitMongoModuleStore(ModuleStoreBase):
children
.
append
(
child
)
children
.
append
(
child
)
is_updated
=
is_updated
or
updated_blocks
is_updated
=
is_updated
or
updated_blocks
metadata
=
xblock
.
xblock_kvs
.
get_own_metadata
(
)
block_fields
=
xblock
.
get_explicitly_set_fields_by_scope
(
Scope
.
settings
)
if
not
is_new
and
not
is_updated
:
if
not
is_new
and
not
is_updated
:
is_updated
=
self
.
_compare_metadata
(
metadata
,
structure_blocks
[
usage_id
][
'metadata'
])
is_updated
=
self
.
_compare_settings
(
block_fields
,
structure_blocks
[
usage_id
][
'fields'
])
if
children
:
block_fields
[
'children'
]
=
children
if
is_updated
:
if
is_updated
:
previous_version
=
None
if
is_new
else
structure_blocks
[
usage_id
][
'edit_info'
]
.
get
(
'update_version'
)
structure_blocks
[
usage_id
]
=
{
structure_blocks
[
usage_id
]
=
{
"children"
:
children
,
"category"
:
xblock
.
category
,
"category"
:
xblock
.
category
,
"definition"
:
xblock
.
definition_locator
.
definition_id
,
"definition"
:
xblock
.
definition_locator
.
definition_id
,
"metadata"
:
metadata
if
metadata
else
{},
"fields"
:
block_fields
,
'previous_version'
:
structure_blocks
.
get
(
usage_id
,
{})
.
get
(
'update_version'
),
'edit_info'
:
{
'edited_by'
:
user_id
,
'previous_version'
:
previous_version
,
'edited_on'
:
datetime
.
datetime
.
utcnow
()
'edited_by'
:
user_id
,
'edited_on'
:
datetime
.
datetime
.
now
(
UTC
)
}
}
}
updated_blocks
.
append
(
usage_id
)
updated_blocks
.
append
(
usage_id
)
return
updated_blocks
return
updated_blocks
def
_compare_metadata
(
self
,
metadata
,
original_metadata
):
def
_compare_settings
(
self
,
settings
,
original_fields
):
original_keys
=
original_metadata
.
keys
()
"""
if
len
(
metadata
)
!=
len
(
original_keys
):
Return True if the settings are not == to the original fields
:param settings:
:param original_fields:
"""
original_keys
=
original_fields
.
keys
()
if
'children'
in
original_keys
:
original_keys
.
remove
(
'children'
)
if
len
(
settings
)
!=
len
(
original_keys
):
return
True
return
True
else
:
else
:
new_keys
=
metadata
.
keys
()
new_keys
=
settings
.
keys
()
for
key
in
original_keys
:
for
key
in
original_keys
:
if
key
not
in
new_keys
or
original_
metadata
[
key
]
!=
metadata
[
key
]:
if
key
not
in
new_keys
or
original_
fields
[
key
]
!=
settings
[
key
]:
return
True
return
True
# TODO change all callers to update_item
def
update_children
(
self
,
location
,
children
):
def
update_children
(
self
,
course_id
,
location
,
children
):
'''Deprecated, use update_item.'''
raise
NotImplementedError
()
raise
NotImplementedError
(
'use update_item'
)
# TODO change all callers to update_item
def
update_metadata
(
self
,
location
,
metadata
):
def
update_metadata
(
self
,
course_id
,
location
,
metadata
):
'''Deprecated, use update_item.'''
raise
NotImplementedError
()
raise
NotImplementedError
(
'use update_item'
)
def
update_course_index
(
self
,
course_locator
,
new_values_dict
,
update_versions
=
False
):
def
update_course_index
(
self
,
course_locator
,
new_values_dict
,
update_versions
=
False
):
"""
"""
...
@@ -992,9 +1034,9 @@ class SplitMongoModuleStore(ModuleStoreBase):
...
@@ -992,9 +1034,9 @@ class SplitMongoModuleStore(ModuleStoreBase):
self
.
course_index
.
update
({
'_id'
:
course_locator
.
course_id
},
self
.
course_index
.
update
({
'_id'
:
course_locator
.
course_id
},
{
'$set'
:
new_values_dict
})
{
'$set'
:
new_values_dict
})
def
delete_item
(
self
,
usage_locator
,
user_id
,
force
=
False
):
def
delete_item
(
self
,
usage_locator
,
user_id
,
delete_children
=
False
,
force
=
False
):
"""
"""
Delete the
tree rooted at block
and any references w/in the course to the block
Delete the
block or tree rooted at block (if delete_children)
and any references w/in the course to the block
from a new version of the course structure.
from a new version of the course structure.
returns CourseLocator for new version
returns CourseLocator for new version
...
@@ -1018,17 +1060,18 @@ class SplitMongoModuleStore(ModuleStoreBase):
...
@@ -1018,17 +1060,18 @@ class SplitMongoModuleStore(ModuleStoreBase):
update_version_keys
=
[]
update_version_keys
=
[]
for
parent
in
parents
:
for
parent
in
parents
:
parent_block
=
new_blocks
[
parent
.
usage_id
]
parent_block
=
new_blocks
[
parent
.
usage_id
]
parent_block
[
'children'
]
.
remove
(
usage_locator
.
usage_id
)
parent_block
[
'
fields'
][
'
children'
]
.
remove
(
usage_locator
.
usage_id
)
parent_block
[
'edit
ed_on'
]
=
datetime
.
datetime
.
utcnow
(
)
parent_block
[
'edit
_info'
][
'edited_on'
]
=
datetime
.
datetime
.
now
(
UTC
)
parent_block
[
'edited_by'
]
=
user_id
parent_block
[
'edit
_info'
][
'edit
ed_by'
]
=
user_id
parent_block
[
'
previous_version'
]
=
parent_block
[
'update_version'
]
parent_block
[
'
edit_info'
][
'previous_version'
]
=
parent_block
[
'edit_info'
]
[
'update_version'
]
update_version_keys
.
append
(
'blocks.{}.update_version'
.
format
(
parent
.
usage_id
))
update_version_keys
.
append
(
'blocks.{}.
edit_info.
update_version'
.
format
(
parent
.
usage_id
))
# remove subtree
# remove subtree
def
remove_subtree
(
usage_id
):
def
remove_subtree
(
usage_id
):
for
child
in
new_blocks
[
usage_id
][
'
children'
]
:
for
child
in
new_blocks
[
usage_id
][
'
fields'
]
.
get
(
'children'
,
[])
:
remove_subtree
(
child
)
remove_subtree
(
child
)
del
new_blocks
[
usage_id
]
del
new_blocks
[
usage_id
]
remove_subtree
(
usage_locator
.
usage_id
)
if
delete_children
:
remove_subtree
(
usage_locator
.
usage_id
)
# update index if appropriate and structures
# update index if appropriate and structures
new_id
=
self
.
structures
.
insert
(
new_structure
)
new_id
=
self
.
structures
.
insert
(
new_structure
)
...
@@ -1062,32 +1105,38 @@ class SplitMongoModuleStore(ModuleStoreBase):
...
@@ -1062,32 +1105,38 @@ class SplitMongoModuleStore(ModuleStoreBase):
# this is the only real delete in the system. should it do something else?
# this is the only real delete in the system. should it do something else?
self
.
course_index
.
remove
(
index
[
'_id'
])
self
.
course_index
.
remove
(
index
[
'_id'
])
def
inherit_metadata
(
self
,
block_map
,
block
,
inheriting_metadata
=
None
):
def
get_errored_courses
(
self
):
"""
This function doesn't make sense for the mongo modulestore, as structures
are loaded on demand, rather than up front
"""
return
{}
def
inherit_settings
(
self
,
block_map
,
block
,
inheriting_settings
=
None
):
"""
"""
Updates block with any value
Updates block with any inheritable setting set by an ancestor and recurses to children.
that exist in inheriting_metadata and don't appear in block['metadata'],
and then inherits block['metadata'] to all of the children in
block['children']. Filters by inheritance.INHERITABLE_METADATA
"""
"""
if
block
is
None
:
if
block
is
None
:
return
return
if
inheriting_
metadata
is
None
:
if
inheriting_
settings
is
None
:
inheriting_
metadata
=
{}
inheriting_
settings
=
{}
# the currently passed down values take precedence over any previously cached ones
# the currently passed down values take precedence over any previously cached ones
# NOTE: this should show the values which all fields would have if inherited: i.e.,
# NOTE: this should show the values which all fields would have if inherited: i.e.,
# not set to the locally defined value but to value set by nearest ancestor who sets it
# not set to the locally defined value but to value set by nearest ancestor who sets it
block
.
setdefault
(
'_inherited_metadata'
,
{})
.
update
(
inheriting_metadata
)
# ALSO NOTE: no xblock should ever define a _inherited_settings field as it will collide w/ this logic.
block
.
setdefault
(
'_inherited_settings'
,
{})
.
update
(
inheriting_settings
)
# update the inheriting w/ what should pass to children
# update the inheriting w/ what should pass to children
inheriting_metadata
=
block
[
'_inherited_metadata'
]
.
copy
()
inheriting_settings
=
block
[
'_inherited_settings'
]
.
copy
()
block_fields
=
block
[
'fields'
]
for
field
in
inheritance
.
INHERITABLE_METADATA
:
for
field
in
inheritance
.
INHERITABLE_METADATA
:
if
field
in
block
[
'metadata'
]
:
if
field
in
block
_fields
:
inheriting_
metadata
[
field
]
=
block
[
'metadata'
]
[
field
]
inheriting_
settings
[
field
]
=
block_fields
[
field
]
for
child
in
block
.
get
(
'children'
,
[]):
for
child
in
block
_fields
.
get
(
'children'
,
[]):
self
.
inherit_
metadata
(
block_map
,
block_map
[
child
],
inheriting_metadata
)
self
.
inherit_
settings
(
block_map
,
block_map
[
child
],
inheriting_settings
)
def
descendants
(
self
,
block_map
,
usage_id
,
depth
,
descendent_map
):
def
descendants
(
self
,
block_map
,
usage_id
,
depth
,
descendent_map
):
"""
"""
...
@@ -1104,7 +1153,7 @@ class SplitMongoModuleStore(ModuleStoreBase):
...
@@ -1104,7 +1153,7 @@ class SplitMongoModuleStore(ModuleStoreBase):
if
depth
is
None
or
depth
>
0
:
if
depth
is
None
or
depth
>
0
:
depth
=
depth
-
1
if
depth
is
not
None
else
None
depth
=
depth
-
1
if
depth
is
not
None
else
None
for
child
in
block_map
[
usage_id
]
.
get
(
'children'
,
[]):
for
child
in
block_map
[
usage_id
]
[
'fields'
]
.
get
(
'children'
,
[]):
descendent_map
=
self
.
descendants
(
block_map
,
child
,
depth
,
descendent_map
=
self
.
descendants
(
block_map
,
child
,
depth
,
descendent_map
)
descendent_map
)
...
@@ -1217,7 +1266,7 @@ class SplitMongoModuleStore(ModuleStoreBase):
...
@@ -1217,7 +1266,7 @@ class SplitMongoModuleStore(ModuleStoreBase):
del
new_structure
[
'_id'
]
del
new_structure
[
'_id'
]
new_structure
[
'previous_version'
]
=
structure
[
'_id'
]
new_structure
[
'previous_version'
]
=
structure
[
'_id'
]
new_structure
[
'edited_by'
]
=
user_id
new_structure
[
'edited_by'
]
=
user_id
new_structure
[
'edited_on'
]
=
datetime
.
datetime
.
utcnow
(
)
new_structure
[
'edited_on'
]
=
datetime
.
datetime
.
now
(
UTC
)
return
new_structure
return
new_structure
def
_find_local_root
(
self
,
element_to_find
,
possibility
,
tree
):
def
_find_local_root
(
self
,
element_to_find
,
possibility
,
tree
):
...
@@ -1242,3 +1291,31 @@ class SplitMongoModuleStore(ModuleStoreBase):
...
@@ -1242,3 +1291,31 @@ class SplitMongoModuleStore(ModuleStoreBase):
self
.
course_index
.
update
(
self
.
course_index
.
update
(
{
"_id"
:
index_entry
[
"_id"
]},
{
"_id"
:
index_entry
[
"_id"
]},
{
"$set"
:
{
"versions.{}"
.
format
(
branch
):
new_id
}})
{
"$set"
:
{
"versions.{}"
.
format
(
branch
):
new_id
}})
def
_partition_fields_by_scope
(
self
,
category
,
fields
):
"""
Return dictionary of {scope: {field1: val, ..}..} for the fields of this potential xblock
:param category: the xblock category
:param fields: the dictionary of {fieldname: value}
"""
if
fields
is
None
:
return
{}
cls
=
XModuleDescriptor
.
load_class
(
category
)
result
=
collections
.
defaultdict
(
dict
)
for
field_name
,
value
in
fields
.
iteritems
():
field
=
getattr
(
cls
,
field_name
)
result
[
field
.
scope
][
field_name
]
=
value
return
result
def
_filter_special_fields
(
self
,
fields
):
"""
Remove any fields which split or its kvs computes or adds but does not want persisted.
:param fields: a dict of fields
"""
if
'location'
in
fields
:
del
fields
[
'location'
]
if
'category'
in
fields
:
del
fields
[
'category'
]
return
fields
common/lib/xmodule/xmodule/modulestore/split_mongo/split_mongo_kvs.py
View file @
7f126f13
...
@@ -8,156 +8,175 @@ from .definition_lazy_loader import DefinitionLazyLoader
...
@@ -8,156 +8,175 @@ from .definition_lazy_loader import DefinitionLazyLoader
SplitMongoKVSid
=
namedtuple
(
'SplitMongoKVSid'
,
'id, def_id'
)
SplitMongoKVSid
=
namedtuple
(
'SplitMongoKVSid'
,
'id, def_id'
)
# TODO should this be here or w/ x_module or ???
PROVENANCE_LOCAL
=
'local'
PROVENANCE_DEFAULT
=
'default'
PROVENANCE_INHERITED
=
'inherited'
class
SplitMongoKVS
(
KeyValueStore
):
class
SplitMongoKVS
(
KeyValueStore
):
"""
"""
A KeyValueStore that maps keyed data access to one of the 3 data areas
A KeyValueStore that maps keyed data access to one of the 3 data areas
known to the MongoModuleStore (data, children, and metadata)
known to the MongoModuleStore (data, children, and metadata)
"""
"""
def
__init__
(
self
,
definition
,
children
,
metadata
,
_inherited_metadata
,
location
,
category
):
def
__init__
(
self
,
definition
,
fields
,
_inherited_settings
,
location
,
category
):
"""
"""
:param definition:
:param definition: either a lazyloader or definition id for the definition
:param children:
:param fields: a dictionary of the locally set fields
:param metadata: the locally defined value for each metadata field
:param _inherited_settings: the value of each inheritable field from above this.
:param _inherited_metadata: the value of each inheritable field from above this.
Note, local fields may override and disagree w/ this b/c this says what the value
Note, metadata may override and disagree w/ this b/c this says what the value
should be if the field is undefined.
should be if metadata is undefined for this field.
"""
"""
# ensure kvs's don't share objects w/ others so that changes can't appear in separate ones
# ensure kvs's don't share objects w/ others so that changes can't appear in separate ones
# the particular use case was that changes to kvs's were polluting caches. My thinking was
# the particular use case was that changes to kvs's were polluting caches. My thinking was
# that kvs's should be independent thus responsible for the isolation.
# that kvs's should be independent thus responsible for the isolation.
if
isinstance
(
definition
,
DefinitionLazyLoader
):
self
.
_definition
=
definition
# either a DefinitionLazyLoader or the db id of the definition.
self
.
_definition
=
definition
# if the db id, then the definition is presumed to be loaded into _fields
else
:
self
.
_fields
=
copy
.
copy
(
fields
)
self
.
_definition
=
copy
.
copy
(
definition
)
self
.
_inherited_settings
=
_inherited_settings
self
.
_children
=
copy
.
copy
(
children
)
self
.
_metadata
=
copy
.
copy
(
metadata
)
self
.
_inherited_metadata
=
_inherited_metadata
self
.
_location
=
location
self
.
_location
=
location
self
.
_category
=
category
self
.
_category
=
category
def
get
(
self
,
key
):
def
get
(
self
,
key
):
if
key
.
scope
==
Scope
.
children
:
# simplest case, field is directly set
return
self
.
_children
if
key
.
field_name
in
self
.
_fields
:
elif
key
.
scope
==
Scope
.
parent
:
return
self
.
_fields
[
key
.
field_name
]
# parent undefined in editing runtime (I think)
if
key
.
scope
==
Scope
.
parent
:
# see STUD-624. Right now copies MongoKeyValueStore.get's behavior of returning None
return
None
return
None
if
key
.
scope
==
Scope
.
children
:
# didn't find children in _fields; so, see if there's a default
raise
KeyError
()
elif
key
.
scope
==
Scope
.
settings
:
elif
key
.
scope
==
Scope
.
settings
:
if
key
.
field_name
in
self
.
_metadata
:
# didn't find in _fields; so, get from inheritance since not locally set
return
self
.
_metadata
[
key
.
field_name
]
if
key
.
field_name
in
self
.
_inherited_settings
:
elif
key
.
field_name
in
self
.
_inherited_metadata
:
return
self
.
_inherited_settings
[
key
.
field_name
]
return
self
.
_inherited_metadata
[
key
.
field_name
]
else
:
else
:
# or get default
raise
KeyError
()
raise
KeyError
()
elif
key
.
scope
==
Scope
.
content
:
elif
key
.
scope
==
Scope
.
content
:
if
key
.
field_name
==
'location'
:
if
key
.
field_name
==
'location'
:
return
self
.
_location
return
self
.
_location
elif
key
.
field_name
==
'category'
:
elif
key
.
field_name
==
'category'
:
return
self
.
_category
return
self
.
_category
else
:
elif
isinstance
(
self
.
_definition
,
DefinitionLazyLoader
):
if
isinstance
(
self
.
_definition
,
DefinitionLazyLoader
):
self
.
_load_definition
()
self
.
_definition
=
self
.
_definition
.
fetch
()
if
key
.
field_name
in
self
.
_fields
:
if
(
key
.
field_name
==
'data'
and
return
self
.
_fields
[
key
.
field_name
]
not
isinstance
(
self
.
_definition
.
get
(
'data'
),
dict
)):
return
self
.
_definition
.
get
(
'data'
)
raise
KeyError
()
elif
'data'
not
in
self
.
_definition
or
key
.
field_name
not
in
self
.
_definition
[
'data'
]:
raise
KeyError
()
else
:
return
self
.
_definition
[
'data'
][
key
.
field_name
]
else
:
else
:
raise
InvalidScopeError
(
key
.
scope
)
raise
InvalidScopeError
(
key
.
scope
)
def
set
(
self
,
key
,
value
):
def
set
(
self
,
key
,
value
):
# TODO cache db update implications & add method to invoke
# handle any special cases
if
key
.
scope
==
Scope
.
children
:
if
key
.
scope
not
in
[
Scope
.
children
,
Scope
.
settings
,
Scope
.
content
]:
self
.
_children
=
value
raise
InvalidScopeError
(
key
.
scope
)
# TODO remove inheritance from any orphaned exchildren
if
key
.
scope
==
Scope
.
content
:
# TODO add inheritance to any new children
elif
key
.
scope
==
Scope
.
settings
:
# TODO if inheritable, push down to children who don't override
self
.
_metadata
[
key
.
field_name
]
=
value
elif
key
.
scope
==
Scope
.
content
:
if
key
.
field_name
==
'location'
:
if
key
.
field_name
==
'location'
:
self
.
_location
=
value
self
.
_location
=
value
# is changing this legal?
return
elif
key
.
field_name
==
'category'
:
elif
key
.
field_name
==
'category'
:
self
.
_category
=
value
# TODO should this raise an exception? category is not changeable.
return
else
:
else
:
if
isinstance
(
self
.
_definition
,
DefinitionLazyLoader
):
self
.
_load_definition
()
self
.
_definition
=
self
.
_definition
.
fetch
()
if
(
key
.
field_name
==
'data'
and
# set the field
not
isinstance
(
self
.
_definition
.
get
(
'data'
),
dict
)):
self
.
_fields
[
key
.
field_name
]
=
value
self
.
_definition
.
get
(
'data'
)
else
:
# handle any side effects -- story STUD-624
self
.
_definition
.
setdefault
(
'data'
,
{})[
key
.
field_name
]
=
value
# if key.scope == Scope.children:
else
:
# STUD-624 remove inheritance from any exchildren
raise
InvalidScopeError
(
key
.
scope
)
# STUD-624 add inheritance to any new children
# if key.scope == Scope.settings:
# STUD-624 if inheritable, push down to children
def
delete
(
self
,
key
):
def
delete
(
self
,
key
):
# TODO cache db update implications & add method to invoke
# handle any special cases
if
key
.
scope
==
Scope
.
children
:
if
key
.
scope
not
in
[
Scope
.
children
,
Scope
.
settings
,
Scope
.
content
]:
self
.
_children
=
[]
raise
InvalidScopeError
(
key
.
scope
)
elif
key
.
scope
==
Scope
.
settings
:
if
key
.
scope
==
Scope
.
content
:
# TODO if inheritable, ensure _inherited_metadata has value from above and
# revert children to that value
if
key
.
field_name
in
self
.
_metadata
:
del
self
.
_metadata
[
key
.
field_name
]
elif
key
.
scope
==
Scope
.
content
:
# don't allow deletion of location nor category
if
key
.
field_name
==
'location'
:
if
key
.
field_name
==
'location'
:
pass
return
# noop
elif
key
.
field_name
==
'category'
:
elif
key
.
field_name
==
'category'
:
pass
# TODO should this raise an exception? category is not deleteable.
return
# noop
else
:
else
:
if
isinstance
(
self
.
_definition
,
DefinitionLazyLoader
):
self
.
_load_definition
()
self
.
_definition
=
self
.
_definition
.
fetch
()
if
(
key
.
field_name
==
'data'
and
# delete the field value
not
isinstance
(
self
.
_definition
.
get
(
'data'
),
dict
)):
if
key
.
field_name
in
self
.
_fields
:
self
.
_definition
.
setdefault
(
'data'
,
None
)
del
self
.
_fields
[
key
.
field_name
]
else
:
try
:
# handle any side effects
del
self
.
_definition
[
'data'
][
key
.
field_name
]
# if key.scope == Scope.children:
except
KeyError
:
# STUD-624 remove inheritance from any exchildren
pass
# if key.scope == Scope.settings:
else
:
# STUD-624 if inheritable, push down _inherited_settings value to children
raise
InvalidScopeError
(
key
.
scope
)
def
has
(
self
,
key
):
def
has
(
self
,
key
):
if
key
.
scope
in
(
Scope
.
children
,
Scope
.
parent
):
"""
return
True
Is the given field explicitly set in this kvs (not inherited nor default)
elif
key
.
scope
==
Scope
.
settings
:
"""
return
key
.
field_name
in
self
.
_metadata
or
key
.
field_name
in
self
.
_inherited_metadata
# handle any special cases
el
if
key
.
scope
==
Scope
.
content
:
if
key
.
scope
==
Scope
.
content
:
if
key
.
field_name
==
'location'
:
if
key
.
field_name
==
'location'
:
return
True
return
True
elif
key
.
field_name
==
'category'
:
elif
key
.
field_name
==
'category'
:
return
self
.
_category
is
not
None
return
self
.
_category
is
not
None
else
:
else
:
if
isinstance
(
self
.
_definition
,
DefinitionLazyLoader
):
self
.
_load_definition
()
self
.
_definition
=
self
.
_definition
.
fetch
()
elif
key
.
scope
==
Scope
.
parent
:
if
(
key
.
field_name
==
'data'
and
return
True
not
isinstance
(
self
.
_definition
.
get
(
'data'
),
dict
)):
return
self
.
_definition
.
get
(
'data'
)
is
not
None
# it's not clear whether inherited values should return True. Right now they don't
else
:
# if someone changes it so that they do, then change any tests of field.name in xx._model_data
return
key
.
field_name
in
self
.
_definition
.
get
(
'data'
,
{})
return
key
.
field_name
in
self
.
_fields
else
:
return
False
def
get_data
(
self
):
# would like to just take a key, but there's a bunch of magic in DbModel for constructing the key via
# a private method
def
field_value_provenance
(
self
,
key_scope
,
key_name
):
"""
"""
Intended only for use by persistence layer to get the native definition['data'] rep
Where the field value comes from: one of [PROVENANCE_LOCAL, PROVENANCE_DEFAULT, PROVENANCE_INHERITED].
"""
"""
if
isinstance
(
self
.
_definition
,
DefinitionLazyLoader
):
# handle any special cases
self
.
_definition
=
self
.
_definition
.
fetch
()
if
key_scope
==
Scope
.
content
:
return
self
.
_definition
.
get
(
'data'
)
if
key_name
==
'location'
:
return
PROVENANCE_LOCAL
elif
key_name
==
'category'
:
return
PROVENANCE_LOCAL
else
:
self
.
_load_definition
()
if
key_name
in
self
.
_fields
:
return
PROVENANCE_LOCAL
else
:
return
PROVENANCE_DEFAULT
elif
key_scope
==
Scope
.
parent
:
return
PROVENANCE_DEFAULT
# catch the locally set state
elif
key_name
in
self
.
_fields
:
return
PROVENANCE_LOCAL
elif
key_scope
==
Scope
.
settings
and
key_name
in
self
.
_inherited_settings
:
return
PROVENANCE_INHERITED
else
:
return
PROVENANCE_DEFAULT
def
get_
own_metadata
(
self
):
def
get_
inherited_settings
(
self
):
"""
"""
Get the
metadata explicitly set on this element.
Get the
settings set by the ancestors (which locally set fields may override or not)
"""
"""
return
self
.
_
metadata
return
self
.
_
inherited_settings
def
get_inherited_metadata
(
self
):
def
_load_definition
(
self
):
"""
"""
Get the metadata set by the ancestors (which own metadata may override or not)
Update fields w/ the lazily loaded definitions
"""
"""
return
self
.
_inherited_metadata
if
isinstance
(
self
.
_definition
,
DefinitionLazyLoader
):
persisted_definition
=
self
.
_definition
.
fetch
()
if
persisted_definition
is
not
None
:
self
.
_fields
.
update
(
persisted_definition
.
get
(
'fields'
))
# do we want to cache any of the edit_info?
self
.
_definition
=
None
# already loaded
common/lib/xmodule/xmodule/modulestore/tests/persistent_factories.py
View file @
7f126f13
...
@@ -11,44 +11,24 @@ class PersistentCourseFactory(factory.Factory):
...
@@ -11,44 +11,24 @@ class PersistentCourseFactory(factory.Factory):
"""
"""
Create a new course (not a new version of a course, but a whole new index entry).
Create a new course (not a new version of a course, but a whole new index entry).
keywords:
keywords: any xblock field plus (note, the below are filtered out; so, if they
become legitimate xblock fields, they won't be settable via this factory)
* org: defaults to textX
* org: defaults to textX
* prettyid: defaults to 999
* prettyid: defaults to 999
* display_name
* master_branch: (optional) defaults to 'draft'
* user_id
* user_id: (optional) defaults to 'test_user'
* data (optional) the data payload to save in the course item
* display_name (xblock field): will default to 'Robot Super Course' unless provided
* metadata (optional) the metadata payload. If display_name is in the metadata, that takes
precedence over any display_name provided directly.
"""
"""
FACTORY_FOR
=
CourseDescriptor
FACTORY_FOR
=
CourseDescriptor
org
=
'testX'
prettyid
=
'999'
display_name
=
'Robot Super Course'
user_id
=
"test_user"
data
=
None
metadata
=
None
master_version
=
'draft'
# pylint: disable=W0613
# pylint: disable=W0613
@classmethod
@classmethod
def
_create
(
cls
,
target_class
,
*
args
,
**
kwargs
):
def
_create
(
cls
,
target_class
,
org
=
'testX'
,
prettyid
=
'999'
,
user_id
=
'test_user'
,
master_branch
=
'draft'
,
**
kwargs
):
org
=
kwargs
.
get
(
'org'
)
prettyid
=
kwargs
.
get
(
'prettyid'
)
display_name
=
kwargs
.
get
(
'display_name'
)
user_id
=
kwargs
.
get
(
'user_id'
)
data
=
kwargs
.
get
(
'data'
)
metadata
=
kwargs
.
get
(
'metadata'
,
{})
if
metadata
is
None
:
metadata
=
{}
if
'display_name'
not
in
metadata
:
metadata
[
'display_name'
]
=
display_name
# Write the data to the mongo datastore
# Write the data to the mongo datastore
new_course
=
modulestore
(
'split'
)
.
create_course
(
new_course
=
modulestore
(
'split'
)
.
create_course
(
org
,
prettyid
,
user_id
,
metadata
=
metadata
,
course_data
=
data
,
id_root
=
prettyid
,
org
,
prettyid
,
user_id
,
fields
=
kwargs
,
id_root
=
prettyid
,
master_
version
=
kwargs
.
get
(
'master_version'
)
)
master_
branch
=
master_branch
)
return
new_course
return
new_course
...
@@ -60,36 +40,24 @@ class PersistentCourseFactory(factory.Factory):
...
@@ -60,36 +40,24 @@ class PersistentCourseFactory(factory.Factory):
class
ItemFactory
(
factory
.
Factory
):
class
ItemFactory
(
factory
.
Factory
):
FACTORY_FOR
=
XModuleDescriptor
FACTORY_FOR
=
XModuleDescriptor
category
=
'chapter'
user_id
=
'test_user'
display_name
=
factory
.
LazyAttributeSequence
(
lambda
o
,
n
:
"{} {}"
.
format
(
o
.
category
,
n
))
display_name
=
factory
.
LazyAttributeSequence
(
lambda
o
,
n
:
"{} {}"
.
format
(
o
.
category
,
n
))
# pylint: disable=W0613
# pylint: disable=W0613
@classmethod
@classmethod
def
_create
(
cls
,
target_class
,
*
args
,
**
kwargs
):
def
_create
(
cls
,
target_class
,
parent_location
,
category
=
'chapter'
,
user_id
=
'test_user'
,
definition_locator
=
None
,
**
kwargs
):
"""
"""
Uses *kwargs*:
passes *kwargs* as the new item's field values:
*parent_location* (required): the location of the course & possibly parent
*category* (defaults to 'chapter')
:param parent_location: (required) the location of the course & possibly parent
*data* (optional): the data for the item
:param category: (defaults to 'chapter')
definition_locator (optional): the DescriptorLocator for the definition this uses or branches
:param definition_locator (optional): the DescriptorLocator for the definition this uses or branches
*display_name* (optional): the display name of the item
*metadata* (optional): dictionary of metadata attributes (display_name here takes
precedence over the above attr)
"""
"""
metadata
=
kwargs
.
get
(
'metadata'
,
{})
return
modulestore
(
'split'
)
.
create_item
(
if
'display_name'
not
in
metadata
and
'display_name'
in
kwargs
:
parent_location
,
category
,
user_id
,
definition_locator
,
fields
=
kwargs
metadata
[
'display_name'
]
=
kwargs
[
'display_name'
]
)
return
modulestore
(
'split'
)
.
create_item
(
kwargs
[
'parent_location'
],
kwargs
[
'category'
],
kwargs
[
'user_id'
],
definition_locator
=
kwargs
.
get
(
'definition_locator'
),
new_def_data
=
kwargs
.
get
(
'data'
),
metadata
=
metadata
)
@classmethod
@classmethod
def
_build
(
cls
,
target_class
,
*
args
,
**
kwargs
):
def
_build
(
cls
,
target_class
,
*
args
,
**
kwargs
):
...
...
common/lib/xmodule/xmodule/modulestore/tests/test_split_modulestore.py
View file @
7f126f13
...
@@ -187,6 +187,7 @@ class SplitModuleCourseTests(SplitModuleTest):
...
@@ -187,6 +187,7 @@ class SplitModuleCourseTests(SplitModuleTest):
self
.
assertEqual
(
course
.
category
,
'course'
)
self
.
assertEqual
(
course
.
category
,
'course'
)
self
.
assertEqual
(
len
(
course
.
tabs
),
6
)
self
.
assertEqual
(
len
(
course
.
tabs
),
6
)
self
.
assertEqual
(
course
.
display_name
,
"The Ancient Greek Hero"
)
self
.
assertEqual
(
course
.
display_name
,
"The Ancient Greek Hero"
)
self
.
assertEqual
(
course
.
lms
.
graceperiod
,
datetime
.
timedelta
(
hours
=
2
))
self
.
assertIsNone
(
course
.
advertised_start
)
self
.
assertIsNone
(
course
.
advertised_start
)
self
.
assertEqual
(
len
(
course
.
children
),
0
)
self
.
assertEqual
(
len
(
course
.
children
),
0
)
self
.
assertEqual
(
course
.
definition_locator
.
definition_id
,
"head12345_11"
)
self
.
assertEqual
(
course
.
definition_locator
.
definition_id
,
"head12345_11"
)
...
@@ -438,12 +439,12 @@ class SplitModuleItemTests(SplitModuleTest):
...
@@ -438,12 +439,12 @@ class SplitModuleItemTests(SplitModuleTest):
qualifiers
=
qualifiers
=
{
{
'category'
:
'chapter'
,
'category'
:
'chapter'
,
'
metadata
'
:
{
'display_name'
:
{
'$regex'
:
'Hera'
}}
'
fields
'
:
{
'display_name'
:
{
'$regex'
:
'Hera'
}}
}
}
)
)
self
.
assertEqual
(
len
(
matches
),
2
)
self
.
assertEqual
(
len
(
matches
),
2
)
matches
=
modulestore
()
.
get_items
(
locator
,
qualifiers
=
{
'
children'
:
'chapter2'
})
matches
=
modulestore
()
.
get_items
(
locator
,
qualifiers
=
{
'
fields'
:
{
'children'
:
'chapter2'
}
})
self
.
assertEqual
(
len
(
matches
),
1
)
self
.
assertEqual
(
len
(
matches
),
1
)
self
.
assertEqual
(
matches
[
0
]
.
location
.
usage_id
,
'head12345'
)
self
.
assertEqual
(
matches
[
0
]
.
location
.
usage_id
,
'head12345'
)
...
@@ -507,8 +508,7 @@ class TestItemCrud(SplitModuleTest):
...
@@ -507,8 +508,7 @@ class TestItemCrud(SplitModuleTest):
def
test_create_minimal_item
(
self
):
def
test_create_minimal_item
(
self
):
"""
"""
create_item(course_or_parent_locator, category, user, definition_locator=None, new_def_data=None,
create_item(course_or_parent_locator, category, user, definition_locator=None, fields): new_desciptor
metadata=None): new_desciptor
"""
"""
# grab link to course to ensure new versioning works
# grab link to course to ensure new versioning works
locator
=
CourseLocator
(
course_id
=
"GreekHero"
,
branch
=
'draft'
)
locator
=
CourseLocator
(
course_id
=
"GreekHero"
,
branch
=
'draft'
)
...
@@ -518,7 +518,7 @@ class TestItemCrud(SplitModuleTest):
...
@@ -518,7 +518,7 @@ class TestItemCrud(SplitModuleTest):
category
=
'sequential'
category
=
'sequential'
new_module
=
modulestore
()
.
create_item
(
new_module
=
modulestore
()
.
create_item
(
locator
,
category
,
'user123'
,
locator
,
category
,
'user123'
,
metadata
=
{
'display_name'
:
'new sequential'
}
fields
=
{
'display_name'
:
'new sequential'
}
)
)
# check that course version changed and course's previous is the other one
# check that course version changed and course's previous is the other one
self
.
assertEqual
(
new_module
.
location
.
course_id
,
"GreekHero"
)
self
.
assertEqual
(
new_module
.
location
.
course_id
,
"GreekHero"
)
...
@@ -553,7 +553,7 @@ class TestItemCrud(SplitModuleTest):
...
@@ -553,7 +553,7 @@ class TestItemCrud(SplitModuleTest):
category
=
'chapter'
category
=
'chapter'
new_module
=
modulestore
()
.
create_item
(
new_module
=
modulestore
()
.
create_item
(
locator
,
category
,
'user123'
,
locator
,
category
,
'user123'
,
metadata
=
{
'display_name'
:
'new chapter'
},
fields
=
{
'display_name'
:
'new chapter'
},
definition_locator
=
DescriptionLocator
(
"chapter12345_2"
)
definition_locator
=
DescriptionLocator
(
"chapter12345_2"
)
)
)
# check that course version changed and course's previous is the other one
# check that course version changed and course's previous is the other one
...
@@ -574,15 +574,13 @@ class TestItemCrud(SplitModuleTest):
...
@@ -574,15 +574,13 @@ class TestItemCrud(SplitModuleTest):
new_payload
=
"<problem>empty</problem>"
new_payload
=
"<problem>empty</problem>"
new_module
=
modulestore
()
.
create_item
(
new_module
=
modulestore
()
.
create_item
(
locator
,
category
,
'anotheruser'
,
locator
,
category
,
'anotheruser'
,
metadata
=
{
'display_name'
:
'problem 1'
},
fields
=
{
'display_name'
:
'problem 1'
,
'data'
:
new_payload
},
new_def_data
=
new_payload
)
)
another_payload
=
"<problem>not empty</problem>"
another_payload
=
"<problem>not empty</problem>"
another_module
=
modulestore
()
.
create_item
(
another_module
=
modulestore
()
.
create_item
(
locator
,
category
,
'anotheruser'
,
locator
,
category
,
'anotheruser'
,
metadata
=
{
'display_name'
:
'problem 2'
},
fields
=
{
'display_name'
:
'problem 2'
,
'data'
:
another_payload
},
definition_locator
=
DescriptionLocator
(
"problem12345_3_1"
),
definition_locator
=
DescriptionLocator
(
"problem12345_3_1"
),
new_def_data
=
another_payload
)
)
# check that course version changed and course's previous is the other one
# check that course version changed and course's previous is the other one
parent
=
modulestore
()
.
get_item
(
locator
)
parent
=
modulestore
()
.
get_item
(
locator
)
...
@@ -616,6 +614,7 @@ class TestItemCrud(SplitModuleTest):
...
@@ -616,6 +614,7 @@ class TestItemCrud(SplitModuleTest):
self
.
assertNotEqual
(
problem
.
max_attempts
,
4
,
"Invalidates rest of test"
)
self
.
assertNotEqual
(
problem
.
max_attempts
,
4
,
"Invalidates rest of test"
)
problem
.
max_attempts
=
4
problem
.
max_attempts
=
4
problem
.
save
()
# decache above setting into the kvs
updated_problem
=
modulestore
()
.
update_item
(
problem
,
'changeMaven'
)
updated_problem
=
modulestore
()
.
update_item
(
problem
,
'changeMaven'
)
# check that course version changed and course's previous is the other one
# check that course version changed and course's previous is the other one
self
.
assertEqual
(
updated_problem
.
definition_locator
.
definition_id
,
pre_def_id
)
self
.
assertEqual
(
updated_problem
.
definition_locator
.
definition_id
,
pre_def_id
)
...
@@ -651,6 +650,7 @@ class TestItemCrud(SplitModuleTest):
...
@@ -651,6 +650,7 @@ class TestItemCrud(SplitModuleTest):
# reorder children
# reorder children
self
.
assertGreater
(
len
(
block
.
children
),
0
,
"meaningless test"
)
self
.
assertGreater
(
len
(
block
.
children
),
0
,
"meaningless test"
)
moved_child
=
block
.
children
.
pop
()
moved_child
=
block
.
children
.
pop
()
block
.
save
()
# decache model changes
updated_problem
=
modulestore
()
.
update_item
(
block
,
'childchanger'
)
updated_problem
=
modulestore
()
.
update_item
(
block
,
'childchanger'
)
# check that course version changed and course's previous is the other one
# check that course version changed and course's previous is the other one
self
.
assertEqual
(
updated_problem
.
definition_locator
.
definition_id
,
pre_def_id
)
self
.
assertEqual
(
updated_problem
.
definition_locator
.
definition_id
,
pre_def_id
)
...
@@ -660,6 +660,7 @@ class TestItemCrud(SplitModuleTest):
...
@@ -660,6 +660,7 @@ class TestItemCrud(SplitModuleTest):
locator
.
usage_id
=
"chapter1"
locator
.
usage_id
=
"chapter1"
other_block
=
modulestore
()
.
get_item
(
locator
)
other_block
=
modulestore
()
.
get_item
(
locator
)
other_block
.
children
.
append
(
moved_child
)
other_block
.
children
.
append
(
moved_child
)
other_block
.
save
()
# decache model changes
other_updated
=
modulestore
()
.
update_item
(
other_block
,
'childchanger'
)
other_updated
=
modulestore
()
.
update_item
(
other_block
,
'childchanger'
)
self
.
assertIn
(
moved_child
,
other_updated
.
children
)
self
.
assertIn
(
moved_child
,
other_updated
.
children
)
...
@@ -673,6 +674,7 @@ class TestItemCrud(SplitModuleTest):
...
@@ -673,6 +674,7 @@ class TestItemCrud(SplitModuleTest):
pre_version_guid
=
block
.
location
.
version_guid
pre_version_guid
=
block
.
location
.
version_guid
block
.
grading_policy
[
'GRADER'
][
0
][
'min_count'
]
=
13
block
.
grading_policy
[
'GRADER'
][
0
][
'min_count'
]
=
13
block
.
save
()
# decache model changes
updated_block
=
modulestore
()
.
update_item
(
block
,
'definition_changer'
)
updated_block
=
modulestore
()
.
update_item
(
block
,
'definition_changer'
)
self
.
assertNotEqual
(
updated_block
.
definition_locator
.
definition_id
,
pre_def_id
)
self
.
assertNotEqual
(
updated_block
.
definition_locator
.
definition_id
,
pre_def_id
)
...
@@ -689,15 +691,13 @@ class TestItemCrud(SplitModuleTest):
...
@@ -689,15 +691,13 @@ class TestItemCrud(SplitModuleTest):
new_payload
=
"<problem>empty</problem>"
new_payload
=
"<problem>empty</problem>"
modulestore
()
.
create_item
(
modulestore
()
.
create_item
(
locator
,
category
,
'test_update_manifold'
,
locator
,
category
,
'test_update_manifold'
,
metadata
=
{
'display_name'
:
'problem 1'
},
fields
=
{
'display_name'
:
'problem 1'
,
'data'
:
new_payload
},
new_def_data
=
new_payload
)
)
another_payload
=
"<problem>not empty</problem>"
another_payload
=
"<problem>not empty</problem>"
modulestore
()
.
create_item
(
modulestore
()
.
create_item
(
locator
,
category
,
'test_update_manifold'
,
locator
,
category
,
'test_update_manifold'
,
metadata
=
{
'display_name'
:
'problem 2'
},
fields
=
{
'display_name'
:
'problem 2'
,
'data'
:
another_payload
},
definition_locator
=
DescriptionLocator
(
"problem12345_3_1"
),
definition_locator
=
DescriptionLocator
(
"problem12345_3_1"
),
new_def_data
=
another_payload
)
)
# pylint: disable=W0212
# pylint: disable=W0212
modulestore
()
.
_clear_cache
()
modulestore
()
.
_clear_cache
()
...
@@ -712,6 +712,7 @@ class TestItemCrud(SplitModuleTest):
...
@@ -712,6 +712,7 @@ class TestItemCrud(SplitModuleTest):
block
.
children
=
block
.
children
[
1
:]
+
[
block
.
children
[
0
]]
block
.
children
=
block
.
children
[
1
:]
+
[
block
.
children
[
0
]]
block
.
advertised_start
=
"Soon"
block
.
advertised_start
=
"Soon"
block
.
save
()
# decache model changes
updated_block
=
modulestore
()
.
update_item
(
block
,
"test_update_manifold"
)
updated_block
=
modulestore
()
.
update_item
(
block
,
"test_update_manifold"
)
self
.
assertNotEqual
(
updated_block
.
definition_locator
.
definition_id
,
pre_def_id
)
self
.
assertNotEqual
(
updated_block
.
definition_locator
.
definition_id
,
pre_def_id
)
self
.
assertNotEqual
(
updated_block
.
location
.
version_guid
,
pre_version_guid
)
self
.
assertNotEqual
(
updated_block
.
location
.
version_guid
,
pre_version_guid
)
...
@@ -733,7 +734,7 @@ class TestItemCrud(SplitModuleTest):
...
@@ -733,7 +734,7 @@ class TestItemCrud(SplitModuleTest):
# delete a leaf
# delete a leaf
problems
=
modulestore
()
.
get_items
(
reusable_location
,
{
'category'
:
'problem'
})
problems
=
modulestore
()
.
get_items
(
reusable_location
,
{
'category'
:
'problem'
})
locn_to_del
=
problems
[
0
]
.
location
locn_to_del
=
problems
[
0
]
.
location
new_course_loc
=
modulestore
()
.
delete_item
(
locn_to_del
,
'deleting_user'
)
new_course_loc
=
modulestore
()
.
delete_item
(
locn_to_del
,
'deleting_user'
,
delete_children
=
True
)
deleted
=
BlockUsageLocator
(
course_id
=
reusable_location
.
course_id
,
deleted
=
BlockUsageLocator
(
course_id
=
reusable_location
.
course_id
,
branch
=
reusable_location
.
branch
,
branch
=
reusable_location
.
branch
,
usage_id
=
locn_to_del
.
usage_id
)
usage_id
=
locn_to_del
.
usage_id
)
...
@@ -748,7 +749,7 @@ class TestItemCrud(SplitModuleTest):
...
@@ -748,7 +749,7 @@ class TestItemCrud(SplitModuleTest):
# delete a subtree
# delete a subtree
nodes
=
modulestore
()
.
get_items
(
reusable_location
,
{
'category'
:
'chapter'
})
nodes
=
modulestore
()
.
get_items
(
reusable_location
,
{
'category'
:
'chapter'
})
new_course_loc
=
modulestore
()
.
delete_item
(
nodes
[
0
]
.
location
,
'deleting_user'
)
new_course_loc
=
modulestore
()
.
delete_item
(
nodes
[
0
]
.
location
,
'deleting_user'
,
delete_children
=
True
)
# check subtree
# check subtree
def
check_subtree
(
node
):
def
check_subtree
(
node
):
...
@@ -855,7 +856,7 @@ class TestCourseCreation(SplitModuleTest):
...
@@ -855,7 +856,7 @@ class TestCourseCreation(SplitModuleTest):
# using new_draft.location will insert the chapter under the course root
# using new_draft.location will insert the chapter under the course root
new_item
=
modulestore
()
.
create_item
(
new_item
=
modulestore
()
.
create_item
(
new_draft
.
location
,
'chapter'
,
'leech_master'
,
new_draft
.
location
,
'chapter'
,
'leech_master'
,
metadata
=
{
'display_name'
:
'new chapter'
}
fields
=
{
'display_name'
:
'new chapter'
}
)
)
new_draft_locator
.
version_guid
=
None
new_draft_locator
.
version_guid
=
None
new_index
=
modulestore
()
.
get_course_index_info
(
new_draft_locator
)
new_index
=
modulestore
()
.
get_course_index_info
(
new_draft_locator
)
...
@@ -887,20 +888,18 @@ class TestCourseCreation(SplitModuleTest):
...
@@ -887,20 +888,18 @@ class TestCourseCreation(SplitModuleTest):
original_locator
=
CourseLocator
(
course_id
=
"contender"
,
branch
=
'draft'
)
original_locator
=
CourseLocator
(
course_id
=
"contender"
,
branch
=
'draft'
)
original
=
modulestore
()
.
get_course
(
original_locator
)
original
=
modulestore
()
.
get_course
(
original_locator
)
original_index
=
modulestore
()
.
get_course_index_info
(
original_locator
)
original_index
=
modulestore
()
.
get_course_index_info
(
original_locator
)
data_payload
=
{}
fields
=
{}
metadata_payload
=
{}
for
field
in
original
.
fields
:
for
field
in
original
.
fields
:
if
field
.
scope
==
Scope
.
content
and
field
.
name
!=
'location'
:
if
field
.
scope
==
Scope
.
content
and
field
.
name
!=
'location'
:
data_payload
[
field
.
name
]
=
getattr
(
original
,
field
.
name
)
fields
[
field
.
name
]
=
getattr
(
original
,
field
.
name
)
elif
field
.
scope
==
Scope
.
settings
:
elif
field
.
scope
==
Scope
.
settings
:
metadata_payload
[
field
.
name
]
=
getattr
(
original
,
field
.
name
)
fields
[
field
.
name
]
=
getattr
(
original
,
field
.
name
)
data_payload
[
'grading_policy'
][
'GRADE_CUTOFFS'
]
=
{
'A'
:
.
9
,
'B'
:
.
8
,
'C'
:
.
65
}
fields
[
'grading_policy'
][
'GRADE_CUTOFFS'
]
=
{
'A'
:
.
9
,
'B'
:
.
8
,
'C'
:
.
65
}
metadata_payload
[
'display_name'
]
=
'Derivative'
fields
[
'display_name'
]
=
'Derivative'
new_draft
=
modulestore
()
.
create_course
(
new_draft
=
modulestore
()
.
create_course
(
'leech'
,
'derivative'
,
'leech_master'
,
id_root
=
'counter'
,
'leech'
,
'derivative'
,
'leech_master'
,
id_root
=
'counter'
,
versions_dict
=
{
'draft'
:
original_index
[
'versions'
][
'draft'
]},
versions_dict
=
{
'draft'
:
original_index
[
'versions'
][
'draft'
]},
course_data
=
data_payload
,
fields
=
fields
metadata
=
metadata_payload
)
)
new_draft_locator
=
new_draft
.
location
new_draft_locator
=
new_draft
.
location
self
.
assertRegexpMatches
(
new_draft_locator
.
course_id
,
r'counter.*'
)
self
.
assertRegexpMatches
(
new_draft_locator
.
course_id
,
r'counter.*'
)
...
@@ -913,10 +912,10 @@ class TestCourseCreation(SplitModuleTest):
...
@@ -913,10 +912,10 @@ class TestCourseCreation(SplitModuleTest):
self
.
assertGreaterEqual
(
new_index
[
"edited_on"
],
pre_time
)
self
.
assertGreaterEqual
(
new_index
[
"edited_on"
],
pre_time
)
self
.
assertLessEqual
(
new_index
[
"edited_on"
],
datetime
.
datetime
.
now
(
UTC
))
self
.
assertLessEqual
(
new_index
[
"edited_on"
],
datetime
.
datetime
.
now
(
UTC
))
self
.
assertEqual
(
new_index
[
'edited_by'
],
'leech_master'
)
self
.
assertEqual
(
new_index
[
'edited_by'
],
'leech_master'
)
self
.
assertEqual
(
new_draft
.
display_name
,
metadata_payload
[
'display_name'
])
self
.
assertEqual
(
new_draft
.
display_name
,
fields
[
'display_name'
])
self
.
assertDictEqual
(
self
.
assertDictEqual
(
new_draft
.
grading_policy
[
'GRADE_CUTOFFS'
],
new_draft
.
grading_policy
[
'GRADE_CUTOFFS'
],
data_payload
[
'grading_policy'
][
'GRADE_CUTOFFS'
]
fields
[
'grading_policy'
][
'GRADE_CUTOFFS'
]
)
)
def
test_update_course_index
(
self
):
def
test_update_course_index
(
self
):
...
...
common/lib/xmodule/xmodule/x_module.py
View file @
7f126f13
...
@@ -7,7 +7,7 @@ from lxml import etree
...
@@ -7,7 +7,7 @@ from lxml import etree
from
collections
import
namedtuple
from
collections
import
namedtuple
from
pkg_resources
import
resource_listdir
,
resource_string
,
resource_isdir
from
pkg_resources
import
resource_listdir
,
resource_string
,
resource_isdir
from
xmodule.modulestore
import
inheritance
,
Location
from
xmodule.modulestore
import
Location
from
xmodule.modulestore.exceptions
import
ItemNotFoundError
,
InsufficientSpecificationError
,
InvalidLocationError
from
xmodule.modulestore.exceptions
import
ItemNotFoundError
,
InsufficientSpecificationError
,
InvalidLocationError
from
xblock.core
import
XBlock
,
Scope
,
String
,
Integer
,
Float
,
List
,
ModelType
from
xblock.core
import
XBlock
,
Scope
,
String
,
Integer
,
Float
,
List
,
ModelType
...
@@ -557,75 +557,6 @@ class XModuleDescriptor(XModuleFields, HTMLSnippet, ResourceTemplates, XBlock):
...
@@ -557,75 +557,6 @@ class XModuleDescriptor(XModuleFields, HTMLSnippet, ResourceTemplates, XBlock):
"""
"""
return
False
return
False
# ================================= JSON PARSING ===========================
@staticmethod
def
load_from_json
(
json_data
,
system
,
default_class
=
None
,
parent_xblock
=
None
):
"""
This method instantiates the correct subclass of XModuleDescriptor based
on the contents of json_data. It does not persist it and can create one which
has no usage id.
parent_xblock is used to compute inherited metadata as well as to append the new xblock.
json_data:
- 'location' : must have this field
- 'category': the xmodule category (required or location must be a Location)
- 'metadata': a dict of locally set metadata (not inherited)
- 'children': a list of children's usage_ids w/in this course
- 'definition':
- '_id' (optional): the usage_id of this. Will generate one if not given one.
"""
class_
=
XModuleDescriptor
.
load_class
(
json_data
.
get
(
'category'
,
json_data
.
get
(
'location'
,
{})
.
get
(
'category'
)),
default_class
)
return
class_
.
from_json
(
json_data
,
system
,
parent_xblock
)
@classmethod
def
from_json
(
cls
,
json_data
,
system
,
parent_xblock
=
None
):
"""
Creates an instance of this descriptor from the supplied json_data.
This may be overridden by subclasses
json_data: A json object with the keys 'definition' and 'metadata',
definition: A json object with the keys 'data' and 'children'
data: A json value
children: A list of edX Location urls
metadata: A json object with any keys
This json_data is transformed to model_data using the following rules:
1) The model data contains all of the fields from metadata
2) The model data contains the 'children' array
3) If 'definition.data' is a json object, model data contains all of its fields
Otherwise, it contains the single field 'data'
4) Any value later in this list overrides a value earlier in this list
json_data:
- 'category': the xmodule category (required)
- 'metadata': a dict of locally set metadata (not inherited)
- 'children': a list of children's usage_ids w/in this course
- 'definition':
- '_id' (optional): the usage_id of this. Will generate one if not given one.
"""
usage_id
=
json_data
.
get
(
'_id'
,
None
)
if
not
'_inherited_metadata'
in
json_data
and
parent_xblock
is
not
None
:
json_data
[
'_inherited_metadata'
]
=
parent_xblock
.
xblock_kvs
.
get_inherited_metadata
()
.
copy
()
json_metadata
=
json_data
.
get
(
'metadata'
,
{})
for
field
in
inheritance
.
INHERITABLE_METADATA
:
if
field
in
json_metadata
:
json_data
[
'_inherited_metadata'
][
field
]
=
json_metadata
[
field
]
new_block
=
system
.
xblock_from_json
(
cls
,
usage_id
,
json_data
)
if
parent_xblock
is
not
None
:
children
=
parent_xblock
.
children
children
.
append
(
new_block
)
# trigger setter method by using top level field access
parent_xblock
.
children
=
children
# decache pending children field settings (Note, truly persisting at this point would break b/c
# persistence assumes children is a list of ids not actual xblocks)
parent_xblock
.
save
()
return
new_block
@classmethod
@classmethod
def
_translate
(
cls
,
key
):
def
_translate
(
cls
,
key
):
'VS[compat]'
'VS[compat]'
...
@@ -726,6 +657,17 @@ class XModuleDescriptor(XModuleFields, HTMLSnippet, ResourceTemplates, XBlock):
...
@@ -726,6 +657,17 @@ class XModuleDescriptor(XModuleFields, HTMLSnippet, ResourceTemplates, XBlock):
)
)
)
)
def
iterfields
(
self
):
"""
A generator for iterate over the fields of this xblock (including the ones in namespaces).
Example usage: [field.name for field in module.iterfields()]
"""
for
field
in
self
.
fields
:
yield
field
for
namespace
in
self
.
namespaces
:
for
field
in
getattr
(
self
,
namespace
)
.
fields
:
yield
field
@property
@property
def
non_editable_metadata_fields
(
self
):
def
non_editable_metadata_fields
(
self
):
"""
"""
...
@@ -736,6 +678,27 @@ class XModuleDescriptor(XModuleFields, HTMLSnippet, ResourceTemplates, XBlock):
...
@@ -736,6 +678,27 @@ class XModuleDescriptor(XModuleFields, HTMLSnippet, ResourceTemplates, XBlock):
# We are not allowing editing of xblock tag and name fields at this time (for any component).
# We are not allowing editing of xblock tag and name fields at this time (for any component).
return
[
XBlock
.
tags
,
XBlock
.
name
]
return
[
XBlock
.
tags
,
XBlock
.
name
]
def
get_explicitly_set_fields_by_scope
(
self
,
scope
=
Scope
.
content
):
"""
Get a dictionary of the fields for the given scope which are set explicitly on this xblock. (Including
any set to None.)
"""
if
scope
==
Scope
.
settings
and
hasattr
(
self
,
'_inherited_metadata'
):
inherited_metadata
=
getattr
(
self
,
'_inherited_metadata'
)
result
=
{}
for
field
in
self
.
iterfields
():
if
(
field
.
scope
==
scope
and
field
.
name
in
self
.
_model_data
and
field
.
name
not
in
inherited_metadata
):
result
[
field
.
name
]
=
self
.
_model_data
[
field
.
name
]
return
result
else
:
result
=
{}
for
field
in
self
.
iterfields
():
if
(
field
.
scope
==
scope
and
field
.
name
in
self
.
_model_data
):
result
[
field
.
name
]
=
self
.
_model_data
[
field
.
name
]
return
result
@property
@property
def
editable_metadata_fields
(
self
):
def
editable_metadata_fields
(
self
):
"""
"""
...
...
common/test/data/splitmongo_json/definitions.json
View file @
7f126f13
...
@@ -2,7 +2,7 @@
...
@@ -2,7 +2,7 @@
{
{
"_id"
:
"head12345_12"
,
"_id"
:
"head12345_12"
,
"category"
:
"course"
,
"category"
:
"course"
,
"
data
"
:{
"
fields
"
:{
"textbooks"
:[
"textbooks"
:[
],
],
...
@@ -43,15 +43,17 @@
...
@@ -43,15 +43,17 @@
},
},
"wiki_slug"
:
null
"wiki_slug"
:
null
},
},
"edited_by"
:
"testassist@edx.org"
,
"edit_info"
:
{
"edited_on"
:{
"$date"
:
1364481713238
},
"edited_by"
:
"testassist@edx.org"
,
"previous_version"
:
"head12345_11"
,
"edited_on"
:{
"$date"
:
1364481713238
},
"original_version"
:
"head12345_10"
"previous_version"
:
"head12345_11"
,
"original_version"
:
"head12345_10"
}
},
},
{
{
"_id"
:
"head12345_11"
,
"_id"
:
"head12345_11"
,
"category"
:
"course"
,
"category"
:
"course"
,
"
data
"
:{
"
fields
"
:{
"textbooks"
:[
"textbooks"
:[
],
],
...
@@ -92,15 +94,17 @@
...
@@ -92,15 +94,17 @@
},
},
"wiki_slug"
:
null
"wiki_slug"
:
null
},
},
"edited_by"
:
"testassist@edx.org"
,
"edit_info"
:
{
"edited_on"
:{
"$date"
:
1364481713238
},
"edited_by"
:
"testassist@edx.org"
,
"previous_version"
:
"head12345_10"
,
"edited_on"
:{
"$date"
:
1364481713238
},
"original_version"
:
"head12345_10"
"previous_version"
:
"head12345_10"
,
"original_version"
:
"head12345_10"
}
},
},
{
{
"_id"
:
"head12345_10"
,
"_id"
:
"head12345_10"
,
"category"
:
"course"
,
"category"
:
"course"
,
"
data
"
:{
"
fields
"
:{
"textbooks"
:[
"textbooks"
:[
],
],
...
@@ -141,15 +145,17 @@
...
@@ -141,15 +145,17 @@
},
},
"wiki_slug"
:
null
"wiki_slug"
:
null
},
},
"edited_by"
:
"test@edx.org"
,
"edit_info"
:
{
"edited_on"
:{
"$date"
:
1364473713238
},
"edited_by"
:
"test@edx.org"
,
"previous_version"
:
null
,
"edited_on"
:{
"$date"
:
1364473713238
},
"original_version"
:
"head12345_10"
"previous_version"
:
null
,
"original_version"
:
"head12345_10"
}
},
},
{
{
"_id"
:
"head23456_1"
,
"_id"
:
"head23456_1"
,
"category"
:
"course"
,
"category"
:
"course"
,
"
data
"
:{
"
fields
"
:{
"textbooks"
:[
"textbooks"
:[
],
],
...
@@ -190,15 +196,17 @@
...
@@ -190,15 +196,17 @@
},
},
"wiki_slug"
:
null
"wiki_slug"
:
null
},
},
"edited_by"
:
"test@edx.org"
,
"edit_info"
:
{
"edited_on"
:{
"$date"
:
1364481313238
},
"edited_by"
:
"test@edx.org"
,
"previous_version"
:
"head23456_0"
,
"edited_on"
:{
"$date"
:
1364481313238
},
"original_version"
:
"head23456_0"
"previous_version"
:
"head23456_0"
,
"original_version"
:
"head23456_0"
}
},
},
{
{
"_id"
:
"head23456_0"
,
"_id"
:
"head23456_0"
,
"category"
:
"course"
,
"category"
:
"course"
,
"
data
"
:{
"
fields
"
:{
"textbooks"
:[
"textbooks"
:[
],
],
...
@@ -239,15 +247,17 @@
...
@@ -239,15 +247,17 @@
},
},
"wiki_slug"
:
null
"wiki_slug"
:
null
},
},
"edited_by"
:
"test@edx.org"
,
"edit_info"
:
{
"edited_on"
:{
"$date"
:
1364481313238
},
"edited_by"
:
"test@edx.org"
,
"previous_version"
:
null
,
"edited_on"
:{
"$date"
:
1364481313238
},
"original_version"
:
"head23456_0"
"previous_version"
:
null
,
"original_version"
:
"head23456_0"
}
},
},
{
{
"_id"
:
"head345679_1"
,
"_id"
:
"head345679_1"
,
"category"
:
"course"
,
"category"
:
"course"
,
"
data
"
:{
"
fields
"
:{
"textbooks"
:[
"textbooks"
:[
],
],
...
@@ -281,54 +291,66 @@
...
@@ -281,54 +291,66 @@
},
},
"wiki_slug"
:
null
"wiki_slug"
:
null
},
},
"edited_by"
:
"test@edx.org"
,
"edit_info"
:
{
"edited_on"
:{
"$date"
:
1364481313238
},
"edited_by"
:
"test@edx.org"
,
"previous_version"
:
null
,
"edited_on"
:{
"$date"
:
1364481313238
},
"original_version"
:
"head23456_0"
"previous_version"
:
null
,
"original_version"
:
"head23456_0"
}
},
},
{
{
"_id"
:
"chapter12345_1"
,
"_id"
:
"chapter12345_1"
,
"category"
:
"chapter"
,
"category"
:
"chapter"
,
"data"
:
null
,
"fields"
:{},
"edited_by"
:
"testassist@edx.org"
,
"edit_info"
:
{
"edited_on"
:{
"$date"
:
1364483713238
},
"edited_by"
:
"testassist@edx.org"
,
"previous_version"
:
null
,
"edited_on"
:{
"$date"
:
1364483713238
},
"original_version"
:
"chapter12345_1"
"previous_version"
:
null
,
"original_version"
:
"chapter12345_1"
}
},
},
{
{
"_id"
:
"chapter12345_2"
,
"_id"
:
"chapter12345_2"
,
"category"
:
"chapter"
,
"category"
:
"chapter"
,
"data"
:
null
,
"fields"
:{},
"edited_by"
:
"testassist@edx.org"
,
"edit_info"
:
{
"edited_on"
:{
"$date"
:
1364483713238
},
"edited_by"
:
"testassist@edx.org"
,
"previous_version"
:
null
,
"edited_on"
:{
"$date"
:
1364483713238
},
"original_version"
:
"chapter12345_2"
"previous_version"
:
null
,
"original_version"
:
"chapter12345_2"
}
},
},
{
{
"_id"
:
"chapter12345_3"
,
"_id"
:
"chapter12345_3"
,
"category"
:
"chapter"
,
"category"
:
"chapter"
,
"data"
:
null
,
"fields"
:{},
"edited_by"
:
"testassist@edx.org"
,
"edit_info"
:
{
"edited_on"
:{
"$date"
:
1364483713238
},
"edited_by"
:
"testassist@edx.org"
,
"previous_version"
:
null
,
"edited_on"
:{
"$date"
:
1364483713238
},
"original_version"
:
"chapter12345_3"
"previous_version"
:
null
,
"original_version"
:
"chapter12345_3"
}
},
},
{
{
"_id"
:
"problem12345_3_1"
,
"_id"
:
"problem12345_3_1"
,
"category"
:
"problem"
,
"category"
:
"problem"
,
"data"
:
""
,
"fields"
:
{
"data"
:
""
},
"edited_by"
:
"testassist@edx.org"
,
"edit_info"
:
{
"edited_on"
:{
"$date"
:
1364483713238
},
"edited_by"
:
"testassist@edx.org"
,
"previous_version"
:
null
,
"edited_on"
:{
"$date"
:
1364483713238
},
"original_version"
:
"problem12345_3_1"
"previous_version"
:
null
,
"original_version"
:
"problem12345_3_1"
}
},
},
{
{
"_id"
:
"problem12345_3_2"
,
"_id"
:
"problem12345_3_2"
,
"category"
:
"problem"
,
"category"
:
"problem"
,
"data"
:
""
,
"fields"
:
{
"data"
:
""
},
"edited_by"
:
"testassist@edx.org"
,
"edit_info"
:
{
"edited_on"
:{
"$date"
:
1364483713238
},
"edited_by"
:
"testassist@edx.org"
,
"previous_version"
:
null
,
"edited_on"
:{
"$date"
:
1364483713238
},
"original_version"
:
"problem12345_3_2"
"previous_version"
:
null
,
"original_version"
:
"problem12345_3_2"
}
}
}
]
]
\ No newline at end of file
common/test/data/splitmongo_json/structures.json
View file @
7f126f13
...
@@ -10,14 +10,14 @@
...
@@ -10,14 +10,14 @@
},
},
"blocks"
:{
"blocks"
:{
"head12345"
:{
"head12345"
:{
"children"
:[
"chapter1"
,
"chapter2"
,
"chapter3"
],
"category"
:
"course"
,
"category"
:
"course"
,
"definition"
:
"head12345_12"
,
"definition"
:
"head12345_12"
,
"metadata"
:{
"fields"
:{
"children"
:[
"chapter1"
,
"chapter2"
,
"chapter3"
],
"end"
:
"2013-06-13T04:30"
,
"end"
:
"2013-06-13T04:30"
,
"tabs"
:[
"tabs"
:[
{
{
...
@@ -54,93 +54,105 @@
...
@@ -54,93 +54,105 @@
"advertised_start"
:
"Fall 2013"
,
"advertised_start"
:
"Fall 2013"
,
"display_name"
:
"The Ancient Greek Hero"
"display_name"
:
"The Ancient Greek Hero"
},
},
"update_version"
:{
"$oid"
:
"1d00000000000000dddd0000"
},
"edit_info"
:
{
"previous_version"
:{
"$oid"
:
"1d00000000000000dddd1111"
},
"update_version"
:{
"$oid"
:
"1d00000000000000dddd0000"
},
"edited_by"
:
"testassist@edx.org"
,
"previous_version"
:{
"$oid"
:
"1d00000000000000dddd1111"
},
"edited_on"
:{
"edited_by"
:
"testassist@edx.org"
,
"$date"
:
1364483713238
"edited_on"
:{
"$date"
:
1364483713238
}
}
}
},
},
"chapter1"
:{
"chapter1"
:{
"children"
:[
],
"category"
:
"chapter"
,
"category"
:
"chapter"
,
"definition"
:
"chapter12345_1"
,
"definition"
:
"chapter12345_1"
,
"metadata"
:{
"fields"
:{
"children"
:[
],
"display_name"
:
"Hercules"
"display_name"
:
"Hercules"
},
},
"update_version"
:{
"$oid"
:
"1d00000000000000dddd0000"
},
"edit_info"
:
{
"previous_version"
:
null
,
"update_version"
:{
"$oid"
:
"1d00000000000000dddd0000"
},
"edited_by"
:
"testassist@edx.org"
,
"previous_version"
:
null
,
"edited_on"
:{
"edited_by"
:
"testassist@edx.org"
,
"$date"
:
1364483713238
"edited_on"
:{
"$date"
:
1364483713238
}
}
}
},
},
"chapter2"
:{
"chapter2"
:{
"children"
:[
],
"category"
:
"chapter"
,
"category"
:
"chapter"
,
"definition"
:
"chapter12345_2"
,
"definition"
:
"chapter12345_2"
,
"metadata"
:{
"fields"
:{
"children"
:[
],
"display_name"
:
"Hera heckles Hercules"
"display_name"
:
"Hera heckles Hercules"
},
},
"update_version"
:{
"$oid"
:
"1d00000000000000dddd0000"
},
"edit_info"
:
{
"previous_version"
:
null
,
"update_version"
:{
"$oid"
:
"1d00000000000000dddd0000"
},
"edited_by"
:
"testassist@edx.org"
,
"previous_version"
:
null
,
"edited_on"
:{
"edited_by"
:
"testassist@edx.org"
,
"$date"
:
1364483713238
"edited_on"
:{
"$date"
:
1364483713238
}
}
}
},
},
"chapter3"
:{
"chapter3"
:{
"children"
:[
"problem1"
,
"problem3_2"
],
"category"
:
"chapter"
,
"category"
:
"chapter"
,
"definition"
:
"chapter12345_3"
,
"definition"
:
"chapter12345_3"
,
"metadata"
:{
"fields"
:{
"children"
:[
"problem1"
,
"problem3_2"
],
"display_name"
:
"Hera cuckolds Zeus"
"display_name"
:
"Hera cuckolds Zeus"
},
},
"update_version"
:{
"$oid"
:
"1d00000000000000dddd0000"
},
"edit_info"
:
{
"previous_version"
:
null
,
"update_version"
:{
"$oid"
:
"1d00000000000000dddd0000"
},
"edited_by"
:
"testassist@edx.org"
,
"previous_version"
:
null
,
"edited_on"
:{
"edited_by"
:
"testassist@edx.org"
,
"$date"
:
1364483713238
"edited_on"
:{
"$date"
:
1364483713238
}
}
}
},
},
"problem1"
:{
"problem1"
:{
"children"
:[
],
"category"
:
"problem"
,
"category"
:
"problem"
,
"definition"
:
"problem12345_3_1"
,
"definition"
:
"problem12345_3_1"
,
"metadata"
:{
"fields"
:{
"children"
:[
],
"display_name"
:
"Problem 3.1"
,
"display_name"
:
"Problem 3.1"
,
"graceperiod"
:
"4 hours 0 minutes 0 seconds"
"graceperiod"
:
"4 hours 0 minutes 0 seconds"
},
},
"update_version"
:{
"$oid"
:
"1d00000000000000dddd0000"
},
"edit_info"
:
{
"previous_version"
:
null
,
"update_version"
:{
"$oid"
:
"1d00000000000000dddd0000"
},
"edited_by"
:
"testassist@edx.org"
,
"previous_version"
:
null
,
"edited_on"
:{
"edited_by"
:
"testassist@edx.org"
,
"$date"
:
1364483713238
"edited_on"
:{
"$date"
:
1364483713238
}
}
}
},
},
"problem3_2"
:{
"problem3_2"
:{
"children"
:[
],
"category"
:
"problem"
,
"category"
:
"problem"
,
"definition"
:
"problem12345_3_2"
,
"definition"
:
"problem12345_3_2"
,
"metadata"
:{
"fields"
:{
"children"
:[
],
"display_name"
:
"Problem 3.2"
"display_name"
:
"Problem 3.2"
},
},
"update_version"
:{
"$oid"
:
"1d00000000000000dddd0000"
},
"edit_info"
:
{
"previous_version"
:
null
,
"update_version"
:{
"$oid"
:
"1d00000000000000dddd0000"
},
"edited_by"
:
"testassist@edx.org"
,
"previous_version"
:
null
,
"edited_on"
:{
"edited_by"
:
"testassist@edx.org"
,
"$date"
:
1364483713238
"edited_on"
:{
"$date"
:
1364483713238
}
}
}
}
}
}
}
...
@@ -156,12 +168,12 @@
...
@@ -156,12 +168,12 @@
},
},
"blocks"
:{
"blocks"
:{
"head12345"
:{
"head12345"
:{
"children"
:[
],
"category"
:
"course"
,
"category"
:
"course"
,
"definition"
:
"head12345_11"
,
"definition"
:
"head12345_11"
,
"metadata"
:{
"fields"
:{
"children"
:[
],
"end"
:
"2013-04-13T04:30"
,
"end"
:
"2013-04-13T04:30"
,
"tabs"
:[
"tabs"
:[
{
{
...
@@ -198,11 +210,13 @@
...
@@ -198,11 +210,13 @@
"advertised_start"
:
null
,
"advertised_start"
:
null
,
"display_name"
:
"The Ancient Greek Hero"
"display_name"
:
"The Ancient Greek Hero"
},
},
"update_version"
:{
"$oid"
:
"1d00000000000000dddd1111"
},
"edit_info"
:
{
"previous_version"
:{
"$oid"
:
"1d00000000000000dddd3333"
},
"update_version"
:{
"$oid"
:
"1d00000000000000dddd1111"
},
"edited_by"
:
"testassist@edx.org"
,
"previous_version"
:{
"$oid"
:
"1d00000000000000dddd3333"
},
"edited_on"
:{
"edited_by"
:
"testassist@edx.org"
,
"$date"
:
1364481713238
"edited_on"
:{
"$date"
:
1364481713238
}
}
}
}
}
}
}
...
@@ -218,12 +232,12 @@
...
@@ -218,12 +232,12 @@
},
},
"blocks"
:{
"blocks"
:{
"head12345"
:{
"head12345"
:{
"children"
:[
],
"category"
:
"course"
,
"category"
:
"course"
,
"definition"
:
"head12345_10"
,
"definition"
:
"head12345_10"
,
"metadata"
:{
"fields"
:{
"children"
:[
],
"end"
:
null
,
"end"
:
null
,
"tabs"
:[
"tabs"
:[
{
{
...
@@ -250,11 +264,13 @@
...
@@ -250,11 +264,13 @@
"advertised_start"
:
null
,
"advertised_start"
:
null
,
"display_name"
:
"The Ancient Greek Hero"
"display_name"
:
"The Ancient Greek Hero"
},
},
"update_version"
:{
"$oid"
:
"1d00000000000000dddd3333"
},
"edit_info"
:
{
"previous_version"
:
null
,
"update_version"
:{
"$oid"
:
"1d00000000000000dddd3333"
},
"edited_by"
:
"test@edx.org"
,
"previous_version"
:
null
,
"edited_on"
:{
"edited_by"
:
"test@edx.org"
,
"$date"
:
1364473713238
"edited_on"
:{
"$date"
:
1364473713238
}
}
}
}
}
}
}
...
@@ -270,12 +286,12 @@
...
@@ -270,12 +286,12 @@
},
},
"blocks"
:{
"blocks"
:{
"head23456"
:{
"head23456"
:{
"children"
:[
],
"category"
:
"course"
,
"category"
:
"course"
,
"definition"
:
"head23456_1"
,
"definition"
:
"head23456_1"
,
"metadata"
:{
"fields"
:{
"children"
:[
],
"end"
:
null
,
"end"
:
null
,
"tabs"
:[
"tabs"
:[
{
{
...
@@ -302,11 +318,13 @@
...
@@ -302,11 +318,13 @@
"advertised_start"
:
null
,
"advertised_start"
:
null
,
"display_name"
:
"The most wonderful course"
"display_name"
:
"The most wonderful course"
},
},
"update_version"
:{
"$oid"
:
"1d00000000000000dddd2222"
},
"edit_info"
:
{
"previous_version"
:{
"$oid"
:
"1d00000000000000dddd4444"
},
"update_version"
:{
"$oid"
:
"1d00000000000000dddd2222"
},
"edited_by"
:
"test@edx.org"
,
"previous_version"
:{
"$oid"
:
"1d00000000000000dddd4444"
},
"edited_on"
:{
"edited_by"
:
"test@edx.org"
,
"$date"
:
1364481313238
"edited_on"
:{
"$date"
:
1364481313238
}
}
}
}
}
...
@@ -323,12 +341,12 @@
...
@@ -323,12 +341,12 @@
},
},
"blocks"
:{
"blocks"
:{
"head23456"
:{
"head23456"
:{
"children"
:[
],
"category"
:
"course"
,
"category"
:
"course"
,
"definition"
:
"head23456_0"
,
"definition"
:
"head23456_0"
,
"metadata"
:{
"fields"
:{
"children"
:[
],
"end"
:
null
,
"end"
:
null
,
"tabs"
:[
"tabs"
:[
{
{
...
@@ -355,11 +373,13 @@
...
@@ -355,11 +373,13 @@
"advertised_start"
:
null
,
"advertised_start"
:
null
,
"display_name"
:
"A wonderful course"
"display_name"
:
"A wonderful course"
},
},
"update_version"
:{
"$oid"
:
"1d00000000000000dddd4444"
},
"edit_info"
:
{
"previous_version"
:
null
,
"update_version"
:{
"$oid"
:
"1d00000000000000dddd4444"
},
"edited_by"
:
"test@edx.org"
,
"previous_version"
:
null
,
"edited_on"
:{
"edited_by"
:
"test@edx.org"
,
"$date"
:
1364480313238
"edited_on"
:{
"$date"
:
1364480313238
}
}
}
}
}
}
}
...
@@ -375,12 +395,12 @@
...
@@ -375,12 +395,12 @@
},
},
"blocks"
:{
"blocks"
:{
"head23456"
:{
"head23456"
:{
"children"
:[
],
"category"
:
"course"
,
"category"
:
"course"
,
"definition"
:
"head23456_1"
,
"definition"
:
"head23456_1"
,
"metadata"
:{
"fields"
:{
"children"
:[
],
"end"
:
null
,
"end"
:
null
,
"tabs"
:[
"tabs"
:[
{
{
...
@@ -407,11 +427,13 @@
...
@@ -407,11 +427,13 @@
"advertised_start"
:
null
,
"advertised_start"
:
null
,
"display_name"
:
"The most wonderful course"
"display_name"
:
"The most wonderful course"
},
},
"update_version"
:{
"$oid"
:
"1d00000000000000eeee0000"
},
"edit_info"
:
{
"previous_version"
:
null
,
"update_version"
:{
"$oid"
:
"1d00000000000000eeee0000"
},
"edited_by"
:
"test@edx.org"
,
"previous_version"
:
null
,
"edited_on"
:{
"edited_by"
:
"test@edx.org"
,
"$date"
:
1364481333238
"edited_on"
:{
"$date"
:
1364481333238
}
}
}
}
}
}
}
...
@@ -427,12 +449,12 @@
...
@@ -427,12 +449,12 @@
},
},
"blocks"
:{
"blocks"
:{
"head345679"
:{
"head345679"
:{
"children"
:[
],
"category"
:
"course"
,
"category"
:
"course"
,
"definition"
:
"head345679_1"
,
"definition"
:
"head345679_1"
,
"metadata"
:{
"fields"
:{
"children"
:[
],
"end"
:
null
,
"end"
:
null
,
"tabs"
:[
"tabs"
:[
{
{
...
@@ -459,11 +481,13 @@
...
@@ -459,11 +481,13 @@
"advertised_start"
:
null
,
"advertised_start"
:
null
,
"display_name"
:
"Yet another contender"
"display_name"
:
"Yet another contender"
},
},
"update_version"
:{
"$oid"
:
"1d00000000000000dddd5555"
},
"edit_info"
:
{
"previous_version"
:
null
,
"update_version"
:{
"$oid"
:
"1d00000000000000dddd5555"
},
"edited_by"
:
"test@guestx.edu"
,
"previous_version"
:
null
,
"edited_on"
:{
"edited_by"
:
"test@guestx.edu"
,
"$date"
:
1364491313238
"edited_on"
:{
"$date"
:
1364491313238
}
}
}
}
}
}
}
...
...
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