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
bac223ac
Commit
bac223ac
authored
Jun 07, 2017
by
noraiz-anwar
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Enhance delete_course management command
parent
484ec256
Show whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
104 additions
and
18 deletions
+104
-18
cms/djangoapps/contentstore/management/commands/delete_course.py
+26
-2
cms/djangoapps/contentstore/management/commands/tests/test_delete_course.py
+26
-0
cms/djangoapps/contentstore/tests/test_contentstore.py
+26
-6
cms/djangoapps/contentstore/tests/test_course_listing.py
+2
-2
cms/djangoapps/contentstore/tests/test_users_default_role.py
+4
-4
cms/djangoapps/contentstore/utils.py
+20
-4
No files found.
cms/djangoapps/contentstore/management/commands/delete_course.py
View file @
bac223ac
...
...
@@ -11,7 +11,7 @@ from django.core.management.base import BaseCommand, CommandError
from
opaque_keys
import
InvalidKeyError
from
opaque_keys.edx.keys
import
CourseKey
from
contentstore.utils
import
delete_course
_and_groups
from
contentstore.utils
import
delete_course
from
xmodule.modulestore
import
ModuleStoreEnum
from
xmodule.modulestore.django
import
modulestore
...
...
@@ -21,12 +21,36 @@ from .prompt import query_yes_no
class
Command
(
BaseCommand
):
"""
Delete a MongoDB backed course
Example usage:
$ ./manage.py cms delete_course 'course-v1:edX+DemoX+Demo_Course' --settings=devstack
$ ./manage.py cms delete_course 'course-v1:edX+DemoX+Demo_Course' --keep-instructors --settings=devstack
Note:
keep-instructors option is added in effort to delete duplicate courses safely.
There happens to be courses with difference of casing in ids, for example
course-v1:DartmouthX+DART.ENGL.01.X+2016_T1 is a duplicate of course-v1:DartmouthX+DART.ENGL.01.x+2016_T1
(Note the differene in 'x' of course number). These two are independent courses in MongoDB.
Current MYSQL setup is case-insensitive which essentially means there are not
seperate entries (in all course related mysql tables, but here we are concerned about accesses)
for duplicate courses.
This option will make us able to delete course (duplicate one) from
mongo while perserving course's related access data in mysql.
"""
help
=
'''Delete a MongoDB backed course'''
def
add_arguments
(
self
,
parser
):
"""
Add arguments to the command parser.
"""
parser
.
add_argument
(
'course_key'
,
help
=
"ID of the course to delete."
)
parser
.
add_argument
(
'--keep-instructors'
,
action
=
'store_true'
,
default
=
False
,
help
=
'Do not remove permissions of users and groups for course'
,
)
def
handle
(
self
,
*
args
,
**
options
):
try
:
course_key
=
CourseKey
.
from_string
(
options
[
'course_key'
])
...
...
@@ -39,5 +63,5 @@ class Command(BaseCommand):
print
'Going to delete the
%
s course from DB....'
%
options
[
'course_key'
]
if
query_yes_no
(
"Deleting course {0}. Confirm?"
.
format
(
course_key
),
default
=
"no"
):
if
query_yes_no
(
"Are you sure. This action cannot be undone!"
,
default
=
"no"
):
delete_course
_and_groups
(
course_key
,
ModuleStoreEnum
.
UserID
.
mgmt_command
)
delete_course
(
course_key
,
ModuleStoreEnum
.
UserID
.
mgmt_command
,
options
[
'keep_instructors'
]
)
print
"Deleted course {}"
.
format
(
course_key
)
cms/djangoapps/contentstore/management/commands/tests/test_delete_course.py
View file @
bac223ac
...
...
@@ -6,9 +6,11 @@ import mock
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
from
django.core.management
import
call_command
,
CommandError
from
django.contrib.auth.models
import
User
from
contentstore.tests.utils
import
CourseTestCase
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
xmodule.modulestore.django
import
modulestore
from
student.roles
import
CourseInstructorRole
class
DeleteCourseTest
(
CourseTestCase
):
...
...
@@ -60,3 +62,27 @@ class DeleteCourseTest(CourseTestCase):
patched_yes_no
.
return_value
=
True
call_command
(
'delete_course'
,
'TestX/TS01/2015_Q1'
)
self
.
assertIsNone
(
modulestore
()
.
get_course
(
SlashSeparatedCourseKey
(
"TestX"
,
"TS01"
,
"2015_Q1"
)))
def
test_course_deletion_with_keep_instructors
(
self
):
"""
Tests that deleting course with keep-instructors option do not remove instructors from course.
"""
instructor_user
=
User
.
objects
.
create
(
username
=
'test_instructor'
,
email
=
'test_email@example.com'
)
self
.
assertIsNotNone
(
instructor_user
)
# Add and verify instructor role for the course
instructor_role
=
CourseInstructorRole
(
self
.
course
.
id
)
instructor_role
.
add_users
(
instructor_user
)
self
.
assertTrue
(
instructor_role
.
has_user
(
instructor_user
))
# Verify the course we are about to delete exists in the modulestore
self
.
assertIsNotNone
(
modulestore
()
.
get_course
(
self
.
course
.
id
))
with
mock
.
patch
(
self
.
YESNO_PATCH_LOCATION
,
return_value
=
True
):
call_command
(
'delete_course'
,
'TestX/TS01/2015_Q1'
,
'--keep-instructors'
)
self
.
assertIsNone
(
modulestore
()
.
get_course
(
self
.
course
.
id
))
self
.
assertTrue
(
instructor_role
.
has_user
(
instructor_user
))
cms/djangoapps/contentstore/tests/test_contentstore.py
View file @
bac223ac
...
...
@@ -26,7 +26,7 @@ from path import Path as path
from
common.test.utils
import
XssTestMixin
from
contentstore.tests.utils
import
AjaxEnabledTestClient
,
CourseTestCase
,
get_url
,
parse_json
from
contentstore.utils
import
delete_course
_and_groups
,
reverse_course_url
,
reverse_url
from
contentstore.utils
import
delete_course
,
reverse_course_url
,
reverse_url
from
contentstore.views.component
import
ADVANCED_COMPONENT_TYPES
from
course_action_state.managers
import
CourseActionStateItemNotFoundError
from
course_action_state.models
import
CourseRerunState
,
CourseRerunUIStateManager
...
...
@@ -1223,7 +1223,7 @@ class ContentStoreTest(ContentStoreTestCase, XssTestMixin):
test_course_data
=
self
.
assert_created_course
(
number_suffix
=
uuid4
()
.
hex
)
course_id
=
_get_course_id
(
self
.
store
,
test_course_data
)
self
.
assertTrue
(
are_permissions_roles_seeded
(
course_id
))
delete_course
_and_groups
(
course_id
,
self
.
user
.
id
)
delete_course
(
course_id
,
self
.
user
.
id
)
# should raise an exception for checking permissions on deleted course
with
self
.
assertRaises
(
ItemNotFoundError
):
are_permissions_roles_seeded
(
course_id
)
...
...
@@ -1235,7 +1235,7 @@ class ContentStoreTest(ContentStoreTestCase, XssTestMixin):
# unseed the forums for the first course
course_id
=
_get_course_id
(
self
.
store
,
test_course_data
)
delete_course
_and_groups
(
course_id
,
self
.
user
.
id
)
delete_course
(
course_id
,
self
.
user
.
id
)
# should raise an exception for checking permissions on deleted course
with
self
.
assertRaises
(
ItemNotFoundError
):
are_permissions_roles_seeded
(
course_id
)
...
...
@@ -1255,7 +1255,7 @@ class ContentStoreTest(ContentStoreTestCase, XssTestMixin):
self
.
assertTrue
(
CourseEnrollment
.
is_enrolled
(
self
.
user
,
course_id
))
self
.
assertTrue
(
self
.
user
.
roles
.
filter
(
name
=
"Student"
,
course_id
=
course_id
))
delete_course
_and_groups
(
course_id
,
self
.
user
.
id
)
delete_course
(
course_id
,
self
.
user
.
id
)
# check that user's enrollment for this course is not deleted
self
.
assertTrue
(
CourseEnrollment
.
is_enrolled
(
self
.
user
,
course_id
))
# check that user has form role "Student" for this course even after deleting it
...
...
@@ -1277,7 +1277,7 @@ class ContentStoreTest(ContentStoreTestCase, XssTestMixin):
self
.
assertGreater
(
len
(
instructor_role
.
users_with_role
()),
0
)
# Now delete course and check that user not in instructor groups of this course
delete_course
_and_groups
(
course_id
,
self
.
user
.
id
)
delete_course
(
course_id
,
self
.
user
.
id
)
# Update our cached user since its roles have changed
self
.
user
=
User
.
objects
.
get_by_natural_key
(
self
.
user
.
natural_key
()[
0
])
...
...
@@ -1285,6 +1285,26 @@ class ContentStoreTest(ContentStoreTestCase, XssTestMixin):
self
.
assertFalse
(
instructor_role
.
has_user
(
self
.
user
))
self
.
assertEqual
(
len
(
instructor_role
.
users_with_role
()),
0
)
def
test_delete_course_with_keep_instructors
(
self
):
"""
Tests that when you delete a course with 'keep_instructors',
it does not remove any permissions of users/groups from the course
"""
test_course_data
=
self
.
assert_created_course
(
number_suffix
=
uuid4
()
.
hex
)
course_id
=
_get_course_id
(
self
.
store
,
test_course_data
)
# Add and verify instructor role for the course
instructor_role
=
CourseInstructorRole
(
course_id
)
instructor_role
.
add_users
(
self
.
user
)
self
.
assertTrue
(
instructor_role
.
has_user
(
self
.
user
))
delete_course
(
course_id
,
self
.
user
.
id
,
keep_instructors
=
True
)
# Update our cached user so if any change in roles can be captured
self
.
user
=
User
.
objects
.
get_by_natural_key
(
self
.
user
.
natural_key
()[
0
])
self
.
assertTrue
(
instructor_role
.
has_user
(
self
.
user
))
def
test_create_course_after_delete
(
self
):
"""
Test that course creation works after deleting a course with the same URL
...
...
@@ -1292,7 +1312,7 @@ class ContentStoreTest(ContentStoreTestCase, XssTestMixin):
test_course_data
=
self
.
assert_created_course
()
course_id
=
_get_course_id
(
self
.
store
,
test_course_data
)
delete_course
_and_groups
(
course_id
,
self
.
user
.
id
)
delete_course
(
course_id
,
self
.
user
.
id
)
self
.
assert_created_course
()
...
...
cms/djangoapps/contentstore/tests/test_course_listing.py
View file @
bac223ac
...
...
@@ -15,7 +15,7 @@ from opaque_keys.edx.locations import CourseLocator
from
common.test.utils
import
XssTestMixin
from
contentstore.tests.utils
import
AjaxEnabledTestClient
from
contentstore.utils
import
delete_course
_and_groups
from
contentstore.utils
import
delete_course
from
contentstore.views.course
import
(
AccessListFallback
,
_accessible_courses_iter
,
...
...
@@ -298,7 +298,7 @@ class TestCourseListing(ModuleStoreTestCase, XssTestMixin):
self
.
assertEqual
(
courses_list
,
courses_list_by_groups
)
# now delete this course and re-add user to instructor group of this course
delete_course
_and_groups
(
course_key
,
self
.
user
.
id
)
delete_course
(
course_key
,
self
.
user
.
id
)
CourseInstructorRole
(
course_key
)
.
add_users
(
self
.
user
)
...
...
cms/djangoapps/contentstore/tests/test_users_default_role.py
View file @
bac223ac
...
...
@@ -3,7 +3,7 @@ Unit tests for checking default forum role "Student" of a user when he creates a
after deleting it creates same course again
"""
from
contentstore.tests.utils
import
AjaxEnabledTestClient
from
contentstore.utils
import
delete_course
_and_groups
,
reverse_url
from
contentstore.utils
import
delete_course
,
reverse_url
from
courseware.tests.factories
import
UserFactory
from
student.models
import
CourseEnrollment
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
...
...
@@ -60,7 +60,7 @@ class TestUsersDefaultRole(ModuleStoreTestCase):
# check that user has his default "Student" forum role for this course
self
.
assertTrue
(
self
.
user
.
roles
.
filter
(
name
=
"Student"
,
course_id
=
self
.
course_key
))
delete_course
_and_groups
(
self
.
course_key
,
self
.
user
.
id
)
delete_course
(
self
.
course_key
,
self
.
user
.
id
)
# check that user's enrollment for this course is not deleted
self
.
assertTrue
(
CourseEnrollment
.
is_enrolled
(
self
.
user
,
self
.
course_key
))
...
...
@@ -78,7 +78,7 @@ class TestUsersDefaultRole(ModuleStoreTestCase):
self
.
assertTrue
(
self
.
user
.
roles
.
filter
(
name
=
"Student"
,
course_id
=
self
.
course_key
))
# delete this course and recreate this course with same user
delete_course
_and_groups
(
self
.
course_key
,
self
.
user
.
id
)
delete_course
(
self
.
course_key
,
self
.
user
.
id
)
resp
=
self
.
_create_course_with_given_location
(
self
.
course_key
)
self
.
assertEqual
(
resp
.
status_code
,
200
)
...
...
@@ -96,7 +96,7 @@ class TestUsersDefaultRole(ModuleStoreTestCase):
# check that user has enrollment and his default "Student" forum role for this course
self
.
assertTrue
(
CourseEnrollment
.
is_enrolled
(
self
.
user
,
self
.
course_key
))
# delete this course and recreate this course with same user
delete_course
_and_groups
(
self
.
course_key
,
self
.
user
.
id
)
delete_course
(
self
.
course_key
,
self
.
user
.
id
)
# now create same course with different name case ('uppercase')
new_course_key
=
self
.
course_key
.
replace
(
course
=
self
.
course_key
.
course
.
upper
())
...
...
cms/djangoapps/contentstore/utils.py
View file @
bac223ac
...
...
@@ -61,18 +61,34 @@ def remove_all_instructors(course_key):
instructor_role
.
remove_users
(
*
instructor_role
.
users_with_role
())
def
delete_course
_and_groups
(
course_key
,
user_id
):
def
delete_course
(
course_key
,
user_id
,
keep_instructors
=
False
):
"""
This deletes the courseware associated with a course_key as well as cleaning update_item
the various user table stuff (groups, permissions, etc.)
Delete course from module store and if specified remove user and
groups permissions from course.
"""
_delete_course_from_modulestore
(
course_key
,
user_id
)
if
not
keep_instructors
:
_remove_instructors
(
course_key
)
def
_delete_course_from_modulestore
(
course_key
,
user_id
):
"""
Delete course from MongoDB. Deleting course will fire a signal which will result into
deletion of the courseware associated with a course_key.
"""
module_store
=
modulestore
()
with
module_store
.
bulk_operations
(
course_key
):
module_store
.
delete_course
(
course_key
,
user_id
)
def
_remove_instructors
(
course_key
):
"""
In the django layer, remove all the user/groups permissions associated with this course
"""
print
'removing User permissions from course....'
# in the django layer, we need to remove all the user permissions groups associated with this course
try
:
remove_all_instructors
(
course_key
)
except
Exception
as
err
:
...
...
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