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
8834bbad
Commit
8834bbad
authored
Sep 15, 2015
by
Adam Palay
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
delete_item should not delete any child that has a parent we do not intend to delete
parent
99c410dc
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
90 additions
and
52 deletions
+90
-52
cms/djangoapps/contentstore/management/commands/tests/test_delete_orphans.py
+20
-16
cms/djangoapps/contentstore/tests/test_orphan.py
+46
-28
common/lib/xmodule/xmodule/modulestore/split_mongo/split.py
+23
-7
common/lib/xmodule/xmodule/modulestore/split_mongo/split_draft.py
+1
-1
No files found.
cms/djangoapps/contentstore/management/commands/tests/test_delete_orphans.py
View file @
8834bbad
"""Tests running the delete_orphan command"""
"""Tests running the delete_orphan command"""
import
ddt
from
django.core.management
import
call_command
from
django.core.management
import
call_command
from
contentstore.tests.test_orphan
import
TestOrphanBase
from
contentstore.tests.test_orphan
import
TestOrphanBase
from
xmodule.modulestore
import
ModuleStoreEnum
@ddt.ddt
class
TestDeleteOrphan
(
TestOrphanBase
):
class
TestDeleteOrphan
(
TestOrphanBase
):
"""
"""
Tests for running the delete_orphan management command.
Tests for running the delete_orphan management command.
Inherits from TestOrphan in order to use its setUp method.
Inherits from TestOrphan in order to use its setUp method.
"""
"""
def
setUp
(
self
):
@ddt.data
(
ModuleStoreEnum
.
Type
.
split
,
ModuleStoreEnum
.
Type
.
mongo
)
super
(
TestDeleteOrphan
,
self
)
.
setUp
()
def
test_delete_orphans_no_commit
(
self
,
default_store
):
self
.
course_id
=
self
.
course
.
id
.
to_deprecated_string
()
def
test_delete_orphans_no_commit
(
self
):
"""
"""
Tests that running the command without a 'commit' argument
Tests that running the command without a 'commit' argument
results in no orphans being deleted
results in no orphans being deleted
"""
"""
call_command
(
'delete_orphans'
,
self
.
course_id
)
course
=
self
.
create_course_with_orphans
(
default_store
)
self
.
assertTrue
(
self
.
store
.
has_item
(
self
.
course
.
id
.
make_usage_key
(
'html'
,
'multi_parent_html'
)))
call_command
(
'delete_orphans'
,
unicode
(
course
.
id
))
self
.
assertTrue
(
self
.
store
.
has_item
(
self
.
course
.
id
.
make_usage_key
(
'vertical'
,
'OrphanVert'
)))
self
.
assertTrue
(
self
.
store
.
has_item
(
course
.
id
.
make_usage_key
(
'html'
,
'multi_parent_html'
)))
self
.
assertTrue
(
self
.
store
.
has_item
(
self
.
course
.
id
.
make_usage_key
(
'chapter'
,
'OrphanChapter'
)))
self
.
assertTrue
(
self
.
store
.
has_item
(
course
.
id
.
make_usage_key
(
'vertical'
,
'OrphanVert'
)))
self
.
assertTrue
(
self
.
store
.
has_item
(
self
.
course
.
id
.
make_usage_key
(
'html'
,
'OrphanHtml'
)))
self
.
assertTrue
(
self
.
store
.
has_item
(
course
.
id
.
make_usage_key
(
'chapter'
,
'OrphanChapter'
)))
self
.
assertTrue
(
self
.
store
.
has_item
(
course
.
id
.
make_usage_key
(
'html'
,
'OrphanHtml'
)))
def
test_delete_orphans_commit
(
self
):
@ddt.data
(
ModuleStoreEnum
.
Type
.
split
,
ModuleStoreEnum
.
Type
.
mongo
)
def
test_delete_orphans_commit
(
self
,
default_store
):
"""
"""
Tests that running the command WITH the 'commit' argument
Tests that running the command WITH the 'commit' argument
results in the orphans being deleted
results in the orphans being deleted
"""
"""
call_command
(
'delete_orphans'
,
self
.
course_id
,
'commit'
)
course
=
self
.
create_course_with_orphans
(
default_store
)
call_command
(
'delete_orphans'
,
unicode
(
course
.
id
),
'commit'
)
# make sure this module wasn't deleted
# make sure this module wasn't deleted
self
.
assertTrue
(
self
.
store
.
has_item
(
self
.
course
.
id
.
make_usage_key
(
'html'
,
'multi_parent_html'
)))
self
.
assertTrue
(
self
.
store
.
has_item
(
course
.
id
.
make_usage_key
(
'html'
,
'multi_parent_html'
)))
# and make sure that these were
# and make sure that these were
self
.
assertFalse
(
self
.
store
.
has_item
(
self
.
course
.
id
.
make_usage_key
(
'vertical'
,
'OrphanVert'
)))
self
.
assertFalse
(
self
.
store
.
has_item
(
course
.
id
.
make_usage_key
(
'vertical'
,
'OrphanVert'
)))
self
.
assertFalse
(
self
.
store
.
has_item
(
self
.
course
.
id
.
make_usage_key
(
'chapter'
,
'OrphanChapter'
)))
self
.
assertFalse
(
self
.
store
.
has_item
(
course
.
id
.
make_usage_key
(
'chapter'
,
'OrphanChapter'
)))
self
.
assertFalse
(
self
.
store
.
has_item
(
self
.
course
.
id
.
make_usage_key
(
'html'
,
'OrphanHtml'
)))
self
.
assertFalse
(
self
.
store
.
has_item
(
course
.
id
.
make_usage_key
(
'html'
,
'OrphanHtml'
)))
cms/djangoapps/contentstore/tests/test_orphan.py
View file @
8834bbad
...
@@ -2,27 +2,34 @@
...
@@ -2,27 +2,34 @@
Test finding orphans via the view and django config
Test finding orphans via the view and django config
"""
"""
import
json
import
json
import
ddt
from
contentstore.tests.utils
import
CourseTestCase
from
contentstore.tests.utils
import
CourseTestCase
from
student.models
import
CourseEnrollment
from
student.models
import
CourseEnrollment
from
contentstore.utils
import
reverse_course_url
from
contentstore.utils
import
reverse_course_url
from
xmodule.modulestore
import
ModuleStoreEnum
from
xmodule.modulestore.tests.factories
import
CourseFactory
class
TestOrphanBase
(
CourseTestCase
):
class
TestOrphanBase
(
CourseTestCase
):
"""
"""
Base class for Studio tests that require orphaned modules
Base class for Studio tests that require orphaned modules
"""
"""
def
setUp
(
self
):
def
create_course_with_orphans
(
self
,
default_store
):
super
(
TestOrphanBase
,
self
)
.
setUp
()
"""
Creates a course with 3 orphan modules, one of which
has a child that's also in the course tree.
"""
course
=
CourseFactory
.
create
(
default_store
=
default_store
)
# create chapters and add them to course tree
# create chapters and add them to course tree
chapter1
=
self
.
store
.
create_child
(
self
.
user
.
id
,
self
.
course
.
location
,
'chapter'
,
"Chapter1"
)
chapter1
=
self
.
store
.
create_child
(
self
.
user
.
id
,
course
.
location
,
'chapter'
,
"Chapter1"
)
self
.
store
.
publish
(
chapter1
.
location
,
self
.
user
.
id
)
self
.
store
.
publish
(
chapter1
.
location
,
self
.
user
.
id
)
chapter2
=
self
.
store
.
create_child
(
self
.
user
.
id
,
self
.
course
.
location
,
'chapter'
,
"Chapter2"
)
chapter2
=
self
.
store
.
create_child
(
self
.
user
.
id
,
course
.
location
,
'chapter'
,
"Chapter2"
)
self
.
store
.
publish
(
chapter2
.
location
,
self
.
user
.
id
)
self
.
store
.
publish
(
chapter2
.
location
,
self
.
user
.
id
)
# orphan chapter
# orphan chapter
orphan_chapter
=
self
.
store
.
create_item
(
self
.
user
.
id
,
self
.
course
.
id
,
'chapter'
,
"OrphanChapter"
)
orphan_chapter
=
self
.
store
.
create_item
(
self
.
user
.
id
,
course
.
id
,
'chapter'
,
"OrphanChapter"
)
self
.
store
.
publish
(
orphan_chapter
.
location
,
self
.
user
.
id
)
self
.
store
.
publish
(
orphan_chapter
.
location
,
self
.
user
.
id
)
# create vertical and add it as child to chapter1
# create vertical and add it as child to chapter1
...
@@ -30,7 +37,7 @@ class TestOrphanBase(CourseTestCase):
...
@@ -30,7 +37,7 @@ class TestOrphanBase(CourseTestCase):
self
.
store
.
publish
(
vertical1
.
location
,
self
.
user
.
id
)
self
.
store
.
publish
(
vertical1
.
location
,
self
.
user
.
id
)
# create orphan vertical
# create orphan vertical
orphan_vertical
=
self
.
store
.
create_item
(
self
.
user
.
id
,
self
.
course
.
id
,
'vertical'
,
"OrphanVert"
)
orphan_vertical
=
self
.
store
.
create_item
(
self
.
user
.
id
,
course
.
id
,
'vertical'
,
"OrphanVert"
)
self
.
store
.
publish
(
orphan_vertical
.
location
,
self
.
user
.
id
)
self
.
store
.
publish
(
orphan_vertical
.
location
,
self
.
user
.
id
)
# create component and add it to vertical1
# create component and add it to vertical1
...
@@ -45,61 +52,72 @@ class TestOrphanBase(CourseTestCase):
...
@@ -45,61 +52,72 @@ class TestOrphanBase(CourseTestCase):
self
.
store
.
update_item
(
orphan_vertical
,
self
.
user
.
id
)
self
.
store
.
update_item
(
orphan_vertical
,
self
.
user
.
id
)
# create an orphaned html module
# create an orphaned html module
orphan_html
=
self
.
store
.
create_item
(
self
.
user
.
id
,
self
.
course
.
id
,
'html'
,
"OrphanHtml"
)
orphan_html
=
self
.
store
.
create_item
(
self
.
user
.
id
,
course
.
id
,
'html'
,
"OrphanHtml"
)
self
.
store
.
publish
(
orphan_html
.
location
,
self
.
user
.
id
)
self
.
store
.
publish
(
orphan_html
.
location
,
self
.
user
.
id
)
self
.
store
.
create_child
(
self
.
user
.
id
,
self
.
course
.
location
,
'static_tab'
,
"staticuno"
)
self
.
store
.
create_child
(
self
.
user
.
id
,
course
.
location
,
'static_tab'
,
"staticuno"
)
self
.
store
.
create_child
(
self
.
user
.
id
,
self
.
course
.
location
,
'about'
,
"overview"
)
self
.
store
.
create_child
(
self
.
user
.
id
,
course
.
location
,
'course_info'
,
"updates"
)
self
.
store
.
create_child
(
self
.
user
.
id
,
self
.
course
.
location
,
'course_info'
,
"updates"
)
return
course
@ddt.ddt
class
TestOrphan
(
TestOrphanBase
):
class
TestOrphan
(
TestOrphanBase
):
"""
"""
Test finding orphans via view and django config
Test finding orphans via view and django config
"""
"""
def
setUp
(
self
):
super
(
TestOrphan
,
self
)
.
setUp
()
self
.
orphan_url
=
reverse_course_url
(
'orphan_handler'
,
self
.
course
.
id
)
def
test_mongo_orphan
(
self
):
@ddt.data
(
ModuleStoreEnum
.
Type
.
split
,
ModuleStoreEnum
.
Type
.
mongo
)
def
test_get_orphans
(
self
,
default_store
):
"""
"""
Test that
old mongo
finds the orphans
Test that
the orphan handler
finds the orphans
"""
"""
course
=
self
.
create_course_with_orphans
(
default_store
)
orphan_url
=
reverse_course_url
(
'orphan_handler'
,
course
.
id
)
orphans
=
json
.
loads
(
orphans
=
json
.
loads
(
self
.
client
.
get
(
self
.
client
.
get
(
self
.
orphan_url
,
orphan_url
,
HTTP_ACCEPT
=
'application/json'
HTTP_ACCEPT
=
'application/json'
)
.
content
)
.
content
)
)
self
.
assertEqual
(
len
(
orphans
),
3
,
"Wrong # {}"
.
format
(
orphans
))
self
.
assertEqual
(
len
(
orphans
),
3
,
"Wrong # {}"
.
format
(
orphans
))
location
=
self
.
course
.
location
.
replace
(
category
=
'chapter'
,
name
=
'OrphanChapter'
)
location
=
course
.
location
.
replace
(
category
=
'chapter'
,
name
=
'OrphanChapter'
)
self
.
assertIn
(
location
.
to_deprecated_string
(),
orphans
)
self
.
assertIn
(
location
.
to_deprecated_string
(),
orphans
)
location
=
self
.
course
.
location
.
replace
(
category
=
'vertical'
,
name
=
'OrphanVert'
)
location
=
course
.
location
.
replace
(
category
=
'vertical'
,
name
=
'OrphanVert'
)
self
.
assertIn
(
location
.
to_deprecated_string
(),
orphans
)
self
.
assertIn
(
location
.
to_deprecated_string
(),
orphans
)
location
=
self
.
course
.
location
.
replace
(
category
=
'html'
,
name
=
'OrphanHtml'
)
location
=
course
.
location
.
replace
(
category
=
'html'
,
name
=
'OrphanHtml'
)
self
.
assertIn
(
location
.
to_deprecated_string
(),
orphans
)
self
.
assertIn
(
location
.
to_deprecated_string
(),
orphans
)
def
test_mongo_orphan_delete
(
self
):
@ddt.data
(
ModuleStoreEnum
.
Type
.
split
,
ModuleStoreEnum
.
Type
.
mongo
)
def
test_delete_orphans
(
self
,
default_store
):
"""
"""
Test that
old mongo
deletes the orphans
Test that
the orphan handler
deletes the orphans
"""
"""
self
.
client
.
delete
(
self
.
orphan_url
)
course
=
self
.
create_course_with_orphans
(
default_store
)
orphan_url
=
reverse_course_url
(
'orphan_handler'
,
course
.
id
)
self
.
client
.
delete
(
orphan_url
)
orphans
=
json
.
loads
(
orphans
=
json
.
loads
(
self
.
client
.
get
(
self
.
orphan_url
,
HTTP_ACCEPT
=
'application/json'
)
.
content
self
.
client
.
get
(
orphan_url
,
HTTP_ACCEPT
=
'application/json'
)
.
content
)
)
self
.
assertEqual
(
len
(
orphans
),
0
,
"Orphans not deleted {}"
.
format
(
orphans
))
self
.
assertEqual
(
len
(
orphans
),
0
,
"Orphans not deleted {}"
.
format
(
orphans
))
# make sure that any children with one orphan parent and one non-orphan
# make sure that any children with one orphan parent and one non-orphan
# parent are not deleted
# parent are not deleted
self
.
assertTrue
(
self
.
store
.
has_item
(
self
.
course
.
id
.
make_usage_key
(
'html'
,
"multi_parent_html"
)))
self
.
assertTrue
(
self
.
store
.
has_item
(
course
.
id
.
make_usage_key
(
'html'
,
"multi_parent_html"
)))
def
test_not_permitted
(
self
):
@ddt.data
(
ModuleStoreEnum
.
Type
.
split
,
ModuleStoreEnum
.
Type
.
mongo
)
def
test_not_permitted
(
self
,
default_store
):
"""
"""
Test that auth restricts get and delete appropriately
Test that auth restricts get and delete appropriately
"""
"""
course
=
self
.
create_course_with_orphans
(
default_store
)
orphan_url
=
reverse_course_url
(
'orphan_handler'
,
course
.
id
)
test_user_client
,
test_user
=
self
.
create_non_staff_authed_user_client
()
test_user_client
,
test_user
=
self
.
create_non_staff_authed_user_client
()
CourseEnrollment
.
enroll
(
test_user
,
self
.
course
.
id
)
CourseEnrollment
.
enroll
(
test_user
,
course
.
id
)
response
=
test_user_client
.
get
(
self
.
orphan_url
)
response
=
test_user_client
.
get
(
orphan_url
)
self
.
assertEqual
(
response
.
status_code
,
403
)
self
.
assertEqual
(
response
.
status_code
,
403
)
response
=
test_user_client
.
delete
(
self
.
orphan_url
)
response
=
test_user_client
.
delete
(
orphan_url
)
self
.
assertEqual
(
response
.
status_code
,
403
)
self
.
assertEqual
(
response
.
status_code
,
403
)
common/lib/xmodule/xmodule/modulestore/split_mongo/split.py
View file @
8834bbad
...
@@ -2399,7 +2399,7 @@ class SplitMongoModuleStore(SplitBulkWriteMixin, ModuleStoreWriteBase):
...
@@ -2399,7 +2399,7 @@ class SplitMongoModuleStore(SplitBulkWriteMixin, ModuleStoreWriteBase):
parent_block
.
edit_info
.
source_version
=
None
parent_block
.
edit_info
.
source_version
=
None
self
.
decache_block
(
usage_locator
.
course_key
,
new_id
,
parent_block_key
)
self
.
decache_block
(
usage_locator
.
course_key
,
new_id
,
parent_block_key
)
self
.
_remove_subtree
(
BlockKey
.
from_usage_key
(
usage_locator
),
new_
blocks
)
self
.
_remove_subtree
(
BlockKey
.
from_usage_key
(
usage_locator
),
new_
structure
)
# update index if appropriate and structures
# update index if appropriate and structures
self
.
update_structure
(
usage_locator
.
course_key
,
new_structure
)
self
.
update_structure
(
usage_locator
.
course_key
,
new_structure
)
...
@@ -2416,14 +2416,30 @@ class SplitMongoModuleStore(SplitBulkWriteMixin, ModuleStoreWriteBase):
...
@@ -2416,14 +2416,30 @@ class SplitMongoModuleStore(SplitBulkWriteMixin, ModuleStoreWriteBase):
return
result
return
result
@contract
(
block_key
=
BlockKey
,
blocks
=
'dict(BlockKey: BlockData)
'
)
@contract
(
block_key
=
BlockKey
,
structure
=
'dict
'
)
def
_remove_subtree
(
self
,
block_key
,
blocks
):
def
_remove_subtree
(
self
,
block_key
,
structure
):
"""
"""
Remove the subtree rooted at block_key
Remove the subtree rooted at block_key
"""
We do this breadth-first to make sure that we don't remove
for
child
in
blocks
[
block_key
]
.
fields
.
get
(
'children'
,
[]):
any children that may have parents that we don't want to delete.
self
.
_remove_subtree
(
BlockKey
(
*
child
),
blocks
)
"""
del
blocks
[
block_key
]
to_delete
=
{
block_key
}
tier
=
{
block_key
}
while
tier
:
new_tier
=
set
()
for
block_key
in
tier
:
for
child
in
structure
[
'blocks'
][
block_key
]
.
fields
.
get
(
'children'
,
[]):
child_block_key
=
BlockKey
(
*
child
)
parents
=
self
.
_get_parents_from_structure
(
child_block_key
,
structure
)
# Make sure we want to delete all of the child's parents
# before slating it for deletion
if
all
(
parent
in
to_delete
for
parent
in
parents
):
new_tier
.
add
(
child_block_key
)
tier
=
new_tier
to_delete
.
update
(
tier
)
for
block_key
in
to_delete
:
del
structure
[
'blocks'
][
block_key
]
def
delete_course
(
self
,
course_key
,
user_id
):
def
delete_course
(
self
,
course_key
,
user_id
):
"""
"""
...
...
common/lib/xmodule/xmodule/modulestore/split_mongo/split_draft.py
View file @
8834bbad
...
@@ -410,7 +410,7 @@ class DraftVersioningModuleStore(SplitMongoModuleStore, ModuleStoreDraftAndPubli
...
@@ -410,7 +410,7 @@ class DraftVersioningModuleStore(SplitMongoModuleStore, ModuleStoreDraftAndPubli
new_structure
=
self
.
version_structure
(
draft_course_key
,
draft_course_structure
,
user_id
)
new_structure
=
self
.
version_structure
(
draft_course_key
,
draft_course_structure
,
user_id
)
# remove the block and its descendants from the new structure
# remove the block and its descendants from the new structure
self
.
_remove_subtree
(
BlockKey
.
from_usage_key
(
location
),
new_structure
[
'blocks'
]
)
self
.
_remove_subtree
(
BlockKey
.
from_usage_key
(
location
),
new_structure
)
# copy over the block and its descendants from the published branch
# copy over the block and its descendants from the published branch
def
copy_from_published
(
root_block_id
):
def
copy_from_published
(
root_block_id
):
...
...
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