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
ba7959fc
Commit
ba7959fc
authored
Feb 21, 2013
by
Don Mitchell
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1511 from MITx/feature/cdodge/metadata-inheritence-crawling
Feature/cdodge/metadata inheritence crawling
parents
8b1579e4
67f97c2c
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
148 additions
and
14 deletions
+148
-14
cms/djangoapps/contentstore/tests/test_contentstore.py
+47
-0
common/lib/xmodule/xmodule/mako_module.py
+2
-1
common/lib/xmodule/xmodule/modulestore/mongo.py
+97
-10
common/lib/xmodule/xmodule/x_module.py
+2
-3
No files found.
cms/djangoapps/contentstore/tests/test_contentstore.py
View file @
ba7959fc
...
...
@@ -264,6 +264,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
self
.
assertContains
(
resp
,
'/c4x/edX/full/asset/handouts_schematic_tutorial.pdf'
)
class
ContentStoreTest
(
ModuleStoreTestCase
):
"""
Tests for the CMS ContentStore application.
...
...
@@ -422,6 +423,52 @@ class ContentStoreTest(ModuleStoreTestCase):
self
.
assertNotIn
(
'markdown'
,
problem
.
editable_metadata_fields
,
"Markdown slipped into the editable metadata fields"
)
def
test_metadata_inheritance
(
self
):
import_from_xml
(
modulestore
(),
'common/test/data/'
,
[
'full'
])
ms
=
modulestore
(
'direct'
)
course
=
ms
.
get_item
(
Location
([
'i4x'
,
'edX'
,
'full'
,
'course'
,
'6.002_Spring_2012'
,
None
]))
verticals
=
ms
.
get_items
([
'i4x'
,
'edX'
,
'full'
,
'vertical'
,
None
,
None
])
# let's assert on the metadata_inheritance on an existing vertical
for
vertical
in
verticals
:
self
.
assertIn
(
'xqa_key'
,
vertical
.
metadata
)
self
.
assertEqual
(
course
.
metadata
[
'xqa_key'
],
vertical
.
metadata
[
'xqa_key'
])
self
.
assertGreater
(
len
(
verticals
),
0
)
new_component_location
=
Location
(
'i4x'
,
'edX'
,
'full'
,
'html'
,
'new_component'
)
source_template_location
=
Location
(
'i4x'
,
'edx'
,
'templates'
,
'html'
,
'Empty'
)
# crate a new module and add it as a child to a vertical
ms
.
clone_item
(
source_template_location
,
new_component_location
)
parent
=
verticals
[
0
]
ms
.
update_children
(
parent
.
location
,
parent
.
definition
.
get
(
'children'
,
[])
+
[
new_component_location
.
url
()])
# flush the cache
ms
.
get_cached_metadata_inheritance_tree
(
new_component_location
,
-
1
)
new_module
=
ms
.
get_item
(
new_component_location
)
# check for grace period definition which should be defined at the course level
self
.
assertIn
(
'graceperiod'
,
new_module
.
metadata
)
self
.
assertEqual
(
course
.
metadata
[
'graceperiod'
],
new_module
.
metadata
[
'graceperiod'
])
#
# now let's define an override at the leaf node level
#
new_module
.
metadata
[
'graceperiod'
]
=
'1 day'
ms
.
update_metadata
(
new_module
.
location
,
new_module
.
metadata
)
# flush the cache and refetch
ms
.
get_cached_metadata_inheritance_tree
(
new_component_location
,
-
1
)
new_module
=
ms
.
get_item
(
new_component_location
)
self
.
assertIn
(
'graceperiod'
,
new_module
.
metadata
)
self
.
assertEqual
(
'1 day'
,
new_module
.
metadata
[
'graceperiod'
])
class
TemplateTestCase
(
ModuleStoreTestCase
):
def
test_template_cleanup
(
self
):
...
...
common/lib/xmodule/xmodule/mako_module.py
View file @
ba7959fc
...
...
@@ -44,5 +44,6 @@ class MakoModuleDescriptor(XModuleDescriptor):
# cdodge: encapsulate a means to expose "editable" metadata fields (i.e. not internal system metadata)
@property
def
editable_metadata_fields
(
self
):
subset
=
[
name
for
name
in
self
.
metadata
.
keys
()
if
name
not
in
self
.
system_metadata_fields
]
subset
=
[
name
for
name
in
self
.
metadata
.
keys
()
if
name
not
in
self
.
system_metadata_fields
and
name
not
in
self
.
_inherited_metadata
]
return
subset
common/lib/xmodule/xmodule/modulestore/mongo.py
View file @
ba7959fc
import
pymongo
import
sys
import
logging
import
copy
from
bson.son
import
SON
from
fs.osfs
import
OSFS
from
itertools
import
repeat
from
path
import
path
from
datetime
import
datetime
,
timedelta
from
importlib
import
import_module
from
xmodule.errortracker
import
null_error_tracker
,
exc_info_to_str
...
...
@@ -27,9 +29,11 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
"""
A system that has a cache of module json that it will use to load modules
from, with a backup of calling to the underlying modulestore for more data
TODO (cdodge) when the 'split module store' work has been completed we can remove all
references to metadata_inheritance_tree
"""
def
__init__
(
self
,
modulestore
,
module_data
,
default_class
,
resources_fs
,
error_tracker
,
render_template
):
error_tracker
,
render_template
,
metadata_inheritance_tree
=
None
):
"""
modulestore: the module store that can be used to retrieve additional modules
...
...
@@ -54,6 +58,7 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
# cdodge: other Systems have a course_id attribute defined. To keep things consistent, let's
# define an attribute here as well, even though it's None
self
.
course_id
=
None
self
.
metadata_inheritance_tree
=
metadata_inheritance_tree
def
load_item
(
self
,
location
):
location
=
Location
(
location
)
...
...
@@ -61,11 +66,13 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
if
json_data
is
None
:
return
self
.
modulestore
.
get_item
(
location
)
else
:
# TODO (vshnayder): metadata inheritance is somewhat broken because mongo, doesn't
# always load an entire course. We're punting on this until after launch, and then
# will build a proper course policy framework.
# load the module and apply the inherited metadata
try
:
return
XModuleDescriptor
.
load_from_json
(
json_data
,
self
,
self
.
default_class
)
module
=
XModuleDescriptor
.
load_from_json
(
json_data
,
self
,
self
.
default_class
)
if
self
.
metadata_inheritance_tree
is
not
None
:
metadata_to_inherit
=
self
.
metadata_inheritance_tree
.
get
(
'parent_metadata'
,
{})
.
get
(
location
.
url
(),{})
module
.
inherit_metadata
(
metadata_to_inherit
)
return
module
except
:
return
ErrorDescriptor
.
from_json
(
json_data
,
...
...
@@ -142,6 +149,82 @@ class MongoModuleStore(ModuleStoreBase):
self
.
fs_root
=
path
(
fs_root
)
self
.
error_tracker
=
error_tracker
self
.
render_template
=
render_template
self
.
metadata_inheritance_cache
=
{}
def
get_metadata_inheritance_tree
(
self
,
location
):
'''
TODO (cdodge) This method can be deleted when the 'split module store' work has been completed
'''
# get all collections in the course, this query should not return any leaf nodes
query
=
{
'_id.org'
:
location
.
org
,
'_id.course'
:
location
.
course
,
'_id.revision'
:
None
,
'definition.children'
:{
'$ne'
:
[]}
}
# we just want the Location, children, and metadata
record_filter
=
{
'_id'
:
1
,
'definition.children'
:
1
,
'metadata'
:
1
}
# call out to the DB
resultset
=
self
.
collection
.
find
(
query
,
record_filter
)
results_by_url
=
{}
root
=
None
# now go through the results and order them by the location url
for
result
in
resultset
:
location
=
Location
(
result
[
'_id'
])
results_by_url
[
location
.
url
()]
=
result
if
location
.
category
==
'course'
:
root
=
location
.
url
()
# now traverse the tree and compute down the inherited metadata
metadata_to_inherit
=
{}
def
_compute_inherited_metadata
(
url
):
my_metadata
=
results_by_url
[
url
][
'metadata'
]
for
key
in
my_metadata
.
keys
():
if
key
not
in
XModuleDescriptor
.
inheritable_metadata
:
del
my_metadata
[
key
]
results_by_url
[
url
][
'metadata'
]
=
my_metadata
# go through all the children and recurse, but only if we have
# in the result set. Remember results will not contain leaf nodes
for
child
in
results_by_url
[
url
]
.
get
(
'definition'
,{})
.
get
(
'children'
,[]):
if
child
in
results_by_url
:
new_child_metadata
=
copy
.
deepcopy
(
my_metadata
)
new_child_metadata
.
update
(
results_by_url
[
child
][
'metadata'
])
results_by_url
[
child
][
'metadata'
]
=
new_child_metadata
metadata_to_inherit
[
child
]
=
new_child_metadata
_compute_inherited_metadata
(
child
)
else
:
# this is likely a leaf node, so let's record what metadata we need to inherit
metadata_to_inherit
[
child
]
=
my_metadata
if
root
is
not
None
:
_compute_inherited_metadata
(
root
)
cache
=
{
'parent_metadata'
:
metadata_to_inherit
,
'timestamp'
:
datetime
.
now
()}
return
cache
def
get_cached_metadata_inheritance_tree
(
self
,
location
,
max_age_allowed
):
'''
TODO (cdodge) This method can be deleted when the 'split module store' work has been completed
'''
cache_name
=
'{0}/{1}'
.
format
(
location
.
org
,
location
.
course
)
cache
=
self
.
metadata_inheritance_cache
.
get
(
cache_name
,{
'parent_metadata'
:
{},
'timestamp'
:
datetime
.
now
()
-
timedelta
(
hours
=
1
)})
age
=
(
datetime
.
now
()
-
cache
[
'timestamp'
])
if
age
.
seconds
>=
max_age_allowed
:
logging
.
debug
(
'loading entire inheritance tree for {0}'
.
format
(
cache_name
))
cache
=
self
.
get_metadata_inheritance_tree
(
location
)
self
.
metadata_inheritance_cache
[
cache_name
]
=
cache
return
cache
def
_clean_item_data
(
self
,
item
):
"""
...
...
@@ -196,6 +279,8 @@ class MongoModuleStore(ModuleStoreBase):
resource_fs
=
OSFS
(
root
)
# TODO (cdodge): When the 'split module store' work has been completed, we should remove
# the 'metadata_inheritance_tree' parameter
system
=
CachingDescriptorSystem
(
self
,
data_cache
,
...
...
@@ -203,6 +288,7 @@ class MongoModuleStore(ModuleStoreBase):
resource_fs
,
self
.
error_tracker
,
self
.
render_template
,
metadata_inheritance_tree
=
self
.
get_cached_metadata_inheritance_tree
(
Location
(
item
[
'location'
]),
60
)
)
return
system
.
load_item
(
item
[
'location'
])
...
...
@@ -261,11 +347,11 @@ class MongoModuleStore(ModuleStoreBase):
descendents of the queried modules for more efficient results later
in the request. The depth is counted in the number of
calls to get_children() to cache. None indicates to cache all descendents.
"""
location
=
Location
.
ensure_fully_specified
(
location
)
item
=
self
.
_find_one
(
location
)
return
self
.
_load_items
([
item
],
depth
)[
0
]
module
=
self
.
_load_items
([
item
],
depth
)[
0
]
return
module
def
get_instance
(
self
,
course_id
,
location
,
depth
=
0
):
"""
...
...
@@ -285,7 +371,8 @@ class MongoModuleStore(ModuleStoreBase):
sort
=
[(
'revision'
,
pymongo
.
ASCENDING
)],
)
return
self
.
_load_items
(
list
(
items
),
depth
)
modules
=
self
.
_load_items
(
list
(
items
),
depth
)
return
modules
def
clone_item
(
self
,
source
,
location
):
"""
...
...
@@ -313,7 +400,7 @@ class MongoModuleStore(ModuleStoreBase):
raise
DuplicateItemError
(
location
)
def
get_course_for_item
(
self
,
location
):
def
get_course_for_item
(
self
,
location
,
depth
=
0
):
'''
VS[compat]
cdodge: for a given Xmodule, return the course that it belongs to
...
...
@@ -327,7 +414,7 @@ class MongoModuleStore(ModuleStoreBase):
# know the 'name' parameter in this context, so we have
# to assume there's only one item in this query even though we are not specifying a name
course_search_location
=
[
'i4x'
,
location
.
org
,
location
.
course
,
'course'
,
None
]
courses
=
self
.
get_items
(
course_search_location
)
courses
=
self
.
get_items
(
course_search_location
,
depth
=
depth
)
# make sure we found exactly one match on this above course search
found_cnt
=
len
(
courses
)
...
...
common/lib/xmodule/xmodule/x_module.py
View file @
ba7959fc
...
...
@@ -411,7 +411,6 @@ class ResourceTemplates(object):
return
templates
class
XModuleDescriptor
(
Plugin
,
HTMLSnippet
,
ResourceTemplates
):
"""
An XModuleDescriptor is a specification for an element of a course. This
...
...
@@ -585,11 +584,11 @@ class XModuleDescriptor(Plugin, HTMLSnippet, ResourceTemplates):
def
inherit_metadata
(
self
,
metadata
):
"""
Updates this module with metadata inherited from a containing module.
Only metadata specified in
self.
inheritable_metadata will
Only metadata specified in inheritable_metadata will
be inherited
"""
# Set all inheritable metadata from kwargs that are
# in
self.
inheritable_metadata and aren't already set in metadata
# in inheritable_metadata and aren't already set in metadata
for
attr
in
self
.
inheritable_metadata
:
if
attr
not
in
self
.
metadata
and
attr
in
metadata
:
self
.
_inherited_metadata
.
add
(
attr
)
...
...
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