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
b40a8a3e
Commit
b40a8a3e
authored
Dec 20, 2012
by
Chris Dodge
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
implement course clone and delete functionality from the command line
parent
3ae08d10
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
274 additions
and
3 deletions
+274
-3
cms/djangoapps/auth/authz.py
+16
-0
cms/djangoapps/contentstore/management/commands/clone.py
+31
-0
cms/djangoapps/contentstore/management/commands/delete_course.py
+36
-0
cms/djangoapps/contentstore/management/commands/prompt.py
+34
-0
common/lib/xmodule/xmodule/contentstore/mongo.py
+5
-3
common/lib/xmodule/xmodule/modulestore/store_utilities.py
+130
-0
rakefile
+22
-0
No files found.
cms/djangoapps/auth/authz.py
View file @
b40a8a3e
...
@@ -55,6 +55,22 @@ def create_new_course_group(creator, location, role):
...
@@ -55,6 +55,22 @@ def create_new_course_group(creator, location, role):
return
return
'''
This is to be called only by either a command line code path or through a app which has already
asserted permissions
'''
def
_delete_course_group
(
location
):
# remove all memberships
instructors
=
Group
.
objects
.
get
(
name
=
get_course_groupname_for_role
(
location
,
INSTRUCTOR_ROLE_NAME
))
for
user
in
instructors
.
user_set
.
all
():
user
.
groups
.
remove
(
instructors
)
user
.
save
()
staff
=
Group
.
objects
.
get
(
name
=
get_course_groupname_for_role
(
location
,
STAFF_ROLE_NAME
))
for
user
in
staff
.
user_set
.
all
():
user
.
groups
.
remove
(
staff
)
user
.
save
()
def
add_user_to_course_group
(
caller
,
user
,
location
,
role
):
def
add_user_to_course_group
(
caller
,
user
,
location
,
role
):
# only admins can add/remove other users
# only admins can add/remove other users
...
...
cms/djangoapps/contentstore/management/commands/clone.py
0 → 100644
View file @
b40a8a3e
###
### Script for cloning a course
###
from
django.core.management.base
import
BaseCommand
,
CommandError
from
xmodule.modulestore.store_utilities
import
clone_course
from
xmodule.modulestore.django
import
modulestore
from
xmodule.contentstore.django
import
contentstore
from
xmodule.modulestore
import
Location
from
xmodule.course_module
import
CourseDescriptor
#
# To run from command line: rake cms:clone SOURCE_LOC=MITx/111/Foo1 DEST_LOC=MITx/135/Foo3
#
class
Command
(
BaseCommand
):
help
=
\
'''Clone a MongoDB backed course to another location'''
def
handle
(
self
,
*
args
,
**
options
):
if
len
(
args
)
!=
2
:
raise
CommandError
(
"clone requires two arguments: <source-location> <dest-location>"
)
source_location_str
=
args
[
0
]
dest_location_str
=
args
[
1
]
print
"Cloning course {0} to {1}"
.
format
(
source_location_str
,
dest_location_str
)
source_location
=
CourseDescriptor
.
id_to_location
(
source_location_str
)
dest_location
=
CourseDescriptor
.
id_to_location
(
dest_location_str
)
clone_course
(
modulestore
(
'direct'
),
contentstore
(),
source_location
,
dest_location
)
cms/djangoapps/contentstore/management/commands/delete_course.py
0 → 100644
View file @
b40a8a3e
###
### Script for cloning a course
###
from
django.core.management.base
import
BaseCommand
,
CommandError
from
xmodule.modulestore.store_utilities
import
delete_course
from
xmodule.modulestore.django
import
modulestore
from
xmodule.contentstore.django
import
contentstore
from
xmodule.modulestore
import
Location
from
xmodule.course_module
import
CourseDescriptor
from
prompt
import
query_yes_no
from
auth.authz
import
_delete_course_group
#
# To run from command line: rake cms:delete_course LOC=MITx/111/Foo1
#
class
Command
(
BaseCommand
):
help
=
\
'''Delete a MongoDB backed course'''
def
handle
(
self
,
*
args
,
**
options
):
if
len
(
args
)
!=
1
:
raise
CommandError
(
"delete_course requires one arguments: <location>"
)
loc_str
=
args
[
0
]
if
query_yes_no
(
"Deleting course {0}. Confirm?"
.
format
(
loc_str
),
default
=
"no"
):
if
query_yes_no
(
"Are you sure. This action cannot be undone!"
,
default
=
"no"
):
loc
=
CourseDescriptor
.
id_to_location
(
loc_str
)
if
delete_course
(
modulestore
(
'direct'
),
contentstore
(),
loc
)
==
True
:
# in the django layer, we need to remove all the user permissions groups associated with this course
_delete_course_group
(
loc
)
cms/djangoapps/contentstore/management/commands/prompt.py
0 → 100644
View file @
b40a8a3e
import
sys
def
query_yes_no
(
question
,
default
=
"yes"
):
"""Ask a yes/no question via raw_input() and return their answer.
"question" is a string that is presented to the user.
"default" is the presumed answer if the user just hits <Enter>.
It must be "yes" (the default), "no" or None (meaning
an answer is required of the user).
The "answer" return value is one of "yes" or "no".
"""
valid
=
{
"yes"
:
True
,
"y"
:
True
,
"ye"
:
True
,
"no"
:
False
,
"n"
:
False
}
if
default
==
None
:
prompt
=
" [y/n] "
elif
default
==
"yes"
:
prompt
=
" [Y/n] "
elif
default
==
"no"
:
prompt
=
" [y/N] "
else
:
raise
ValueError
(
"invalid default answer: '
%
s'"
%
default
)
while
True
:
sys
.
stdout
.
write
(
question
+
prompt
)
choice
=
raw_input
()
.
lower
()
if
default
is
not
None
and
choice
==
''
:
return
valid
[
default
]
elif
choice
in
valid
:
return
valid
[
choice
]
else
:
sys
.
stdout
.
write
(
"Please respond with 'yes' or 'no' "
\
"(or 'y' or 'n').
\n
"
)
\ No newline at end of file
common/lib/xmodule/xmodule/contentstore/mongo.py
View file @
b40a8a3e
...
@@ -29,8 +29,7 @@ class MongoContentStore(ContentStore):
...
@@ -29,8 +29,7 @@ class MongoContentStore(ContentStore):
id
=
content
.
get_id
()
id
=
content
.
get_id
()
# Seems like with the GridFS we can't update existing ID's we have to do a delete/add pair
# Seems like with the GridFS we can't update existing ID's we have to do a delete/add pair
if
self
.
fs
.
exists
({
"_id"
:
id
}):
self
.
delete
(
id
)
self
.
fs
.
delete
(
id
)
with
self
.
fs
.
new_file
(
_id
=
id
,
filename
=
content
.
get_url_path
(),
content_type
=
content
.
content_type
,
with
self
.
fs
.
new_file
(
_id
=
id
,
filename
=
content
.
get_url_path
(),
content_type
=
content
.
content_type
,
displayname
=
content
.
name
,
thumbnail_location
=
content
.
thumbnail_location
)
as
fp
:
displayname
=
content
.
name
,
thumbnail_location
=
content
.
thumbnail_location
)
as
fp
:
...
@@ -39,7 +38,10 @@ class MongoContentStore(ContentStore):
...
@@ -39,7 +38,10 @@ class MongoContentStore(ContentStore):
return
content
return
content
def
delete
(
self
,
id
):
if
self
.
fs
.
exists
({
"_id"
:
id
}):
self
.
fs
.
delete
(
id
)
def
find
(
self
,
location
):
def
find
(
self
,
location
):
id
=
StaticContent
.
get_id_from_location
(
location
)
id
=
StaticContent
.
get_id_from_location
(
location
)
try
:
try
:
...
...
common/lib/xmodule/xmodule/modulestore/store_utilities.py
0 → 100644
View file @
b40a8a3e
import
logging
from
xmodule.contentstore.content
import
StaticContent
from
xmodule.modulestore
import
Location
from
xmodule.modulestore.mongo
import
MongoModuleStore
def
clone_course
(
modulestore
,
contentstore
,
source_location
,
dest_location
,
delete_original
=
False
):
# first check to see if the modulestore is Mongo backed
if
not
isinstance
(
modulestore
,
MongoModuleStore
):
raise
Exception
(
"Expected a MongoModuleStore in the runtime. Aborting...."
)
# check to see if the dest_location exists as an empty course
# we need an empty course because the app layers manage the permissions and users
if
not
modulestore
.
has_item
(
dest_location
):
raise
Exception
(
"An empty course at {0} must have already been created. Aborting..."
.
format
(
dest_location
))
# verify that the dest_location really is an empty course, which means only one
dest_modules
=
modulestore
.
get_items
([
dest_location
.
tag
,
dest_location
.
org
,
dest_location
.
course
,
None
,
None
,
None
])
if
len
(
dest_modules
)
!=
1
:
raise
Exception
(
"Course at destination {0} is not an empty course. You can only clone into an empty course. Aborting..."
.
format
(
dest_location
))
# check to see if the source course is actually there
if
not
modulestore
.
has_item
(
source_location
):
raise
Exception
(
"Cannot find a course at {0}. Aborting"
.
format
(
source_location
))
# Get all modules under this namespace which is (tag, org, course) tuple
modules
=
modulestore
.
get_items
([
source_location
.
tag
,
source_location
.
org
,
source_location
.
course
,
None
,
None
,
None
])
for
module
in
modules
:
original_loc
=
Location
(
module
.
location
)
if
original_loc
.
category
!=
'course'
:
module
.
location
=
module
.
location
.
_replace
(
tag
=
dest_location
.
tag
,
org
=
dest_location
.
org
,
course
=
dest_location
.
course
)
else
:
# on the course module we also have to update the module name
module
.
location
=
module
.
location
.
_replace
(
tag
=
dest_location
.
tag
,
org
=
dest_location
.
org
,
course
=
dest_location
.
course
,
name
=
dest_location
.
name
)
print
"Cloning module {0} to {1}...."
.
format
(
original_loc
,
module
.
location
)
if
'data'
in
module
.
definition
:
modulestore
.
update_item
(
module
.
location
,
module
.
definition
[
'data'
])
# repoint children
if
'children'
in
module
.
definition
:
new_children
=
[]
for
child_loc_url
in
module
.
definition
[
'children'
]:
child_loc
=
Location
(
child_loc_url
)
child_loc
=
child_loc
.
_replace
(
tag
=
dest_location
.
tag
,
org
=
dest_location
.
org
,
course
=
dest_location
.
course
)
new_children
=
new_children
+
[
child_loc
.
url
()]
modulestore
.
update_children
(
module
.
location
,
new_children
)
# save metadata
modulestore
.
update_metadata
(
module
.
location
,
module
.
metadata
)
# now iterate through all of the assets and clone them
# first the thumbnails
thumbs
=
contentstore
.
get_all_content_thumbnails_for_course
(
source_location
)
for
thumb
in
thumbs
:
thumb_loc
=
Location
(
thumb
[
"_id"
])
content
=
contentstore
.
find
(
thumb_loc
)
content
.
location
=
content
.
location
.
_replace
(
org
=
dest_location
.
org
,
course
=
dest_location
.
course
)
print
"Cloning thumbnail {0} to {1}"
.
format
(
thumb_loc
,
content
.
location
)
contentstore
.
save
(
content
)
# now iterate through all of the assets, also updating the thumbnail pointer
assets
=
contentstore
.
get_all_content_for_course
(
source_location
)
for
asset
in
assets
:
asset_loc
=
Location
(
asset
[
"_id"
])
content
=
contentstore
.
find
(
asset_loc
)
content
.
location
=
content
.
location
.
_replace
(
org
=
dest_location
.
org
,
course
=
dest_location
.
course
)
# be sure to update the pointer to the thumbnail
if
content
.
thumbnail_location
is
not
None
:
content
.
thumbnail_location
.
_replace
(
tag
=
dest_location
.
tag
,
org
=
dest_location
.
org
,
course
=
dest_location
.
course
)
print
"Cloning asset {0} to {1}"
.
format
(
asset_loc
,
content
.
location
)
contentstore
.
save
(
content
)
def
delete_course
(
modulestore
,
contentstore
,
source_location
):
# first check to see if the modulestore is Mongo backed
if
not
isinstance
(
modulestore
,
MongoModuleStore
):
raise
Exception
(
"Expected a MongoModuleStore in the runtime. Aborting...."
)
# check to see if the source course is actually there
if
not
modulestore
.
has_item
(
source_location
):
raise
Exception
(
"Cannot find a course at {0}. Aborting"
.
format
(
source_location
))
# first delete all of the thumbnails
thumbs
=
contentstore
.
get_all_content_thumbnails_for_course
(
source_location
)
for
thumb
in
thumbs
:
thumb_loc
=
Location
(
thumb
[
"_id"
])
id
=
StaticContent
.
get_id_from_location
(
thumb_loc
)
print
"Deleting {0}..."
.
format
(
id
)
contentstore
.
delete
(
id
)
# then delete all of the assets
assets
=
contentstore
.
get_all_content_for_course
(
source_location
)
for
asset
in
assets
:
asset_loc
=
Location
(
asset
[
"_id"
])
id
=
StaticContent
.
get_id_from_location
(
asset_loc
)
print
"Deleting {0}..."
.
format
(
id
)
contentstore
.
delete
(
id
)
# then delete all course modules
modules
=
modulestore
.
get_items
([
source_location
.
tag
,
source_location
.
org
,
source_location
.
course
,
None
,
None
,
None
])
for
module
in
modules
:
if
module
.
category
!=
'course'
:
# save deleting the course module for last
print
"Deleting {0}..."
.
format
(
module
.
location
)
modulestore
.
delete_item
(
module
.
location
)
# finally delete the top-level course module itself
print
"Deleting {0}..."
.
format
(
source_location
)
modulestore
.
delete_item
(
source_location
)
return
True
\ No newline at end of file
rakefile
View file @
b40a8a3e
...
@@ -397,6 +397,28 @@ task :publish => :package do
...
@@ -397,6 +397,28 @@ task :publish => :package do
end
end
namespace
:cms
do
namespace
:cms
do
desc
"Clone existing MongoDB based course"
task
:clone
do
if
ENV
[
'SOURCE_LOC'
]
and
ENV
[
'DEST_LOC'
]
sh
(
django_admin
(
:cms
,
:dev
,
:clone
,
ENV
[
'SOURCE_LOC'
],
ENV
[
'DEST_LOC'
]))
else
raise
"You must pass in a SOURCE_LOC and DEST_LOC parameters"
end
end
end
namespace
:cms
do
desc
"Delete existing MongoDB based course"
task
:delete_course
do
if
ENV
[
'LOC'
]
sh
(
django_admin
(
:cms
,
:dev
,
:delete_course
,
ENV
[
'LOC'
]))
else
raise
"You must pass in a LOC parameter"
end
end
end
namespace
:cms
do
desc
"Import course data within the given DATA_DIR variable"
desc
"Import course data within the given DATA_DIR variable"
task
:import
do
task
:import
do
if
ENV
[
'DATA_DIR'
]
if
ENV
[
'DATA_DIR'
]
...
...
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