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
75fab0cc
Commit
75fab0cc
authored
Oct 30, 2014
by
Adam
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #5538 from edx/adam/generalized-draft-import-export-2
Adam/generalized draft import export 2
parents
355a3b45
a6c31fc4
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
292 additions
and
51 deletions
+292
-51
cms/djangoapps/contentstore/tests/test_contentstore.py
+9
-0
cms/djangoapps/contentstore/tests/test_import_pure_xblock.py
+0
-1
cms/djangoapps/contentstore/tests/utils.py
+40
-3
common/lib/xmodule/xmodule/modulestore/__init__.py
+7
-0
common/lib/xmodule/xmodule/modulestore/store_utilities.py
+28
-0
common/lib/xmodule/xmodule/modulestore/tests/test_split_modulestore.py
+5
-0
common/lib/xmodule/xmodule/modulestore/tests/test_store_utilities.py
+81
-0
common/lib/xmodule/xmodule/modulestore/xml_exporter.py
+36
-16
common/lib/xmodule/xmodule/modulestore/xml_importer.py
+85
-30
common/lib/xmodule/xmodule/tests/xml/test_inheritance.py
+1
-1
No files found.
cms/djangoapps/contentstore/tests/test_contentstore.py
View file @
75fab0cc
...
@@ -250,6 +250,14 @@ class ImportRequiredTestCases(ContentStoreTestCase):
...
@@ -250,6 +250,14 @@ class ImportRequiredTestCases(ContentStoreTestCase):
# check for about content
# check for about content
self
.
verify_content_existence
(
self
.
store
,
root_dir
,
course_id
,
'about'
,
'about'
,
'.html'
)
self
.
verify_content_existence
(
self
.
store
,
root_dir
,
course_id
,
'about'
,
'about'
,
'.html'
)
# assert that there is an html and video directory in drafts:
draft_dir
=
OSFS
(
root_dir
/
'test_export/drafts'
)
self
.
assertTrue
(
draft_dir
.
exists
(
'html'
))
self
.
assertTrue
(
draft_dir
.
exists
(
'video'
))
# and assert that they contain the created modules
self
.
assertIn
(
self
.
DRAFT_HTML
+
".xml"
,
draft_dir
.
listdir
(
'html'
))
self
.
assertIn
(
self
.
DRAFT_VIDEO
+
".xml"
,
draft_dir
.
listdir
(
'video'
))
# check for grading_policy.json
# check for grading_policy.json
filesystem
=
OSFS
(
root_dir
/
'test_export/policies/2012_Fall'
)
filesystem
=
OSFS
(
root_dir
/
'test_export/policies/2012_Fall'
)
self
.
assertTrue
(
filesystem
.
exists
(
'grading_policy.json'
))
self
.
assertTrue
(
filesystem
.
exists
(
'grading_policy.json'
))
...
@@ -302,6 +310,7 @@ class ImportRequiredTestCases(ContentStoreTestCase):
...
@@ -302,6 +310,7 @@ class ImportRequiredTestCases(ContentStoreTestCase):
"""Verifies all temporary attributes added during export are removed"""
"""Verifies all temporary attributes added during export are removed"""
self
.
assertNotIn
(
'index_in_children_list'
,
attributes
)
self
.
assertNotIn
(
'index_in_children_list'
,
attributes
)
self
.
assertNotIn
(
'parent_sequential_url'
,
attributes
)
self
.
assertNotIn
(
'parent_sequential_url'
,
attributes
)
self
.
assertNotIn
(
'parent_url'
,
attributes
)
vertical
=
self
.
store
.
get_item
(
course_id
.
make_usage_key
(
'vertical'
,
self
.
TEST_VERTICAL
))
vertical
=
self
.
store
.
get_item
(
course_id
.
make_usage_key
(
'vertical'
,
self
.
TEST_VERTICAL
))
verify_export_attrs_removed
(
vertical
.
xml_attributes
)
verify_export_attrs_removed
(
vertical
.
xml_attributes
)
...
...
cms/djangoapps/contentstore/tests/test_import_pure_xblock.py
View file @
75fab0cc
...
@@ -7,7 +7,6 @@ from xblock.fields import String
...
@@ -7,7 +7,6 @@ from xblock.fields import String
from
xmodule.modulestore.xml_importer
import
import_from_xml
from
xmodule.modulestore.xml_importer
import
import_from_xml
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.mongo.draft
import
as_draft
from
xmodule.modulestore.mongo.draft
import
as_draft
...
...
cms/djangoapps/contentstore/tests/utils.py
View file @
75fab0cc
...
@@ -128,6 +128,8 @@ class CourseTestCase(ModuleStoreTestCase):
...
@@ -128,6 +128,8 @@ class CourseTestCase(ModuleStoreTestCase):
PRIVATE_VERTICAL
=
'a_private_vertical'
PRIVATE_VERTICAL
=
'a_private_vertical'
PUBLISHED_VERTICAL
=
'a_published_vertical'
PUBLISHED_VERTICAL
=
'a_published_vertical'
SEQUENTIAL
=
'vertical_sequential'
SEQUENTIAL
=
'vertical_sequential'
DRAFT_HTML
=
'draft_html'
DRAFT_VIDEO
=
'draft_video'
LOCKED_ASSET_KEY
=
AssetLocation
.
from_deprecated_string
(
'/c4x/edX/toy/asset/sample_static.txt'
)
LOCKED_ASSET_KEY
=
AssetLocation
.
from_deprecated_string
(
'/c4x/edX/toy/asset/sample_static.txt'
)
def
import_and_populate_course
(
self
):
def
import_and_populate_course
(
self
):
...
@@ -167,6 +169,20 @@ class CourseTestCase(ModuleStoreTestCase):
...
@@ -167,6 +169,20 @@ class CourseTestCase(ModuleStoreTestCase):
sequential
.
children
.
append
(
public_vertical
.
location
)
sequential
.
children
.
append
(
public_vertical
.
location
)
self
.
store
.
update_item
(
sequential
,
self
.
user
.
id
)
self
.
store
.
update_item
(
sequential
,
self
.
user
.
id
)
# create an html and video component to make drafts:
draft_html
=
self
.
store
.
create_item
(
self
.
user
.
id
,
course_id
,
'html'
,
self
.
DRAFT_HTML
)
draft_video
=
self
.
store
.
create_item
(
self
.
user
.
id
,
course_id
,
'video'
,
self
.
DRAFT_VIDEO
)
# add them as children to the public_vertical
public_vertical
.
children
.
append
(
draft_html
.
location
)
public_vertical
.
children
.
append
(
draft_video
.
location
)
self
.
store
.
update_item
(
public_vertical
,
self
.
user
.
id
)
# publish changes to vertical
self
.
store
.
publish
(
public_vertical
.
location
,
self
.
user
.
id
)
# convert html/video to draft
self
.
store
.
convert_to_draft
(
draft_html
.
location
,
self
.
user
.
id
)
self
.
store
.
convert_to_draft
(
draft_video
.
location
,
self
.
user
.
id
)
# lock an asset
# lock an asset
content_store
.
set_attr
(
self
.
LOCKED_ASSET_KEY
,
'locked'
,
True
)
content_store
.
set_attr
(
self
.
LOCKED_ASSET_KEY
,
'locked'
,
True
)
...
@@ -199,18 +215,25 @@ class CourseTestCase(ModuleStoreTestCase):
...
@@ -199,18 +215,25 @@ class CourseTestCase(ModuleStoreTestCase):
self
.
assertEqual
(
self
.
store
.
has_published_version
(
item
),
publish_state
)
self
.
assertEqual
(
self
.
store
.
has_published_version
(
item
),
publish_state
)
def
get_and_verify_publish_state
(
item_type
,
item_name
,
publish_state
):
def
get_and_verify_publish_state
(
item_type
,
item_name
,
publish_state
):
"""Gets the given item from the store and verifies the publish state of the item is as expected."""
"""
Gets the given item from the store and verifies the publish state
of the item is as expected.
"""
item
=
self
.
store
.
get_item
(
course_id
.
make_usage_key
(
item_type
,
item_name
))
item
=
self
.
store
.
get_item
(
course_id
.
make_usage_key
(
item_type
,
item_name
))
verify_item_publish_state
(
item
,
publish_state
)
verify_item_publish_state
(
item
,
publish_state
)
return
item
return
item
# verify
that the draft vertical is draft
# verify
draft vertical has a published version with published children
vertical
=
get_and_verify_publish_state
(
'vertical'
,
self
.
TEST_VERTICAL
,
True
)
vertical
=
get_and_verify_publish_state
(
'vertical'
,
self
.
TEST_VERTICAL
,
True
)
for
child
in
vertical
.
get_children
():
for
child
in
vertical
.
get_children
():
verify_item_publish_state
(
child
,
True
)
verify_item_publish_state
(
child
,
True
)
# make sure that we don't have a sequential that is not in draft mode
# verify that it has a draft too
self
.
assertTrue
(
getattr
(
vertical
,
"is_draft"
,
False
))
# make sure that we don't have a sequential that is in draft mode
sequential
=
get_and_verify_publish_state
(
'sequential'
,
self
.
SEQUENTIAL
,
True
)
sequential
=
get_and_verify_publish_state
(
'sequential'
,
self
.
SEQUENTIAL
,
True
)
self
.
assertFalse
(
getattr
(
sequential
,
"is_draft"
,
False
))
# verify that we have the private vertical
# verify that we have the private vertical
private_vertical
=
get_and_verify_publish_state
(
'vertical'
,
self
.
PRIVATE_VERTICAL
,
False
)
private_vertical
=
get_and_verify_publish_state
(
'vertical'
,
self
.
PRIVATE_VERTICAL
,
False
)
...
@@ -218,10 +241,24 @@ class CourseTestCase(ModuleStoreTestCase):
...
@@ -218,10 +241,24 @@ class CourseTestCase(ModuleStoreTestCase):
# verify that we have the public vertical
# verify that we have the public vertical
public_vertical
=
get_and_verify_publish_state
(
'vertical'
,
self
.
PUBLISHED_VERTICAL
,
True
)
public_vertical
=
get_and_verify_publish_state
(
'vertical'
,
self
.
PUBLISHED_VERTICAL
,
True
)
# verify that we have the draft html
draft_html
=
self
.
store
.
get_item
(
course_id
.
make_usage_key
(
'html'
,
self
.
DRAFT_HTML
))
self
.
assertTrue
(
getattr
(
draft_html
,
'is_draft'
,
False
))
# verify that we have the draft video
draft_video
=
self
.
store
.
get_item
(
course_id
.
make_usage_key
(
'video'
,
self
.
DRAFT_VIDEO
))
self
.
assertTrue
(
getattr
(
draft_video
,
'is_draft'
,
False
))
# verify verticals are children of sequential
# verify verticals are children of sequential
for
vert
in
[
vertical
,
private_vertical
,
public_vertical
]:
for
vert
in
[
vertical
,
private_vertical
,
public_vertical
]:
self
.
assertIn
(
vert
.
location
,
sequential
.
children
)
self
.
assertIn
(
vert
.
location
,
sequential
.
children
)
# verify draft html is the child of the public vertical
self
.
assertIn
(
draft_html
.
location
,
public_vertical
.
children
)
# verify draft video is the child of the public vertical
self
.
assertIn
(
draft_video
.
location
,
public_vertical
.
children
)
# verify textbook exists
# verify textbook exists
course
=
self
.
store
.
get_course
(
course_id
)
course
=
self
.
store
.
get_course
(
course_id
)
self
.
assertGreater
(
len
(
course
.
textbooks
),
0
)
self
.
assertGreater
(
len
(
course
.
textbooks
),
0
)
...
...
common/lib/xmodule/xmodule/modulestore/__init__.py
View file @
75fab0cc
...
@@ -382,6 +382,7 @@ class ModuleStoreRead(object):
...
@@ -382,6 +382,7 @@ class ModuleStoreRead(object):
If target is a list, do any of the list elements meet the criteria
If target is a list, do any of the list elements meet the criteria
If the criteria is a regex, does the target match it?
If the criteria is a regex, does the target match it?
If the criteria is a function, does invoking it on the target yield something truthy?
If the criteria is a function, does invoking it on the target yield something truthy?
If criteria is a dict {($nin|$in): []}, then do (none|any) of the list elements meet the criteria
Otherwise, is the target == criteria
Otherwise, is the target == criteria
'''
'''
if
isinstance
(
target
,
list
):
if
isinstance
(
target
,
list
):
...
@@ -390,6 +391,12 @@ class ModuleStoreRead(object):
...
@@ -390,6 +391,12 @@ class ModuleStoreRead(object):
return
criteria
.
search
(
target
)
is
not
None
return
criteria
.
search
(
target
)
is
not
None
elif
callable
(
criteria
):
elif
callable
(
criteria
):
return
criteria
(
target
)
return
criteria
(
target
)
elif
isinstance
(
criteria
,
dict
)
and
'$in'
in
criteria
:
# note isn't handling any other things in the dict other than in
return
any
(
self
.
_value_matches
(
target
,
test_val
)
for
test_val
in
criteria
[
'$in'
])
elif
isinstance
(
criteria
,
dict
)
and
'$nin'
in
criteria
:
# note isn't handling any other things in the dict other than nin
return
not
any
(
self
.
_value_matches
(
target
,
test_val
)
for
test_val
in
criteria
[
'$nin'
])
else
:
else
:
return
criteria
==
target
return
criteria
==
target
...
...
common/lib/xmodule/xmodule/modulestore/store_utilities.py
View file @
75fab0cc
import
re
import
re
import
logging
import
logging
from
collections
import
namedtuple
import
uuid
import
uuid
...
@@ -71,3 +72,30 @@ def rewrite_nonportable_content_links(source_course_id, dest_course_id, text):
...
@@ -71,3 +72,30 @@ def rewrite_nonportable_content_links(source_course_id, dest_course_id, text):
logging
.
warning
(
"Error producing regex substitution
%
r for text =
%
r.
\n\n
Error msg =
%
s"
,
source_course_id
,
text
,
str
(
exc
))
logging
.
warning
(
"Error producing regex substitution
%
r for text =
%
r.
\n\n
Error msg =
%
s"
,
source_course_id
,
text
,
str
(
exc
))
return
text
return
text
def
draft_node_constructor
(
module
,
url
,
parent_url
,
location
=
None
,
parent_location
=
None
,
index
=
None
):
"""
Contructs a draft_node namedtuple with defaults.
"""
draft_node
=
namedtuple
(
'draft_node'
,
[
'module'
,
'location'
,
'url'
,
'parent_location'
,
'parent_url'
,
'index'
])
return
draft_node
(
module
,
location
,
url
,
parent_location
,
parent_url
,
index
)
def
get_draft_subtree_roots
(
draft_nodes
):
"""
Takes a list of draft_nodes, which are namedtuples, each of which identify
itself and its parent.
If a draft_node is in `draft_nodes`, then we expect for all its children
should be in `draft_nodes` as well. Since `_import_draft` is recursive,
we only want to import the roots of any draft subtrees contained in
`draft_nodes`.
This generator yields those roots.
"""
urls
=
[
draft_node
.
url
for
draft_node
in
draft_nodes
]
for
draft_node
in
draft_nodes
:
if
draft_node
.
parent_url
not
in
urls
:
yield
draft_node
common/lib/xmodule/xmodule/modulestore/tests/test_split_modulestore.py
View file @
75fab0cc
...
@@ -884,6 +884,11 @@ class SplitModuleItemTests(SplitModuleTest):
...
@@ -884,6 +884,11 @@ class SplitModuleItemTests(SplitModuleTest):
self
.
assertFalse
(
modulestore
()
.
_value_matches
(
'I need some help'
,
re
.
compile
(
r'Help'
)))
self
.
assertFalse
(
modulestore
()
.
_value_matches
(
'I need some help'
,
re
.
compile
(
r'Help'
)))
self
.
assertTrue
(
modulestore
()
.
_value_matches
([
'I need some help'
,
'today'
],
re
.
compile
(
r'Help'
,
re
.
IGNORECASE
)))
self
.
assertTrue
(
modulestore
()
.
_value_matches
([
'I need some help'
,
'today'
],
re
.
compile
(
r'Help'
,
re
.
IGNORECASE
)))
self
.
assertTrue
(
modulestore
()
.
_value_matches
(
'gotcha'
,
{
'$in'
:
[
'a'
,
'bunch'
,
'of'
,
'gotcha'
]}))
self
.
assertFalse
(
modulestore
()
.
_value_matches
(
'gotcha'
,
{
'$in'
:
[
'a'
,
'bunch'
,
'of'
,
'gotchas'
]}))
self
.
assertFalse
(
modulestore
()
.
_value_matches
(
'gotcha'
,
{
'$nin'
:
[
'a'
,
'bunch'
,
'of'
,
'gotcha'
]}))
self
.
assertTrue
(
modulestore
()
.
_value_matches
(
'gotcha'
,
{
'$nin'
:
[
'a'
,
'bunch'
,
'of'
,
'gotchas'
]}))
self
.
assertTrue
(
modulestore
()
.
_block_matches
({
'a'
:
1
,
'b'
:
2
},
{
'a'
:
1
}))
self
.
assertTrue
(
modulestore
()
.
_block_matches
({
'a'
:
1
,
'b'
:
2
},
{
'a'
:
1
}))
self
.
assertFalse
(
modulestore
()
.
_block_matches
({
'a'
:
1
,
'b'
:
2
},
{
'a'
:
2
}))
self
.
assertFalse
(
modulestore
()
.
_block_matches
({
'a'
:
1
,
'b'
:
2
},
{
'a'
:
2
}))
self
.
assertFalse
(
modulestore
()
.
_block_matches
({
'a'
:
1
,
'b'
:
2
},
{
'c'
:
1
}))
self
.
assertFalse
(
modulestore
()
.
_block_matches
({
'a'
:
1
,
'b'
:
2
},
{
'c'
:
1
}))
...
...
common/lib/xmodule/xmodule/modulestore/tests/test_store_utilities.py
0 → 100644
View file @
75fab0cc
"""
Tests for store_utilities.py
"""
import
unittest
from
mock
import
Mock
import
ddt
from
xmodule.modulestore.store_utilities
import
(
get_draft_subtree_roots
,
draft_node_constructor
)
@ddt.ddt
class
TestUtils
(
unittest
.
TestCase
):
"""
Tests for store_utilities
ASCII trees for ONLY_ROOTS and SOME_TREES:
ONLY_ROOTS:
1)
vertical (not draft)
|
url1
2)
sequential (not draft)
|
url2
SOME_TREES:
1)
sequential_1 (not draft)
|
vertical_1
/
\
/
\
child_1 child_2
2)
great_grandparent_vertical (not draft)
|
grandparent_vertical
|
vertical_2
/
\
/
\
child_3 child_4
"""
ONLY_ROOTS
=
[
draft_node_constructor
(
Mock
(),
'url1'
,
'vertical'
),
draft_node_constructor
(
Mock
(),
'url2'
,
'sequential'
),
]
ONLY_ROOTS_URLS
=
[
'url1'
,
'url2'
]
SOME_TREES
=
[
draft_node_constructor
(
Mock
(),
'child_1'
,
'vertical_1'
),
draft_node_constructor
(
Mock
(),
'child_2'
,
'vertical_1'
),
draft_node_constructor
(
Mock
(),
'vertical_1'
,
'sequential_1'
),
draft_node_constructor
(
Mock
(),
'child_3'
,
'vertical_2'
),
draft_node_constructor
(
Mock
(),
'child_4'
,
'vertical_2'
),
draft_node_constructor
(
Mock
(),
'vertical_2'
,
'grandparent_vertical'
),
draft_node_constructor
(
Mock
(),
'grandparent_vertical'
,
'great_grandparent_vertical'
),
]
SOME_TREES_ROOTS_URLS
=
[
'vertical_1'
,
'grandparent_vertical'
]
@ddt.data
(
(
ONLY_ROOTS
,
ONLY_ROOTS_URLS
),
(
SOME_TREES
,
SOME_TREES_ROOTS_URLS
),
)
@ddt.unpack
def
test_get_draft_subtree_roots
(
self
,
module_nodes
,
expected_roots_urls
):
"""tests for get_draft_subtree_roots"""
subtree_roots_urls
=
[
root
.
url
for
root
in
get_draft_subtree_roots
(
module_nodes
)]
# check that we return the expected urls
self
.
assertEqual
(
set
(
subtree_roots_urls
),
set
(
expected_roots_urls
))
common/lib/xmodule/xmodule/modulestore/xml_exporter.py
View file @
75fab0cc
...
@@ -9,6 +9,7 @@ from xmodule.contentstore.content import StaticContent
...
@@ -9,6 +9,7 @@ from xmodule.contentstore.content import StaticContent
from
xmodule.exceptions
import
NotFoundError
from
xmodule.exceptions
import
NotFoundError
from
xmodule.modulestore
import
EdxJSONEncoder
,
ModuleStoreEnum
from
xmodule.modulestore
import
EdxJSONEncoder
,
ModuleStoreEnum
from
xmodule.modulestore.inheritance
import
own_metadata
from
xmodule.modulestore.inheritance
import
own_metadata
from
xmodule.modulestore.store_utilities
import
draft_node_constructor
,
get_draft_subtree_roots
from
fs.osfs
import
OSFS
from
fs.osfs
import
OSFS
from
json
import
dumps
from
json
import
dumps
import
json
import
json
...
@@ -109,36 +110,55 @@ def export_to_xml(modulestore, contentstore, course_key, root_dir, course_dir):
...
@@ -109,36 +110,55 @@ def export_to_xml(modulestore, contentstore, course_key, root_dir, course_dir):
#### DRAFTS ####
#### DRAFTS ####
# xml backed courses don't support drafts!
# xml backed courses don't support drafts!
if
course
.
runtime
.
modulestore
.
get_modulestore_type
()
!=
ModuleStoreEnum
.
Type
.
xml
:
if
course
.
runtime
.
modulestore
.
get_modulestore_type
()
!=
ModuleStoreEnum
.
Type
.
xml
:
# NOTE: this code assumes that verticals are the top most draftable container
# should we change the application, then this assumption will no longer be valid
# NOTE: we need to explicitly implement the logic for setting the vertical's parent
# NOTE: we need to explicitly implement the logic for setting the vertical's parent
# and index here since the XML modulestore cannot load draft modules
# and index here since the XML modulestore cannot load draft modules
with
modulestore
.
branch_setting
(
ModuleStoreEnum
.
Branch
.
draft_preferred
,
course_key
):
with
modulestore
.
branch_setting
(
ModuleStoreEnum
.
Branch
.
draft_preferred
,
course_key
):
draft_
vertical
s
=
modulestore
.
get_items
(
draft_
module
s
=
modulestore
.
get_items
(
course_key
,
course_key
,
qualifiers
=
{
'category'
:
'vertical'
},
qualifiers
=
{
'category'
:
{
'$nin'
:
DIRECT_ONLY_CATEGORIES
}
},
revision
=
ModuleStoreEnum
.
RevisionOption
.
draft_only
revision
=
ModuleStoreEnum
.
RevisionOption
.
draft_only
)
)
if
len
(
draft_verticals
)
>
0
:
if
draft_modules
:
draft_course_dir
=
export_fs
.
makeopendir
(
DRAFT_DIR
)
draft_course_dir
=
export_fs
.
makeopendir
(
DRAFT_DIR
)
for
draft_vertical
in
draft_verticals
:
# accumulate tuples of draft_modules and their parents in
# this list:
draft_node_list
=
[]
for
draft_module
in
draft_modules
:
parent_loc
=
modulestore
.
get_parent_location
(
parent_loc
=
modulestore
.
get_parent_location
(
draft_
vertical
.
location
,
draft_
module
.
location
,
revision
=
ModuleStoreEnum
.
RevisionOption
.
draft_preferred
revision
=
ModuleStoreEnum
.
RevisionOption
.
draft_preferred
)
)
# Don't try to export orphaned items.
# Don't try to export orphaned items.
if
parent_loc
is
not
None
:
if
parent_loc
is
not
None
:
logging
.
debug
(
'parent_loc = {0}'
.
format
(
parent_loc
))
logging
.
debug
(
'parent_loc = {0}'
.
format
(
parent_loc
))
if
parent_loc
.
category
in
DIRECT_ONLY_CATEGORIES
:
draft_node
=
draft_node_constructor
(
draft_vertical
.
xml_attributes
[
'parent_sequential_url'
]
=
parent_loc
.
to_deprecated_string
()
draft_module
,
sequential
=
modulestore
.
get_item
(
parent_loc
)
location
=
draft_module
.
location
,
index
=
sequential
.
children
.
index
(
draft_vertical
.
location
)
url
=
draft_module
.
location
.
to_deprecated_string
(),
draft_vertical
.
xml_attributes
[
'index_in_children_list'
]
=
str
(
index
)
parent_location
=
parent_loc
,
draft_vertical
.
runtime
.
export_fs
=
draft_course_dir
parent_url
=
parent_loc
.
to_deprecated_string
(),
adapt_references
(
draft_vertical
,
xml_centric_course_key
,
draft_course_dir
)
)
node
=
lxml
.
etree
.
Element
(
'unknown'
)
draft_vertical
.
add_xml_to_node
(
node
)
draft_node_list
.
append
(
draft_node
)
for
draft_node
in
get_draft_subtree_roots
(
draft_node_list
):
# only export the roots of the draft subtrees
# since export_from_xml (called by `add_xml_to_node`)
# exports a whole tree
draft_node
.
module
.
xml_attributes
[
'parent_url'
]
=
draft_node
.
parent_url
parent
=
modulestore
.
get_item
(
draft_node
.
parent_location
)
index
=
parent
.
children
.
index
(
draft_node
.
module
.
location
)
draft_node
.
module
.
xml_attributes
[
'index_in_children_list'
]
=
str
(
index
)
draft_node
.
module
.
runtime
.
export_fs
=
draft_course_dir
adapt_references
(
draft_node
.
module
,
xml_centric_course_key
,
draft_course_dir
)
node
=
lxml
.
etree
.
Element
(
'unknown'
)
draft_node
.
module
.
add_xml_to_node
(
node
)
def
adapt_references
(
subtree
,
destination_course_key
,
export_fs
):
def
adapt_references
(
subtree
,
destination_course_key
,
export_fs
):
...
...
common/lib/xmodule/xmodule/modulestore/xml_importer.py
View file @
75fab0cc
...
@@ -42,7 +42,8 @@ from xmodule.modulestore.django import ASSET_IGNORE_REGEX
...
@@ -42,7 +42,8 @@ from xmodule.modulestore.django import ASSET_IGNORE_REGEX
from
xmodule.modulestore.exceptions
import
DuplicateCourseError
from
xmodule.modulestore.exceptions
import
DuplicateCourseError
from
xmodule.modulestore.mongo.base
import
MongoRevisionKey
from
xmodule.modulestore.mongo.base
import
MongoRevisionKey
from
xmodule.modulestore
import
ModuleStoreEnum
from
xmodule.modulestore
import
ModuleStoreEnum
from
xmodule.modulestore.exceptions
import
ItemNotFoundError
from
xmodule.modulestore.store_utilities
import
draft_node_constructor
,
get_draft_subtree_roots
log
=
logging
.
getLogger
(
__name__
)
log
=
logging
.
getLogger
(
__name__
)
...
@@ -438,6 +439,8 @@ def _import_module_and_update_references(
...
@@ -438,6 +439,8 @@ def _import_module_and_update_references(
value
=
field
.
read_from
(
module
)
value
=
field
.
read_from
(
module
)
# remove any export/import only xml_attributes
# remove any export/import only xml_attributes
# which are used to wire together draft imports
# which are used to wire together draft imports
if
'parent_url'
in
value
:
del
value
[
'parent_url'
]
if
'parent_sequential_url'
in
value
:
if
'parent_sequential_url'
in
value
:
del
value
[
'parent_sequential_url'
]
del
value
[
'parent_sequential_url'
]
...
@@ -500,28 +503,26 @@ def _import_course_draft(
...
@@ -500,28 +503,26 @@ def _import_course_draft(
# to ensure that pure XBlock field data is updated correctly.
# to ensure that pure XBlock field data is updated correctly.
_update_module_location
(
module
,
module_location
.
replace
(
revision
=
MongoRevisionKey
.
draft
))
_update_module_location
(
module
,
module_location
.
replace
(
revision
=
MongoRevisionKey
.
draft
))
parent_url
=
get_parent_url
(
module
)
index
=
index_in_children_list
(
module
)
# make sure our parent has us in its list of children
# make sure our parent has us in its list of children
# this is to make sure private only
vertical
s show up
# this is to make sure private only
module
s show up
# in the list of children since they would have been
# in the list of children since they would have been
# filtered out from the non-draft store export.
# filtered out from the non-draft store export.
# Note though that verticals nested below the unit level will not have
if
parent_url
is
not
None
and
index
is
not
None
:
# a parent_sequential_url and do not need special handling.
if
module
.
location
.
category
==
'vertical'
and
'parent_sequential_url'
in
module
.
xml_attributes
:
sequential_url
=
module
.
xml_attributes
[
'parent_sequential_url'
]
index
=
int
(
module
.
xml_attributes
[
'index_in_children_list'
])
course_key
=
descriptor
.
location
.
course_key
course_key
=
descriptor
.
location
.
course_key
seq_location
=
course_key
.
make_usage_key_from_deprecated_string
(
sequential
_url
)
parent_location
=
course_key
.
make_usage_key_from_deprecated_string
(
parent
_url
)
# IMPORTANT: Be sure to update the
sequential
in the NEW namespace
# IMPORTANT: Be sure to update the
parent
in the NEW namespace
seq_location
=
seq
_location
.
map_into_course
(
target_course_id
)
parent_location
=
parent
_location
.
map_into_course
(
target_course_id
)
sequential
=
store
.
get_item
(
seq
_location
,
depth
=
0
)
parent
=
store
.
get_item
(
parent
_location
,
depth
=
0
)
non_draft_location
=
module
.
location
.
map_into_course
(
target_course_id
)
non_draft_location
=
module
.
location
.
map_into_course
(
target_course_id
)
if
not
any
(
child
.
block_id
==
module
.
location
.
block_id
for
child
in
sequential
.
children
):
if
not
any
(
child
.
block_id
==
module
.
location
.
block_id
for
child
in
parent
.
children
):
sequential
.
children
.
insert
(
index
,
non_draft_location
)
parent
.
children
.
insert
(
index
,
non_draft_location
)
store
.
update_item
(
sequential
,
user_id
)
store
.
update_item
(
parent
,
user_id
)
_import_module_and_update_references
(
_import_module_and_update_references
(
module
,
store
,
user_id
,
module
,
store
,
user_id
,
...
@@ -537,8 +538,8 @@ def _import_course_draft(
...
@@ -537,8 +538,8 @@ def _import_course_draft(
# First it is necessary to order the draft items by their desired index in the child list
# First it is necessary to order the draft items by their desired index in the child list
# (order os.walk returns them in is not guaranteed).
# (order os.walk returns them in is not guaranteed).
drafts
=
dict
()
drafts
=
[]
for
dirname
,
_dirnames
,
filenames
in
os
.
walk
(
draft_dir
+
"/vertical"
):
for
dirname
,
_dirnames
,
filenames
in
os
.
walk
(
draft_dir
):
for
filename
in
filenames
:
for
filename
in
filenames
:
module_path
=
os
.
path
.
join
(
dirname
,
filename
)
module_path
=
os
.
path
.
join
(
dirname
,
filename
)
with
open
(
module_path
,
'r'
)
as
f
:
with
open
(
module_path
,
'r'
)
as
f
:
...
@@ -593,23 +594,27 @@ def _import_course_draft(
...
@@ -593,23 +594,27 @@ def _import_course_draft(
filename
,
__
=
os
.
path
.
splitext
(
filename
)
filename
,
__
=
os
.
path
.
splitext
(
filename
)
descriptor
.
location
=
descriptor
.
location
.
replace
(
name
=
filename
)
descriptor
.
location
=
descriptor
.
location
.
replace
(
name
=
filename
)
index
=
int
(
descriptor
.
xml_attributes
[
'index_in_children_list'
])
index
=
index_in_children_list
(
descriptor
)
if
index
in
drafts
:
parent_url
=
get_parent_url
(
descriptor
,
xml
)
drafts
[
index
]
.
append
(
descriptor
)
draft_url
=
descriptor
.
location
.
to_deprecated_string
()
else
:
drafts
[
index
]
=
[
descriptor
]
draft
=
draft_node_constructor
(
module
=
descriptor
,
url
=
draft_url
,
parent_url
=
parent_url
,
index
=
index
)
drafts
.
append
(
draft
)
except
Exception
:
except
Exception
:
# pylint: disable=W0703
logging
.
exception
(
'Error while parsing course xml.'
)
logging
.
exception
(
'Error while parsing course xml.'
)
# For each index_in_children_list key, there is a list of vertical descriptors.
# sort drafts by `index_in_children_list` attribute
drafts
.
sort
(
key
=
lambda
x
:
x
.
index
)
for
key
in
sorted
(
drafts
.
iterkeys
()):
for
draft
in
get_draft_subtree_roots
(
drafts
):
for
descriptor
in
drafts
[
key
]:
try
:
try
:
_import_module
(
draft
.
module
)
_import_module
(
descriptor
)
except
Exception
:
# pylint: disable=W0703
except
Exception
:
logging
.
exception
(
'while importing draft descriptor
%
s'
,
draft
.
module
)
logging
.
exception
(
'while importing draft descriptor
%
s'
,
descriptor
)
def
allowed_metadata_by_category
(
category
):
def
allowed_metadata_by_category
(
category
):
...
@@ -648,6 +653,56 @@ def check_module_metadata_editability(module):
...
@@ -648,6 +653,56 @@ def check_module_metadata_editability(module):
return
err_cnt
return
err_cnt
def
get_parent_url
(
module
,
xml
=
None
):
"""
Get the parent_url, if any, from module using xml as an alternative source. If it finds it in
xml but not on module, it modifies module so that the next call to this w/o the xml will get the parent url
"""
if
hasattr
(
module
,
'xml_attributes'
):
return
module
.
xml_attributes
.
get
(
# handle deprecated old attr
'parent_url'
,
module
.
xml_attributes
.
get
(
'parent_sequential_url'
)
)
if
xml
is
not
None
:
create_xml_attributes
(
module
,
xml
)
return
get_parent_url
(
module
)
# don't reparse xml b/c don't infinite recurse but retry above lines
return
None
def
index_in_children_list
(
module
,
xml
=
None
):
"""
Get the index_in_children_list, if any, from module using xml
as an alternative source. If it finds it in xml but not on module,
it modifies module so that the next call to this w/o the xml
will get the field.
"""
if
hasattr
(
module
,
'xml_attributes'
):
val
=
module
.
xml_attributes
.
get
(
'index_in_children_list'
)
if
val
is
not
None
:
return
int
(
val
)
return
None
if
xml
is
not
None
:
create_xml_attributes
(
module
,
xml
)
return
index_in_children_list
(
module
)
# don't reparse xml b/c don't infinite recurse but retry above lines
return
None
def
create_xml_attributes
(
module
,
xml
):
"""
Make up for modules which don't define xml_attributes by creating them here and populating
"""
xml_attrs
=
{}
for
attr
,
val
in
xml
.
attrib
.
iteritems
():
if
attr
not
in
module
.
fields
:
# translate obsolete attr
if
attr
==
'parent_sequential_url'
:
attr
=
'parent_url'
xml_attrs
[
attr
]
=
val
# now cache it on module where it's expected
setattr
(
module
,
'xml_attributes'
,
xml_attrs
)
def
validate_no_non_editable_metadata
(
module_store
,
course_id
,
category
):
def
validate_no_non_editable_metadata
(
module_store
,
course_id
,
category
):
err_cnt
=
0
err_cnt
=
0
for
module_loc
in
module_store
.
modules
[
course_id
]:
for
module_loc
in
module_store
.
modules
[
course_id
]:
...
...
common/lib/xmodule/xmodule/tests/xml/test_inheritance.py
View file @
75fab0cc
...
@@ -39,7 +39,7 @@ class TestInheritedFieldParsing(XModuleXmlImportTest):
...
@@ -39,7 +39,7 @@ class TestInheritedFieldParsing(XModuleXmlImportTest):
parent
=
sequence
,
parent
=
sequence
,
tag
=
'video'
,
tag
=
'video'
,
attribs
=
{
attribs
=
{
'parent_
sequential_
url'
:
'foo'
,
'garbage'
:
'asdlk'
,
'parent_url'
:
'foo'
,
'garbage'
:
'asdlk'
,
'download_video'
:
'true'
,
'download_video'
:
'true'
,
}
}
)
)
...
...
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