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
8f1a4cca
Commit
8f1a4cca
authored
Mar 17, 2016
by
Dmitry Viskov
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Duplicate item in Studio should also duplicate related xblock aside
parent
b91f940a
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
187 additions
and
56 deletions
+187
-56
cms/djangoapps/contentstore/views/item.py
+13
-0
cms/djangoapps/contentstore/views/tests/test_item.py
+170
-55
common/lib/xmodule/xmodule/modulestore/split_mongo/split_mongo_kvs.py
+4
-1
No files found.
cms/djangoapps/contentstore/views/item.py
View file @
8f1a4cca
...
...
@@ -595,6 +595,18 @@ def _duplicate_item(parent_usage_key, duplicate_source_usage_key, user, display_
else
:
duplicate_metadata
[
'display_name'
]
=
_
(
"Duplicate of '{0}'"
)
.
format
(
source_item
.
display_name
)
asides_to_create
=
[]
for
aside
in
source_item
.
runtime
.
get_asides
(
source_item
):
for
field
in
aside
.
fields
.
values
():
if
field
.
scope
in
(
Scope
.
settings
,
Scope
.
content
,)
and
field
.
is_set_on
(
aside
):
asides_to_create
.
append
(
aside
)
break
for
aside
in
asides_to_create
:
for
field
in
aside
.
fields
.
values
():
if
field
.
scope
not
in
(
Scope
.
settings
,
Scope
.
content
,):
field
.
delete_from
(
aside
)
dest_module
=
store
.
create_item
(
user
.
id
,
dest_usage_key
.
course_key
,
...
...
@@ -603,6 +615,7 @@ def _duplicate_item(parent_usage_key, duplicate_source_usage_key, user, display_
definition_data
=
source_item
.
get_explicitly_set_fields_by_scope
(
Scope
.
content
),
metadata
=
duplicate_metadata
,
runtime
=
source_item
.
runtime
,
asides
=
asides_to_create
)
children_handled
=
False
...
...
cms/djangoapps/contentstore/views/tests/test_item.py
View file @
8f1a4cca
...
...
@@ -32,6 +32,11 @@ from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase, TEST_DAT
from
xmodule.modulestore.tests.factories
import
ItemFactory
,
LibraryFactory
,
check_mongo_calls
,
CourseFactory
from
xmodule.x_module
import
STUDIO_VIEW
,
STUDENT_VIEW
from
xmodule.course_module
import
DEFAULT_START_DATE
from
xblock.core
import
XBlockAside
from
xblock.fields
import
Scope
,
String
,
ScopeIds
from
xblock.fragment
import
Fragment
from
xblock.runtime
import
DictKeyValueStore
,
KvsFieldData
from
xblock.test.tools
import
TestRuntime
from
xblock.exceptions
import
NoSuchHandlerError
from
xblock_django.user_service
import
DjangoXBlockUserService
from
opaque_keys.edx.keys
import
UsageKey
,
CourseKey
...
...
@@ -39,6 +44,22 @@ from opaque_keys.edx.locations import Location
from
xmodule.partitions.partitions
import
Group
,
UserPartition
class
AsideTest
(
XBlockAside
):
"""
Test xblock aside class
"""
FRAG_CONTENT
=
u"<p>Aside Foo rendered</p>"
field11
=
String
(
default
=
"aside1_default_value1"
,
scope
=
Scope
.
content
)
field12
=
String
(
default
=
"aside1_default_value2"
,
scope
=
Scope
.
settings
)
field13
=
String
(
default
=
"aside1_default_value3"
,
scope
=
Scope
.
parent
)
@XBlockAside.aside_for
(
'student_view'
)
def
student_view_aside
(
self
,
block
,
context
):
# pylint: disable=unused-argument
"""Add to the student view"""
return
Fragment
(
self
.
FRAG_CONTENT
)
class
ItemTest
(
CourseTestCase
):
""" Base test class for create, save, and delete """
def
setUp
(
self
):
...
...
@@ -176,7 +197,8 @@ class GetItemTest(ItemTest):
# Add a problem beneath a child vertical
child_vertical_usage_key
=
self
.
_create_vertical
(
parent_usage_key
=
root_usage_key
)
resp
=
self
.
create_xblock
(
parent_usage_key
=
child_vertical_usage_key
,
category
=
'problem'
,
boilerplate
=
'multiplechoice.yaml'
)
resp
=
self
.
create_xblock
(
parent_usage_key
=
child_vertical_usage_key
,
category
=
'problem'
,
boilerplate
=
'multiplechoice.yaml'
)
self
.
assertEqual
(
resp
.
status_code
,
200
)
# Get the preview HTML
...
...
@@ -201,7 +223,8 @@ class GetItemTest(ItemTest):
self
.
assertEqual
(
resp
.
status_code
,
200
)
wrapper_usage_key
=
self
.
response_usage_key
(
resp
)
resp
=
self
.
create_xblock
(
parent_usage_key
=
wrapper_usage_key
,
category
=
'problem'
,
boilerplate
=
'multiplechoice.yaml'
)
resp
=
self
.
create_xblock
(
parent_usage_key
=
wrapper_usage_key
,
category
=
'problem'
,
boilerplate
=
'multiplechoice.yaml'
)
self
.
assertEqual
(
resp
.
status_code
,
200
)
# Get the preview HTML and verify the View -> link is present.
...
...
@@ -223,9 +246,11 @@ class GetItemTest(ItemTest):
root_usage_key
=
self
.
_create_vertical
()
resp
=
self
.
create_xblock
(
category
=
'split_test'
,
parent_usage_key
=
root_usage_key
)
split_test_usage_key
=
self
.
response_usage_key
(
resp
)
resp
=
self
.
create_xblock
(
parent_usage_key
=
split_test_usage_key
,
category
=
'html'
,
boilerplate
=
'announcement.yaml'
)
resp
=
self
.
create_xblock
(
parent_usage_key
=
split_test_usage_key
,
category
=
'html'
,
boilerplate
=
'announcement.yaml'
)
self
.
assertEqual
(
resp
.
status_code
,
200
)
resp
=
self
.
create_xblock
(
parent_usage_key
=
split_test_usage_key
,
category
=
'html'
,
boilerplate
=
'zooming_image.yaml'
)
resp
=
self
.
create_xblock
(
parent_usage_key
=
split_test_usage_key
,
category
=
'html'
,
boilerplate
=
'zooming_image.yaml'
)
self
.
assertEqual
(
resp
.
status_code
,
200
)
html
,
__
=
self
.
_get_container_preview
(
split_test_usage_key
)
self
.
assertIn
(
'Announcement'
,
html
)
...
...
@@ -265,7 +290,8 @@ class GetItemTest(ItemTest):
}
response
=
self
.
client
.
put
(
reverse_course_url
(
'group_configurations_detail_handler'
,
self
.
course
.
id
,
kwargs
=
{
'group_configuration_id'
:
0
}),
reverse_course_url
(
'group_configurations_detail_handler'
,
self
.
course
.
id
,
kwargs
=
{
'group_configuration_id'
:
0
}),
data
=
json
.
dumps
(
GROUP_CONFIGURATION_JSON
),
content_type
=
"application/json"
,
HTTP_ACCEPT
=
"application/json"
,
...
...
@@ -445,53 +471,39 @@ class TestCreateItem(ItemTest):
self
.
assertEquals
(
new_tab
.
display_name
,
'Empty'
)
class
TestDuplicateItem
(
ItemTest
):
"""
Test the duplicate method.
"""
def
setUp
(
self
):
""" Creates the test course structure and a few components to 'duplicate'. """
super
(
TestDuplicateItem
,
self
)
.
setUp
()
# Create a parent chapter (for testing children of children).
resp
=
self
.
create_xblock
(
parent_usage_key
=
self
.
usage_key
,
category
=
'chapter'
)
self
.
chapter_usage_key
=
self
.
response_usage_key
(
resp
)
# create a sequential containing a problem and an html component
resp
=
self
.
create_xblock
(
parent_usage_key
=
self
.
chapter_usage_key
,
category
=
'sequential'
)
self
.
seq_usage_key
=
self
.
response_usage_key
(
resp
)
# create problem and an html component
resp
=
self
.
create_xblock
(
parent_usage_key
=
self
.
seq_usage_key
,
category
=
'problem'
,
boilerplate
=
'multiplechoice.yaml'
)
self
.
problem_usage_key
=
self
.
response_usage_key
(
resp
)
resp
=
self
.
create_xblock
(
parent_usage_key
=
self
.
seq_usage_key
,
category
=
'html'
)
self
.
html_usage_key
=
self
.
response_usage_key
(
resp
)
# Create a second sequential just (testing children of children)
self
.
create_xblock
(
parent_usage_key
=
self
.
chapter_usage_key
,
category
=
'sequential2'
)
def
test_duplicate_equality
(
self
):
class
DuplicateHelper
(
object
):
"""
Tests that a duplicated xblock is identical to the original,
except for location and display name.
Helper mixin class for TestDuplicateItem and TestDuplicateItemWithAsides
"""
def
duplicate_and_verify
(
source_usage_key
,
parent_usage_key
):
def
_duplicate_and_verify
(
self
,
source_usage_key
,
parent_usage_key
,
check_asides
=
False
):
""" Duplicates the source, parenting to supplied parent. Then does equality check. """
usage_key
=
self
.
_duplicate_item
(
parent_usage_key
,
source_usage_key
)
# pylint: disable=no-member
self
.
assertTrue
(
check_equality
(
source_usage_key
,
usage_key
,
parent_usage_key
),
self
.
_check_equality
(
source_usage_key
,
usage_key
,
parent_usage_key
,
check_asides
=
check_asides
),
"Duplicated item differs from original"
)
def
check_equality
(
source_usage_key
,
duplicate_usage_key
,
parent_usage_key
=
Non
e
):
def
_check_equality
(
self
,
source_usage_key
,
duplicate_usage_key
,
parent_usage_key
=
None
,
check_asides
=
Fals
e
):
"""
Gets source and duplicated items from the modulestore using supplied usage keys.
Then verifies that they represent equivalent items (modulo parents and other
known things that may differ).
"""
# pylint: disable=no-member
original_item
=
self
.
get_item_from_modulestore
(
source_usage_key
)
duplicated_item
=
self
.
get_item_from_modulestore
(
duplicate_usage_key
)
if
check_asides
:
original_asides
=
original_item
.
runtime
.
get_asides
(
original_item
)
duplicated_asides
=
duplicated_item
.
runtime
.
get_asides
(
duplicated_item
)
self
.
assertEqual
(
len
(
original_asides
),
1
)
self
.
assertEqual
(
len
(
duplicated_asides
),
1
)
self
.
assertEqual
(
original_asides
[
0
]
.
field11
,
duplicated_asides
[
0
]
.
field11
)
self
.
assertEqual
(
original_asides
[
0
]
.
field12
,
duplicated_asides
[
0
]
.
field12
)
self
.
assertNotEqual
(
original_asides
[
0
]
.
field13
,
duplicated_asides
[
0
]
.
field13
)
self
.
assertEqual
(
duplicated_asides
[
0
]
.
field13
,
'aside1_default_value3'
)
self
.
assertNotEqual
(
unicode
(
original_item
.
location
),
unicode
(
duplicated_item
.
location
),
...
...
@@ -526,16 +538,63 @@ class TestDuplicateItem(ItemTest):
"Duplicated item differs in number of children"
)
for
i
in
xrange
(
len
(
original_item
.
children
)):
if
not
check_equality
(
original_item
.
children
[
i
],
duplicated_item
.
children
[
i
]):
if
not
self
.
_
check_equality
(
original_item
.
children
[
i
],
duplicated_item
.
children
[
i
]):
return
False
duplicated_item
.
children
=
original_item
.
children
return
original_item
==
duplicated_item
duplicate_and_verify
(
self
.
problem_usage_key
,
self
.
seq_usage_key
)
duplicate_and_verify
(
self
.
html_usage_key
,
self
.
seq_usage_key
)
duplicate_and_verify
(
self
.
seq_usage_key
,
self
.
chapter_usage_key
)
duplicate_and_verify
(
self
.
chapter_usage_key
,
self
.
usage_key
)
def
_duplicate_item
(
self
,
parent_usage_key
,
source_usage_key
,
display_name
=
None
):
"""
Duplicates the source.
"""
# pylint: disable=no-member
data
=
{
'parent_locator'
:
unicode
(
parent_usage_key
),
'duplicate_source_locator'
:
unicode
(
source_usage_key
)
}
if
display_name
is
not
None
:
data
[
'display_name'
]
=
display_name
resp
=
self
.
client
.
ajax_post
(
reverse
(
'contentstore.views.xblock_handler'
),
json
.
dumps
(
data
))
return
self
.
response_usage_key
(
resp
)
class
TestDuplicateItem
(
ItemTest
,
DuplicateHelper
):
"""
Test the duplicate method.
"""
def
setUp
(
self
):
""" Creates the test course structure and a few components to 'duplicate'. """
super
(
TestDuplicateItem
,
self
)
.
setUp
()
# Create a parent chapter (for testing children of children).
resp
=
self
.
create_xblock
(
parent_usage_key
=
self
.
usage_key
,
category
=
'chapter'
)
self
.
chapter_usage_key
=
self
.
response_usage_key
(
resp
)
# create a sequential containing a problem and an html component
resp
=
self
.
create_xblock
(
parent_usage_key
=
self
.
chapter_usage_key
,
category
=
'sequential'
)
self
.
seq_usage_key
=
self
.
response_usage_key
(
resp
)
# create problem and an html component
resp
=
self
.
create_xblock
(
parent_usage_key
=
self
.
seq_usage_key
,
category
=
'problem'
,
boilerplate
=
'multiplechoice.yaml'
)
self
.
problem_usage_key
=
self
.
response_usage_key
(
resp
)
resp
=
self
.
create_xblock
(
parent_usage_key
=
self
.
seq_usage_key
,
category
=
'html'
)
self
.
html_usage_key
=
self
.
response_usage_key
(
resp
)
# Create a second sequential just (testing children of children)
self
.
create_xblock
(
parent_usage_key
=
self
.
chapter_usage_key
,
category
=
'sequential2'
)
def
test_duplicate_equality
(
self
):
"""
Tests that a duplicated xblock is identical to the original,
except for location and display name.
"""
self
.
_duplicate_and_verify
(
self
.
problem_usage_key
,
self
.
seq_usage_key
)
self
.
_duplicate_and_verify
(
self
.
html_usage_key
,
self
.
seq_usage_key
)
self
.
_duplicate_and_verify
(
self
.
seq_usage_key
,
self
.
chapter_usage_key
)
self
.
_duplicate_and_verify
(
self
.
chapter_usage_key
,
self
.
usage_key
)
def
test_ordering
(
self
):
"""
...
...
@@ -599,16 +658,67 @@ class TestDuplicateItem(ItemTest):
# Now send a custom display name for the duplicate.
verify_name
(
self
.
seq_usage_key
,
self
.
chapter_usage_key
,
"customized name"
,
display_name
=
"customized name"
)
def
_duplicate_item
(
self
,
parent_usage_key
,
source_usage_key
,
display_name
=
None
):
data
=
{
'parent_locator'
:
unicode
(
parent_usage_key
),
'duplicate_source_locator'
:
unicode
(
source_usage_key
)
}
if
display_name
is
not
None
:
data
[
'display_name'
]
=
display_name
resp
=
self
.
client
.
ajax_post
(
reverse
(
'contentstore.views.xblock_handler'
),
json
.
dumps
(
data
))
return
self
.
response_usage_key
(
resp
)
class
TestDuplicateItemWithAsides
(
ItemTest
,
DuplicateHelper
):
"""
Test the duplicate method for blocks with asides.
"""
MODULESTORE
=
TEST_DATA_SPLIT_MODULESTORE
def
setUp
(
self
):
""" Creates the test course structure and a few components to 'duplicate'. """
super
(
TestDuplicateItemWithAsides
,
self
)
.
setUp
()
# Create a parent chapter
resp
=
self
.
create_xblock
(
parent_usage_key
=
self
.
usage_key
,
category
=
'chapter'
)
self
.
chapter_usage_key
=
self
.
response_usage_key
(
resp
)
# create a sequential containing a problem and an html component
resp
=
self
.
create_xblock
(
parent_usage_key
=
self
.
chapter_usage_key
,
category
=
'sequential'
)
self
.
seq_usage_key
=
self
.
response_usage_key
(
resp
)
# create problem and an html component
resp
=
self
.
create_xblock
(
parent_usage_key
=
self
.
seq_usage_key
,
category
=
'problem'
,
boilerplate
=
'multiplechoice.yaml'
)
self
.
problem_usage_key
=
self
.
response_usage_key
(
resp
)
resp
=
self
.
create_xblock
(
parent_usage_key
=
self
.
seq_usage_key
,
category
=
'html'
)
self
.
html_usage_key
=
self
.
response_usage_key
(
resp
)
@XBlockAside.register_temp_plugin
(
AsideTest
,
'test_aside'
)
@patch
(
'xmodule.modulestore.split_mongo.caching_descriptor_system.CachingDescriptorSystem.applicable_aside_types'
,
lambda
self
,
block
:
[
'test_aside'
])
def
test_duplicate_equality_with_asides
(
self
):
"""
Tests that a duplicated xblock aside is identical to the original
"""
def
create_aside
(
usage_key
,
block_type
):
"""
Helper function to create aside
"""
item
=
self
.
get_item_from_modulestore
(
usage_key
)
key_store
=
DictKeyValueStore
()
field_data
=
KvsFieldData
(
key_store
)
runtime
=
TestRuntime
(
services
=
{
'field-data'
:
field_data
})
# pylint: disable=abstract-class-instantiated
def_id
=
runtime
.
id_generator
.
create_definition
(
block_type
)
usage_id
=
runtime
.
id_generator
.
create_usage
(
def_id
)
aside
=
AsideTest
(
scope_ids
=
ScopeIds
(
'user'
,
block_type
,
def_id
,
usage_id
),
runtime
=
runtime
)
aside
.
field11
=
'
%
s_new_value11'
%
block_type
aside
.
field12
=
'
%
s_new_value12'
%
block_type
aside
.
field13
=
'
%
s_new_value13'
%
block_type
self
.
store
.
update_item
(
item
,
self
.
user
.
id
,
asides
=
[
aside
])
create_aside
(
self
.
html_usage_key
,
'html'
)
create_aside
(
self
.
problem_usage_key
,
'problem'
)
create_aside
(
self
.
seq_usage_key
,
'seq'
)
create_aside
(
self
.
chapter_usage_key
,
'chapter'
)
self
.
_duplicate_and_verify
(
self
.
problem_usage_key
,
self
.
seq_usage_key
,
check_asides
=
True
)
self
.
_duplicate_and_verify
(
self
.
html_usage_key
,
self
.
seq_usage_key
,
check_asides
=
True
)
self
.
_duplicate_and_verify
(
self
.
seq_usage_key
,
self
.
chapter_usage_key
,
check_asides
=
True
)
class
TestEditItemSetup
(
ItemTest
):
...
...
@@ -862,7 +972,8 @@ class TestEditItem(TestEditItemSetup):
data
=
{
'publish'
:
'discard_changes'
}
)
self
.
_verify_published_with_no_draft
(
self
.
problem_usage_key
)
published
=
modulestore
()
.
get_item
(
self
.
problem_usage_key
,
revision
=
ModuleStoreEnum
.
RevisionOption
.
published_only
)
published
=
modulestore
()
.
get_item
(
self
.
problem_usage_key
,
revision
=
ModuleStoreEnum
.
RevisionOption
.
published_only
)
self
.
assertIsNone
(
published
.
due
)
def
test_republish
(
self
):
...
...
@@ -969,7 +1080,8 @@ class TestEditItem(TestEditItemSetup):
data
=
{
'publish'
:
'make_public'
}
)
self
.
_verify_published_with_no_draft
(
self
.
problem_usage_key
)
published
=
modulestore
()
.
get_item
(
self
.
problem_usage_key
,
revision
=
ModuleStoreEnum
.
RevisionOption
.
published_only
)
published
=
modulestore
()
.
get_item
(
self
.
problem_usage_key
,
revision
=
ModuleStoreEnum
.
RevisionOption
.
published_only
)
# Now make a draft
self
.
client
.
ajax_post
(
...
...
@@ -1318,7 +1430,8 @@ class TestComponentHandler(TestCase):
self
.
descriptor
.
handle
=
create_response
self
.
assertEquals
(
component_handler
(
self
.
request
,
self
.
usage_key_string
,
'dummy_handler'
)
.
status_code
,
status_code
)
self
.
assertEquals
(
component_handler
(
self
.
request
,
self
.
usage_key_string
,
'dummy_handler'
)
.
status_code
,
status_code
)
class
TestComponentTemplates
(
CourseTestCase
):
...
...
@@ -1932,7 +2045,8 @@ class TestXBlockPublishingInfo(ItemTest):
if
path
:
direct_child_xblock_info
=
self
.
_get_child_xblock_info
(
xblock_info
,
path
[
0
])
remaining_path
=
path
[
1
:]
if
len
(
path
)
>
1
else
None
self
.
_verify_xblock_info_state
(
direct_child_xblock_info
,
xblock_info_field
,
expected_state
,
remaining_path
,
should_equal
)
self
.
_verify_xblock_info_state
(
direct_child_xblock_info
,
xblock_info_field
,
expected_state
,
remaining_path
,
should_equal
)
else
:
if
should_equal
:
self
.
assertEqual
(
xblock_info
[
xblock_info_field
],
expected_state
)
...
...
@@ -2102,7 +2216,8 @@ class TestXBlockPublishingInfo(ItemTest):
self
.
_create_child
(
sequential
,
'vertical'
,
"Unit"
)
self
.
_create_child
(
sequential
,
'vertical'
,
"Locked Unit"
,
staff_only
=
True
)
xblock_info
=
self
.
_get_xblock_info
(
chapter
.
location
)
self
.
_verify_visibility_state
(
xblock_info
,
VisibilityState
.
staff_only
,
self
.
FIRST_SUBSECTION_PATH
,
should_equal
=
False
)
self
.
_verify_visibility_state
(
xblock_info
,
VisibilityState
.
staff_only
,
self
.
FIRST_SUBSECTION_PATH
,
should_equal
=
False
)
self
.
_verify_visibility_state
(
xblock_info
,
VisibilityState
.
staff_only
,
self
.
FIRST_UNIT_PATH
,
should_equal
=
False
)
self
.
_verify_visibility_state
(
xblock_info
,
VisibilityState
.
staff_only
,
self
.
SECOND_UNIT_PATH
)
...
...
common/lib/xmodule/xmodule/modulestore/split_mongo/split_mongo_kvs.py
View file @
8f1a4cca
...
...
@@ -47,7 +47,7 @@ class SplitMongoKVS(InheritanceKeyValueStore):
def
get
(
self
,
key
):
if
key
.
block_family
==
XBlockAside
.
entry_point
:
if
key
.
scope
not
in
[
Scope
.
settings
,
Scope
.
content
]
:
if
key
.
scope
not
in
self
.
VALID_SCOPES
:
raise
InvalidScopeError
(
key
,
self
.
VALID_SCOPES
)
if
key
.
block_scope_id
.
block_type
not
in
self
.
aside_fields
:
...
...
@@ -139,6 +139,9 @@ class SplitMongoKVS(InheritanceKeyValueStore):
Is the given field explicitly set in this kvs (not inherited nor default)
"""
# handle any special cases
if
key
.
scope
not
in
self
.
VALID_SCOPES
:
return
False
if
key
.
scope
==
Scope
.
content
:
self
.
_load_definition
()
elif
key
.
scope
==
Scope
.
parent
:
...
...
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