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
9efe5d92
Commit
9efe5d92
authored
Aug 08, 2014
by
Nimisha Asthagiri
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #4720 from edx/split/add-and-fixes
Split/add and fixes
parents
26b08a02
d2b59cb6
Show whitespace changes
Inline
Side-by-side
Showing
43 changed files
with
876 additions
and
386 deletions
+876
-386
cms/djangoapps/contentstore/features/video.py
+1
-1
cms/djangoapps/contentstore/management/commands/check_course.py
+1
-1
cms/djangoapps/contentstore/tests/test_contentstore.py
+73
-41
cms/djangoapps/contentstore/tests/test_course_settings.py
+1
-1
cms/djangoapps/contentstore/tests/test_import.py
+2
-1
cms/djangoapps/contentstore/tests/utils.py
+1
-1
cms/djangoapps/contentstore/views/assets.py
+8
-2
cms/djangoapps/contentstore/views/course.py
+32
-18
cms/djangoapps/contentstore/views/item.py
+5
-0
cms/djangoapps/contentstore/views/tasks.py
+26
-2
cms/envs/bok_choy.auth.json
+0
-1
cms/envs/common.py
+3
-0
common/djangoapps/cache_toolbox/core.py
+17
-6
common/djangoapps/contentserver/middleware.py
+6
-2
common/djangoapps/user_api/middleware.py
+2
-2
common/djangoapps/xmodule_django/models.py
+36
-4
common/lib/xmodule/xmodule/contentstore/content.py
+16
-11
common/lib/xmodule/xmodule/contentstore/mongo.py
+12
-4
common/lib/xmodule/xmodule/modulestore/__init__.py
+26
-15
common/lib/xmodule/xmodule/modulestore/mixed.py
+157
-49
common/lib/xmodule/xmodule/modulestore/modulestore_settings.py
+22
-1
common/lib/xmodule/xmodule/modulestore/mongo/base.py
+35
-21
common/lib/xmodule/xmodule/modulestore/mongo/draft.py
+5
-10
common/lib/xmodule/xmodule/modulestore/split_migrator.py
+24
-15
common/lib/xmodule/xmodule/modulestore/split_mongo/caching_descriptor_system.py
+8
-3
common/lib/xmodule/xmodule/modulestore/split_mongo/split.py
+50
-34
common/lib/xmodule/xmodule/modulestore/split_mongo/split_draft.py
+50
-45
common/lib/xmodule/xmodule/modulestore/split_mongo/split_mongo_kvs.py
+17
-10
common/lib/xmodule/xmodule/modulestore/tests/test_contentstore.py
+13
-12
common/lib/xmodule/xmodule/modulestore/tests/test_mixed_modulestore.py
+107
-33
common/lib/xmodule/xmodule/modulestore/tests/test_modulestore_settings.py
+70
-9
common/lib/xmodule/xmodule/modulestore/tests/test_split_migrator.py
+1
-1
common/lib/xmodule/xmodule/modulestore/tests/test_split_modulestore.py
+6
-6
common/lib/xmodule/xmodule/modulestore/xml.py
+22
-12
common/lib/xmodule/xmodule/modulestore/xml_exporter.py
+2
-2
common/lib/xmodule/xmodule/modulestore/xml_importer.py
+3
-2
lms/djangoapps/courseware/tests/test_draft_modulestore.py
+1
-1
lms/djangoapps/courseware/tests/tests.py
+1
-1
lms/djangoapps/courseware/views.py
+2
-2
lms/djangoapps/django_comment_client/utils.py
+1
-1
lms/djangoapps/open_ended_grading/views.py
+1
-1
lms/envs/bok_choy.auth.json
+0
-1
lms/envs/common.py
+10
-1
No files found.
cms/djangoapps/contentstore/features/video.py
View file @
9efe5d92
...
@@ -138,7 +138,7 @@ def xml_only_video(step):
...
@@ -138,7 +138,7 @@ def xml_only_video(step):
course
=
world
.
scenario_dict
[
'COURSE'
]
course
=
world
.
scenario_dict
[
'COURSE'
]
store
=
modulestore
()
store
=
modulestore
()
parent_location
=
store
.
get_items
(
course
.
id
,
category
=
'vertical'
)[
0
]
.
location
parent_location
=
store
.
get_items
(
course
.
id
,
qualifiers
=
{
'category'
:
'vertical'
}
)[
0
]
.
location
youtube_id
=
'ABCDEFG'
youtube_id
=
'ABCDEFG'
world
.
scenario_dict
[
'YOUTUBE_ID'
]
=
youtube_id
world
.
scenario_dict
[
'YOUTUBE_ID'
]
=
youtube_id
...
...
cms/djangoapps/contentstore/management/commands/check_course.py
View file @
9efe5d92
...
@@ -58,7 +58,7 @@ class Command(BaseCommand):
...
@@ -58,7 +58,7 @@ class Command(BaseCommand):
discussion_items
=
_get_discussion_items
(
course
)
discussion_items
=
_get_discussion_items
(
course
)
# now query all discussion items via get_items() and compare with the tree-traversal
# now query all discussion items via get_items() and compare with the tree-traversal
queried_discussion_items
=
store
.
get_items
(
course_key
=
course_key
,
category
=
'discussion'
,
)
queried_discussion_items
=
store
.
get_items
(
course_key
=
course_key
,
qualifiers
=
{
'category'
:
'discussion'
}
)
for
item
in
queried_discussion_items
:
for
item
in
queried_discussion_items
:
if
item
.
location
not
in
discussion_items
:
if
item
.
location
not
in
discussion_items
:
...
...
cms/djangoapps/contentstore/tests/test_contentstore.py
View file @
9efe5d92
...
@@ -28,7 +28,7 @@ from xmodule.exceptions import NotFoundError, InvalidVersionError
...
@@ -28,7 +28,7 @@ from xmodule.exceptions import NotFoundError, InvalidVersionError
from
xmodule.modulestore
import
ModuleStoreEnum
from
xmodule.modulestore
import
ModuleStoreEnum
from
xmodule.modulestore.exceptions
import
ItemNotFoundError
from
xmodule.modulestore.exceptions
import
ItemNotFoundError
from
xmodule.modulestore.inheritance
import
own_metadata
from
xmodule.modulestore.inheritance
import
own_metadata
from
opaque_keys.edx.keys
import
UsageKey
from
opaque_keys.edx.keys
import
UsageKey
,
CourseKey
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
,
AssetLocation
,
CourseLocator
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
,
AssetLocation
,
CourseLocator
from
xmodule.modulestore.tests.factories
import
CourseFactory
,
ItemFactory
from
xmodule.modulestore.tests.factories
import
CourseFactory
,
ItemFactory
from
xmodule.modulestore.xml_exporter
import
export_to_xml
from
xmodule.modulestore.xml_exporter
import
export_to_xml
...
@@ -47,6 +47,7 @@ from student.roles import CourseCreatorRole, CourseInstructorRole
...
@@ -47,6 +47,7 @@ from student.roles import CourseCreatorRole, CourseInstructorRole
from
opaque_keys
import
InvalidKeyError
from
opaque_keys
import
InvalidKeyError
from
contentstore.tests.utils
import
get_url
from
contentstore.tests.utils
import
get_url
from
course_action_state.models
import
CourseRerunState
,
CourseRerunUIStateManager
from
course_action_state.models
import
CourseRerunState
,
CourseRerunUIStateManager
from
course_action_state.managers
import
CourseActionStateItemNotFoundError
TEST_DATA_CONTENTSTORE
=
copy
.
deepcopy
(
settings
.
CONTENTSTORE
)
TEST_DATA_CONTENTSTORE
=
copy
.
deepcopy
(
settings
.
CONTENTSTORE
)
...
@@ -94,7 +95,7 @@ class ContentStoreToyCourseTest(ContentStoreTestCase):
...
@@ -94,7 +95,7 @@ class ContentStoreToyCourseTest(ContentStoreTestCase):
store
.
update_item
(
course
,
self
.
user
.
id
)
store
.
update_item
(
course
,
self
.
user
.
id
)
# just pick one vertical
# just pick one vertical
descriptor
=
store
.
get_items
(
course
.
id
,
category
=
'vertical'
,
)
descriptor
=
store
.
get_items
(
course
.
id
,
qualifiers
=
{
'category'
:
'vertical'
}
)
resp
=
self
.
client
.
get_html
(
get_url
(
'container_handler'
,
descriptor
[
0
]
.
location
))
resp
=
self
.
client
.
get_html
(
get_url
(
'container_handler'
,
descriptor
[
0
]
.
location
))
self
.
assertEqual
(
resp
.
status_code
,
200
)
self
.
assertEqual
(
resp
.
status_code
,
200
)
...
@@ -127,7 +128,7 @@ class ContentStoreToyCourseTest(ContentStoreTestCase):
...
@@ -127,7 +128,7 @@ class ContentStoreToyCourseTest(ContentStoreTestCase):
"""Verifies the editing HTML in all the verticals in the given test course"""
"""Verifies the editing HTML in all the verticals in the given test course"""
_
,
course_items
=
import_from_xml
(
self
.
store
,
self
.
user
.
id
,
'common/test/data/'
,
[
test_course_name
])
_
,
course_items
=
import_from_xml
(
self
.
store
,
self
.
user
.
id
,
'common/test/data/'
,
[
test_course_name
])
items
=
self
.
store
.
get_items
(
course_items
[
0
]
.
id
,
category
=
'vertical'
)
items
=
self
.
store
.
get_items
(
course_items
[
0
]
.
id
,
qualifiers
=
{
'category'
:
'vertical'
}
)
self
.
_check_verticals
(
items
)
self
.
_check_verticals
(
items
)
def
test_edit_unit_toy
(
self
):
def
test_edit_unit_toy
(
self
):
...
@@ -289,7 +290,7 @@ class ContentStoreToyCourseTest(ContentStoreTestCase):
...
@@ -289,7 +290,7 @@ class ContentStoreToyCourseTest(ContentStoreTestCase):
_
,
course_items
=
import_from_xml
(
self
.
store
,
self
.
user
.
id
,
'common/test/data/'
,
[
'toy'
])
_
,
course_items
=
import_from_xml
(
self
.
store
,
self
.
user
.
id
,
'common/test/data/'
,
[
'toy'
])
course_key
=
course_items
[
0
]
.
id
course_key
=
course_items
[
0
]
.
id
items
=
self
.
store
.
get_items
(
course_key
,
category
=
'poll_question'
)
items
=
self
.
store
.
get_items
(
course_key
,
qualifiers
=
{
'category'
:
'poll_question'
}
)
found
=
len
(
items
)
>
0
found
=
len
(
items
)
>
0
self
.
assertTrue
(
found
)
self
.
assertTrue
(
found
)
...
@@ -643,7 +644,7 @@ class ContentStoreToyCourseTest(ContentStoreTestCase):
...
@@ -643,7 +644,7 @@ class ContentStoreToyCourseTest(ContentStoreTestCase):
filesystem
=
OSFS
(
root_dir
/
'test_export'
)
filesystem
=
OSFS
(
root_dir
/
'test_export'
)
self
.
assertTrue
(
filesystem
.
exists
(
dirname
))
self
.
assertTrue
(
filesystem
.
exists
(
dirname
))
items
=
store
.
get_items
(
course_id
,
category
=
category_name
)
items
=
store
.
get_items
(
course_id
,
qualifiers
=
{
'category'
:
category_name
}
)
for
item
in
items
:
for
item
in
items
:
filesystem
=
OSFS
(
root_dir
/
(
'test_export/'
+
dirname
))
filesystem
=
OSFS
(
root_dir
/
(
'test_export/'
+
dirname
))
...
@@ -742,7 +743,7 @@ class ContentStoreToyCourseTest(ContentStoreTestCase):
...
@@ -742,7 +743,7 @@ class ContentStoreToyCourseTest(ContentStoreTestCase):
# create a new video module and add it as a child to a vertical
# create a new video module and add it as a child to a vertical
# this re-creates a bug whereby since the video template doesn't have
# this re-creates a bug whereby since the video template doesn't have
# anything in 'data' field, the export was blowing up
# anything in 'data' field, the export was blowing up
verticals
=
self
.
store
.
get_items
(
course_id
,
category
=
'vertical'
)
verticals
=
self
.
store
.
get_items
(
course_id
,
qualifiers
=
{
'category'
:
'vertical'
}
)
self
.
assertGreater
(
len
(
verticals
),
0
)
self
.
assertGreater
(
len
(
verticals
),
0
)
...
@@ -768,7 +769,7 @@ class ContentStoreToyCourseTest(ContentStoreTestCase):
...
@@ -768,7 +769,7 @@ class ContentStoreToyCourseTest(ContentStoreTestCase):
import_from_xml
(
self
.
store
,
self
.
user
.
id
,
'common/test/data/'
,
[
'word_cloud'
])
import_from_xml
(
self
.
store
,
self
.
user
.
id
,
'common/test/data/'
,
[
'word_cloud'
])
course_id
=
SlashSeparatedCourseKey
(
'HarvardX'
,
'ER22x'
,
'2013_Spring'
)
course_id
=
SlashSeparatedCourseKey
(
'HarvardX'
,
'ER22x'
,
'2013_Spring'
)
verticals
=
self
.
store
.
get_items
(
course_id
,
category
=
'vertical'
)
verticals
=
self
.
store
.
get_items
(
course_id
,
qualifiers
=
{
'category'
:
'vertical'
}
)
self
.
assertGreater
(
len
(
verticals
),
0
)
self
.
assertGreater
(
len
(
verticals
),
0
)
...
@@ -795,7 +796,7 @@ class ContentStoreToyCourseTest(ContentStoreTestCase):
...
@@ -795,7 +796,7 @@ class ContentStoreToyCourseTest(ContentStoreTestCase):
import_from_xml
(
self
.
store
,
self
.
user
.
id
,
'common/test/data/'
,
[
'toy'
])
import_from_xml
(
self
.
store
,
self
.
user
.
id
,
'common/test/data/'
,
[
'toy'
])
course_id
=
SlashSeparatedCourseKey
(
'edX'
,
'toy'
,
'2012_Fall'
)
course_id
=
SlashSeparatedCourseKey
(
'edX'
,
'toy'
,
'2012_Fall'
)
verticals
=
self
.
store
.
get_items
(
course_id
,
category
=
'vertical'
)
verticals
=
self
.
store
.
get_items
(
course_id
,
qualifiers
=
{
'category'
:
'vertical'
}
)
self
.
assertGreater
(
len
(
verticals
),
0
)
self
.
assertGreater
(
len
(
verticals
),
0
)
...
@@ -916,8 +917,10 @@ class ContentStoreToyCourseTest(ContentStoreTestCase):
...
@@ -916,8 +917,10 @@ class ContentStoreToyCourseTest(ContentStoreTestCase):
items
=
self
.
store
.
get_items
(
items
=
self
.
store
.
get_items
(
course_id
,
course_id
,
category
=
'sequential'
,
qualifiers
=
{
name
=
'vertical_sequential'
'category'
:
'sequential'
,
'name'
:
'vertical_sequential'
,
}
)
)
self
.
assertEqual
(
len
(
items
),
1
)
self
.
assertEqual
(
len
(
items
),
1
)
...
@@ -1115,8 +1118,7 @@ class ContentStoreTest(ContentStoreTestCase):
...
@@ -1115,8 +1118,7 @@ class ContentStoreTest(ContentStoreTestCase):
def
test_create_course_with_bad_organization
(
self
):
def
test_create_course_with_bad_organization
(
self
):
"""Test new course creation - error path for bad organization name"""
"""Test new course creation - error path for bad organization name"""
self
.
course_data
[
'org'
]
=
'University of California, Berkeley'
self
.
course_data
[
'org'
]
=
'University of California, Berkeley'
self
.
assert_course_creation_failed
(
self
.
assert_course_creation_failed
(
r"(?s)Unable to create course 'Robot Super Course'.*"
)
r"(?s)Unable to create course 'Robot Super Course'.*: Invalid characters in u'University of California, Berkeley'"
)
def
test_create_course_with_course_creation_disabled_staff
(
self
):
def
test_create_course_with_course_creation_disabled_staff
(
self
):
"""Test new course creation -- course creation disabled, but staff access."""
"""Test new course creation -- course creation disabled, but staff access."""
...
@@ -1401,7 +1403,7 @@ class ContentStoreTest(ContentStoreTestCase):
...
@@ -1401,7 +1403,7 @@ class ContentStoreTest(ContentStoreTestCase):
_
,
course_items
=
import_from_xml
(
self
.
store
,
self
.
user
.
id
,
'common/test/data/'
,
[
'toy'
])
_
,
course_items
=
import_from_xml
(
self
.
store
,
self
.
user
.
id
,
'common/test/data/'
,
[
'toy'
])
course
=
course_items
[
0
]
course
=
course_items
[
0
]
verticals
=
self
.
store
.
get_items
(
course
.
id
,
category
=
'vertical'
)
verticals
=
self
.
store
.
get_items
(
course
.
id
,
qualifiers
=
{
'category'
:
'vertical'
}
)
# let's assert on the metadata_inheritance on an existing vertical
# let's assert on the metadata_inheritance on an existing vertical
for
vertical
in
verticals
:
for
vertical
in
verticals
:
...
@@ -1572,31 +1574,35 @@ class RerunCourseTest(ContentStoreTestCase):
...
@@ -1572,31 +1574,35 @@ class RerunCourseTest(ContentStoreTestCase):
'display_name'
:
'Robot Super Course'
,
'display_name'
:
'Robot Super Course'
,
'run'
:
'2013_Spring'
'run'
:
'2013_Spring'
}
}
self
.
destination_course_key
=
_get_course_id
(
self
.
destination_course_data
)
def
post_rerun_request
(
self
,
source_course_key
,
response_code
=
200
):
def
post_rerun_request
(
self
,
source_course_key
,
destination_course_data
=
None
,
response_code
=
200
,
expect_error
=
False
):
"""Create and send an ajax post for the rerun request"""
"""Create and send an ajax post for the rerun request"""
# create data to post
# create data to post
rerun_course_data
=
{
'source_course_key'
:
unicode
(
source_course_key
)}
rerun_course_data
=
{
'source_course_key'
:
unicode
(
source_course_key
)}
rerun_course_data
.
update
(
self
.
destination_course_data
)
if
not
destination_course_data
:
destination_course_data
=
self
.
destination_course_data
rerun_course_data
.
update
(
destination_course_data
)
destination_course_key
=
_get_course_id
(
destination_course_data
)
# post the request
# post the request
course_url
=
get_url
(
'course_handler'
,
self
.
destination_course_key
,
'course_key_string'
)
course_url
=
get_url
(
'course_handler'
,
destination_course_key
,
'course_key_string'
)
response
=
self
.
client
.
ajax_post
(
course_url
,
rerun_course_data
)
response
=
self
.
client
.
ajax_post
(
course_url
,
rerun_course_data
)
# verify response
# verify response
self
.
assertEqual
(
response
.
status_code
,
response_code
)
self
.
assertEqual
(
response
.
status_code
,
response_code
)
if
response_code
==
200
:
if
not
expect_error
:
self
.
assertNotIn
(
'ErrMsg'
,
parse_json
(
response
))
json_resp
=
parse_json
(
response
)
self
.
assertNotIn
(
'ErrMsg'
,
json_resp
)
destination_course_key
=
CourseKey
.
from_string
(
json_resp
[
'destination_course_key'
])
def
create_course_listing_html
(
self
,
course_key
):
return
destination_course_key
"""Creates html fragment that is created for the given course_key in the course listing section"""
return
'<a class="course-link" href="/course/{}"'
.
format
(
course_key
)
def
create_unsucceeded_course_action_html
(
self
,
course_key
):
def
create_unsucceeded_course_action_html
(
self
,
course_key
):
"""Creates html fragment that is created for the given course_key in the unsucceeded course action section"""
"""Creates html fragment that is created for the given course_key in the unsucceeded course action section"""
# TODO
LMS-11011 Update this once the Rerun UI
is implemented.
# TODO
Update this once the Rerun UI LMS-11011
is implemented.
return
'<div class="unsucceeded-course-action" href="/course/{}"'
.
format
(
course_key
)
return
'<div class="unsucceeded-course-action" href="/course/{}"'
.
format
(
course_key
)
def
assertInCourseListing
(
self
,
course_key
):
def
assertInCourseListing
(
self
,
course_key
):
...
@@ -1605,7 +1611,7 @@ class RerunCourseTest(ContentStoreTestCase):
...
@@ -1605,7 +1611,7 @@ class RerunCourseTest(ContentStoreTestCase):
and NOT in the unsucceeded course action section of the html.
and NOT in the unsucceeded course action section of the html.
"""
"""
course_listing_html
=
self
.
client
.
get_html
(
'/course/'
)
course_listing_html
=
self
.
client
.
get_html
(
'/course/'
)
self
.
assertIn
(
self
.
create_course_listing_html
(
course_key
)
,
course_listing_html
.
content
)
self
.
assertIn
(
course_key
.
run
,
course_listing_html
.
content
)
self
.
assertNotIn
(
self
.
create_unsucceeded_course_action_html
(
course_key
),
course_listing_html
.
content
)
self
.
assertNotIn
(
self
.
create_unsucceeded_course_action_html
(
course_key
),
course_listing_html
.
content
)
def
assertInUnsucceededCourseActions
(
self
,
course_key
):
def
assertInUnsucceededCourseActions
(
self
,
course_key
):
...
@@ -1614,32 +1620,39 @@ class RerunCourseTest(ContentStoreTestCase):
...
@@ -1614,32 +1620,39 @@ class RerunCourseTest(ContentStoreTestCase):
and NOT in the accessible course listing section of the html.
and NOT in the accessible course listing section of the html.
"""
"""
course_listing_html
=
self
.
client
.
get_html
(
'/course/'
)
course_listing_html
=
self
.
client
.
get_html
(
'/course/'
)
self
.
assertNotIn
(
self
.
create_course_listing_html
(
course_key
),
course_listing_html
.
content
)
self
.
assertNotIn
(
course_key
.
run
,
course_listing_html
.
content
)
# TODO Uncomment this once LMS-11011 is implemented.
# TODO Verify the course is in the unsucceeded listing once LMS-11011 is implemented.
# self.assertIn(self.create_unsucceeded_course_action_html(course_key), course_listing_html.content)
def
test_rerun_course_success
(
self
):
def
test_rerun_course_success
(
self
):
source_course
=
CourseFactory
.
create
()
self
.
post_rerun_request
(
source_course
.
id
)
# Verify that the course rerun action is marked succeeded
source_course
=
CourseFactory
.
create
()
rerun_state
=
CourseRerunState
.
objects
.
find_first
(
course_key
=
self
.
destination_course_key
)
destination_course_key
=
self
.
post_rerun_request
(
source_course
.
id
)
self
.
assertEquals
(
rerun_state
.
state
,
CourseRerunUIStateManager
.
State
.
SUCCEEDED
)
# Verify the contents of the course rerun action
rerun_state
=
CourseRerunState
.
objects
.
find_first
(
course_key
=
destination_course_key
)
expected_states
=
{
'state'
:
CourseRerunUIStateManager
.
State
.
SUCCEEDED
,
'source_course_key'
:
source_course
.
id
,
'course_key'
:
destination_course_key
,
'should_display'
:
True
,
}
for
field_name
,
expected_value
in
expected_states
.
iteritems
():
self
.
assertEquals
(
getattr
(
rerun_state
,
field_name
),
expected_value
)
# Verify that the creator is now enrolled in the course.
# Verify that the creator is now enrolled in the course.
self
.
assertTrue
(
CourseEnrollment
.
is_enrolled
(
self
.
user
,
self
.
destination_course_key
))
self
.
assertTrue
(
CourseEnrollment
.
is_enrolled
(
self
.
user
,
destination_course_key
))
# Verify both courses are in the course listing section
# Verify both courses are in the course listing section
self
.
assertInCourseListing
(
source_course
.
id
)
self
.
assertInCourseListing
(
source_course
.
id
)
self
.
assertInCourseListing
(
self
.
destination_course_key
)
self
.
assertInCourseListing
(
destination_course_key
)
def
test_rerun_course_fail
(
self
):
def
test_rerun_course_fail
_no_source_course
(
self
):
existent_course_key
=
CourseFactory
.
create
()
.
id
existent_course_key
=
CourseFactory
.
create
()
.
id
non_existent_course_key
=
CourseLocator
(
"org"
,
"non_existent_course"
,
"run"
)
non_existent_course_key
=
CourseLocator
(
"org"
,
"non_existent_course"
,
"
non_existent_
run"
)
self
.
post_rerun_request
(
non_existent_course_key
)
destination_course_key
=
self
.
post_rerun_request
(
non_existent_course_key
)
# Verify that the course rerun action is marked failed
# Verify that the course rerun action is marked failed
rerun_state
=
CourseRerunState
.
objects
.
find_first
(
course_key
=
self
.
destination_course_key
)
rerun_state
=
CourseRerunState
.
objects
.
find_first
(
course_key
=
destination_course_key
)
self
.
assertEquals
(
rerun_state
.
state
,
CourseRerunUIStateManager
.
State
.
FAILED
)
self
.
assertEquals
(
rerun_state
.
state
,
CourseRerunUIStateManager
.
State
.
FAILED
)
self
.
assertIn
(
"Cannot find a course at"
,
rerun_state
.
message
)
self
.
assertIn
(
"Cannot find a course at"
,
rerun_state
.
message
)
...
@@ -1652,13 +1665,32 @@ class RerunCourseTest(ContentStoreTestCase):
...
@@ -1652,13 +1665,32 @@ class RerunCourseTest(ContentStoreTestCase):
# Verify that the failed course is NOT in the course listings
# Verify that the failed course is NOT in the course listings
self
.
assertInUnsucceededCourseActions
(
non_existent_course_key
)
self
.
assertInUnsucceededCourseActions
(
non_existent_course_key
)
def
test_rerun_course_fail_duplicate_course
(
self
):
existent_course_key
=
CourseFactory
.
create
()
.
id
destination_course_data
=
{
'org'
:
existent_course_key
.
org
,
'number'
:
existent_course_key
.
course
,
'display_name'
:
'existing course'
,
'run'
:
existent_course_key
.
run
}
destination_course_key
=
self
.
post_rerun_request
(
existent_course_key
,
destination_course_data
,
expect_error
=
True
)
# Verify that the course rerun action doesn't exist
with
self
.
assertRaises
(
CourseActionStateItemNotFoundError
):
CourseRerunState
.
objects
.
find_first
(
course_key
=
destination_course_key
)
# Verify that the existing course continues to be in the course listing
self
.
assertInCourseListing
(
existent_course_key
)
def
test_rerun_with_permission_denied
(
self
):
def
test_rerun_with_permission_denied
(
self
):
with
mock
.
patch
.
dict
(
'django.conf.settings.FEATURES'
,
{
"ENABLE_CREATOR_GROUP"
:
True
}):
with
mock
.
patch
.
dict
(
'django.conf.settings.FEATURES'
,
{
"ENABLE_CREATOR_GROUP"
:
True
}):
source_course
=
CourseFactory
.
create
()
source_course
=
CourseFactory
.
create
()
auth
.
add_users
(
self
.
user
,
CourseCreatorRole
(),
self
.
user
)
auth
.
add_users
(
self
.
user
,
CourseCreatorRole
(),
self
.
user
)
self
.
user
.
is_staff
=
False
self
.
user
.
is_staff
=
False
self
.
user
.
save
()
self
.
user
.
save
()
self
.
post_rerun_request
(
source_course
.
id
,
403
)
self
.
post_rerun_request
(
source_course
.
id
,
response_code
=
403
,
expect_error
=
True
)
class
EntryPageTestCase
(
TestCase
):
class
EntryPageTestCase
(
TestCase
):
...
@@ -1705,6 +1737,6 @@ def _course_factory_create_course():
...
@@ -1705,6 +1737,6 @@ def _course_factory_create_course():
return
CourseFactory
.
create
(
org
=
'MITx'
,
course
=
'999'
,
display_name
=
'Robot Super Course'
)
return
CourseFactory
.
create
(
org
=
'MITx'
,
course
=
'999'
,
display_name
=
'Robot Super Course'
)
def
_get_course_id
(
course_data
):
def
_get_course_id
(
course_data
,
key_class
=
SlashSeparatedCourseKey
):
"""Returns the course ID (org/number/run)."""
"""Returns the course ID (org/number/run)."""
return
SlashSeparatedCourseKey
(
course_data
[
'org'
],
course_data
[
'number'
],
course_data
[
'run'
])
return
key_class
(
course_data
[
'org'
],
course_data
[
'number'
],
course_data
[
'run'
])
cms/djangoapps/contentstore/tests/test_course_settings.py
View file @
9efe5d92
...
@@ -413,7 +413,7 @@ class CourseGradingTest(CourseTestCase):
...
@@ -413,7 +413,7 @@ class CourseGradingTest(CourseTestCase):
Populate the course, grab a section, get the url for the assignment type access
Populate the course, grab a section, get the url for the assignment type access
"""
"""
self
.
populate_course
()
self
.
populate_course
()
sections
=
modulestore
()
.
get_items
(
self
.
course
.
id
,
category
=
"sequential"
)
sections
=
modulestore
()
.
get_items
(
self
.
course
.
id
,
qualifiers
=
{
'category'
:
"sequential"
}
)
# see if test makes sense
# see if test makes sense
self
.
assertGreater
(
len
(
sections
),
0
,
"No sections found"
)
self
.
assertGreater
(
len
(
sections
),
0
,
"No sections found"
)
section
=
sections
[
0
]
# just take the first one
section
=
sections
[
0
]
# just take the first one
...
...
cms/djangoapps/contentstore/tests/test_import.py
View file @
9efe5d92
...
@@ -14,7 +14,8 @@ from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
...
@@ -14,7 +14,8 @@ from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from
xmodule.modulestore
import
ModuleStoreEnum
from
xmodule.modulestore
import
ModuleStoreEnum
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.django
import
modulestore
from
xmodule.contentstore.django
import
contentstore
from
xmodule.contentstore.django
import
contentstore
from
xmodule.modulestore.tests.factories
import
check_exact_number_of_calls
,
check_number_of_calls
from
xmodule.modulestore.exceptions
import
DuplicateCourseError
from
xmodule.modulestore.tests.factories
import
check_exact_number_of_calls
,
check_number_of_calls
,
CourseFactory
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
,
AssetLocation
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
,
AssetLocation
from
xmodule.modulestore.xml_importer
import
import_from_xml
from
xmodule.modulestore.xml_importer
import
import_from_xml
from
xmodule.exceptions
import
NotFoundError
from
xmodule.exceptions
import
NotFoundError
...
...
cms/djangoapps/contentstore/tests/utils.py
View file @
9efe5d92
...
@@ -190,7 +190,7 @@ class CourseTestCase(ModuleStoreTestCase):
...
@@ -190,7 +190,7 @@ class CourseTestCase(ModuleStoreTestCase):
"""
"""
items
=
self
.
store
.
get_items
(
items
=
self
.
store
.
get_items
(
course_id
,
course_id
,
category
=
'vertical'
,
qualifiers
=
{
'category'
:
'vertical'
}
,
revision
=
ModuleStoreEnum
.
RevisionOption
.
published_only
revision
=
ModuleStoreEnum
.
RevisionOption
.
published_only
)
)
self
.
check_verticals
(
items
)
self
.
check_verticals
(
items
)
...
...
cms/djangoapps/contentstore/views/assets.py
View file @
9efe5d92
...
@@ -277,7 +277,7 @@ def _get_asset_json(display_name, date, location, thumbnail_location, locked):
...
@@ -277,7 +277,7 @@ def _get_asset_json(display_name, date, location, thumbnail_location, locked):
"""
"""
Helper method for formatting the asset information to send to client.
Helper method for formatting the asset information to send to client.
"""
"""
asset_url
=
location
.
to_deprecated_string
(
)
asset_url
=
_add_slash
(
location
.
to_deprecated_string
()
)
external_url
=
settings
.
LMS_BASE
+
asset_url
external_url
=
settings
.
LMS_BASE
+
asset_url
return
{
return
{
'display_name'
:
display_name
,
'display_name'
:
display_name
,
...
@@ -285,8 +285,14 @@ def _get_asset_json(display_name, date, location, thumbnail_location, locked):
...
@@ -285,8 +285,14 @@ def _get_asset_json(display_name, date, location, thumbnail_location, locked):
'url'
:
asset_url
,
'url'
:
asset_url
,
'external_url'
:
external_url
,
'external_url'
:
external_url
,
'portable_url'
:
StaticContent
.
get_static_path_from_location
(
location
),
'portable_url'
:
StaticContent
.
get_static_path_from_location
(
location
),
'thumbnail'
:
thumbnail_location
.
to_deprecated_string
()
if
thumbnail_location
is
not
None
else
None
,
'thumbnail'
:
_add_slash
(
unicode
(
thumbnail_location
))
if
thumbnail_location
else
None
,
'locked'
:
locked
,
'locked'
:
locked
,
# Needed for Backbone delete/update.
# Needed for Backbone delete/update.
'id'
:
unicode
(
location
)
'id'
:
unicode
(
location
)
}
}
def
_add_slash
(
url
):
if
not
url
.
startswith
(
'/'
):
url
=
'/'
+
url
# TODO - re-address this once LMS-11198 is tackled.
return
url
cms/djangoapps/contentstore/views/course.py
View file @
9efe5d92
...
@@ -24,9 +24,9 @@ from xmodule.contentstore.content import StaticContent
...
@@ -24,9 +24,9 @@ from xmodule.contentstore.content import StaticContent
from
xmodule.tabs
import
PDFTextbookTabs
from
xmodule.tabs
import
PDFTextbookTabs
from
xmodule.partitions.partitions
import
UserPartition
,
Group
from
xmodule.partitions.partitions
import
UserPartition
,
Group
from
xmodule.modulestore.exceptions
import
ItemNotFoundError
,
InvalidLocation
Error
from
xmodule.modulestore.exceptions
import
ItemNotFoundError
,
DuplicateCourse
Error
from
opaque_keys
import
InvalidKeyError
from
opaque_keys
import
InvalidKeyError
from
opaque_keys.edx.locations
import
Location
,
SlashSeparatedCourseKey
from
opaque_keys.edx.locations
import
Location
from
contentstore.course_info_model
import
get_course_updates
,
update_course_updates
,
delete_course_update
from
contentstore.course_info_model
import
get_course_updates
,
update_course_updates
,
delete_course_update
from
contentstore.utils
import
(
from
contentstore.utils
import
(
...
@@ -441,7 +441,7 @@ def _create_or_rerun_course(request):
...
@@ -441,7 +441,7 @@ def _create_or_rerun_course(request):
"""
"""
To be called by requests that create a new destination course (i.e., create_new_course and rerun_course)
To be called by requests that create a new destination course (i.e., create_new_course and rerun_course)
Returns the destination course_key and overriding fields for the new course.
Returns the destination course_key and overriding fields for the new course.
Raises
InvalidLocation
Error and InvalidKeyError
Raises
DuplicateCourse
Error and InvalidKeyError
"""
"""
if
not
auth
.
has_access
(
request
.
user
,
CourseCreatorRole
()):
if
not
auth
.
has_access
(
request
.
user
,
CourseCreatorRole
()):
raise
PermissionDenied
()
raise
PermissionDenied
()
...
@@ -460,15 +460,14 @@ def _create_or_rerun_course(request):
...
@@ -460,15 +460,14 @@ def _create_or_rerun_course(request):
status
=
400
status
=
400
)
)
course_key
=
SlashSeparatedCourseKey
(
org
,
number
,
run
)
fields
=
{
'display_name'
:
display_name
}
if
display_name
is
not
None
else
{}
fields
=
{
'display_name'
:
display_name
}
if
display_name
is
not
None
else
{}
if
'source_course_key'
in
request
.
json
:
if
'source_course_key'
in
request
.
json
:
return
_rerun_course
(
request
,
course_key
,
fields
)
return
_rerun_course
(
request
,
org
,
number
,
run
,
fields
)
else
:
else
:
return
_create_new_course
(
request
,
course_key
,
fields
)
return
_create_new_course
(
request
,
org
,
number
,
run
,
fields
)
except
InvalidLocation
Error
:
except
DuplicateCourse
Error
:
return
JsonResponse
({
return
JsonResponse
({
'ErrMsg'
:
_
(
'ErrMsg'
:
_
(
'There is already a course defined with the same '
'There is already a course defined with the same '
...
@@ -488,24 +487,27 @@ def _create_or_rerun_course(request):
...
@@ -488,24 +487,27 @@ def _create_or_rerun_course(request):
)
)
def
_create_new_course
(
request
,
course_key
,
fields
):
def
_create_new_course
(
request
,
org
,
number
,
run
,
fields
):
"""
"""
Create a new course.
Create a new course.
Returns the URL for the course overview page.
Returns the URL for the course overview page.
Raises DuplicateCourseError if the course already exists
"""
"""
# Set a unique wiki_slug for newly created courses. To maintain active wiki_slugs for
# Set a unique wiki_slug for newly created courses. To maintain active wiki_slugs for
# existing xml courses this cannot be changed in CourseDescriptor.
# existing xml courses this cannot be changed in CourseDescriptor.
# # TODO get rid of defining wiki slug in this org/course/run specific way and reconcile
# # TODO get rid of defining wiki slug in this org/course/run specific way and reconcile
# w/ xmodule.course_module.CourseDescriptor.__init__
# w/ xmodule.course_module.CourseDescriptor.__init__
wiki_slug
=
u"{0}.{1}.{2}"
.
format
(
course_key
.
org
,
course_key
.
course
,
course_key
.
run
)
wiki_slug
=
u"{0}.{1}.{2}"
.
format
(
org
,
number
,
run
)
definition_data
=
{
'wiki_slug'
:
wiki_slug
}
definition_data
=
{
'wiki_slug'
:
wiki_slug
}
fields
.
update
(
definition_data
)
fields
.
update
(
definition_data
)
# Creating the course raises InvalidLocationError if an existing course with this org/name is found
store
=
modulestore
()
new_course
=
modulestore
()
.
create_course
(
with
store
.
default_store
(
settings
.
FEATURES
.
get
(
'DEFAULT_STORE_FOR_NEW_COURSE'
,
'mongo'
)):
course_key
.
org
,
# Creating the course raises DuplicateCourseError if an existing course with this org/name is found
course_key
.
course
,
new_course
=
store
.
create_course
(
course_key
.
run
,
org
,
number
,
run
,
request
.
user
.
id
,
request
.
user
.
id
,
fields
=
fields
,
fields
=
fields
,
)
)
...
@@ -521,7 +523,7 @@ def _create_new_course(request, course_key, fields):
...
@@ -521,7 +523,7 @@ def _create_new_course(request, course_key, fields):
})
})
def
_rerun_course
(
request
,
destination_course_key
,
fields
):
def
_rerun_course
(
request
,
org
,
number
,
run
,
fields
):
"""
"""
Reruns an existing course.
Reruns an existing course.
Returns the URL for the course listing page.
Returns the URL for the course listing page.
...
@@ -532,6 +534,15 @@ def _rerun_course(request, destination_course_key, fields):
...
@@ -532,6 +534,15 @@ def _rerun_course(request, destination_course_key, fields):
if
not
has_course_access
(
request
.
user
,
source_course_key
):
if
not
has_course_access
(
request
.
user
,
source_course_key
):
raise
PermissionDenied
()
raise
PermissionDenied
()
# create destination course key
store
=
modulestore
()
with
store
.
default_store
(
'split'
):
destination_course_key
=
store
.
make_course_key
(
org
,
number
,
run
)
# verify org course and run don't already exist
if
store
.
has_course
(
destination_course_key
,
ignore_case
=
True
):
raise
DuplicateCourseError
(
source_course_key
,
destination_course_key
)
# Make sure user has instructor and staff access to the destination course
# Make sure user has instructor and staff access to the destination course
# so the user can see the updated status for that course
# so the user can see the updated status for that course
add_instructor
(
destination_course_key
,
request
.
user
,
request
.
user
)
add_instructor
(
destination_course_key
,
request
.
user
,
request
.
user
)
...
@@ -540,10 +551,13 @@ def _rerun_course(request, destination_course_key, fields):
...
@@ -540,10 +551,13 @@ def _rerun_course(request, destination_course_key, fields):
CourseRerunState
.
objects
.
initiated
(
source_course_key
,
destination_course_key
,
request
.
user
)
CourseRerunState
.
objects
.
initiated
(
source_course_key
,
destination_course_key
,
request
.
user
)
# Rerun the course as a new celery task
# Rerun the course as a new celery task
rerun_course
.
delay
(
source_course_key
,
destination_course_key
,
request
.
user
.
id
,
fields
)
rerun_course
.
delay
(
unicode
(
source_course_key
),
unicode
(
destination_course_key
)
,
request
.
user
.
id
,
fields
)
# Return course listing page
# Return course listing page
return
JsonResponse
({
'url'
:
reverse_url
(
'course_handler'
)})
return
JsonResponse
({
'url'
:
reverse_url
(
'course_handler'
),
'destination_course_key'
:
unicode
(
destination_course_key
)
})
# pylint: disable=unused-argument
# pylint: disable=unused-argument
...
@@ -1121,7 +1135,7 @@ class GroupConfiguration(object):
...
@@ -1121,7 +1135,7 @@ class GroupConfiguration(object):
}
}
"""
"""
usage_info
=
{}
usage_info
=
{}
descriptors
=
store
.
get_items
(
course
.
id
,
category
=
'split_test'
)
descriptors
=
store
.
get_items
(
course
.
id
,
qualifiers
=
{
'category'
:
'split_test'
}
)
for
split_test
in
descriptors
:
for
split_test
in
descriptors
:
if
split_test
.
user_partition_id
not
in
usage_info
:
if
split_test
.
user_partition_id
not
in
usage_info
:
usage_info
[
split_test
.
user_partition_id
]
=
[]
usage_info
[
split_test
.
user_partition_id
]
=
[]
...
...
cms/djangoapps/contentstore/views/item.py
View file @
9efe5d92
...
@@ -424,6 +424,11 @@ def _create_item(request):
...
@@ -424,6 +424,11 @@ def _create_item(request):
if
display_name
is
not
None
:
if
display_name
is
not
None
:
metadata
[
'display_name'
]
=
display_name
metadata
[
'display_name'
]
=
display_name
# TODO need to fix components that are sending definition_data as strings, instead of as dicts
# For now, migrate them into dicts here.
if
isinstance
(
data
,
basestring
):
data
=
{
'data'
:
data
}
created_block
=
store
.
create_child
(
created_block
=
store
.
create_child
(
request
.
user
.
id
,
request
.
user
.
id
,
usage_key
,
usage_key
,
...
...
cms/djangoapps/contentstore/views/tasks.py
View file @
9efe5d92
...
@@ -5,17 +5,27 @@ This file contains celery tasks for contentstore views
...
@@ -5,17 +5,27 @@ This file contains celery tasks for contentstore views
from
celery.task
import
task
from
celery.task
import
task
from
django.contrib.auth.models
import
User
from
django.contrib.auth.models
import
User
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.exceptions
import
DuplicateCourseError
,
ItemNotFoundError
from
course_action_state.models
import
CourseRerunState
from
course_action_state.models
import
CourseRerunState
from
contentstore.utils
import
initialize_permissions
from
contentstore.utils
import
initialize_permissions
from
opaque_keys.edx.keys
import
CourseKey
@task
()
@task
()
def
rerun_course
(
source_course_key
,
destination_course_key
,
user_id
,
fields
=
None
):
def
rerun_course
(
source_course_key
_string
,
destination_course_key_string
,
user_id
,
fields
=
None
):
"""
"""
Reruns a course in a new celery task.
Reruns a course in a new celery task.
"""
"""
try
:
try
:
modulestore
()
.
clone_course
(
source_course_key
,
destination_course_key
,
user_id
,
fields
=
fields
)
# deserialize the keys
source_course_key
=
CourseKey
.
from_string
(
source_course_key_string
)
destination_course_key
=
CourseKey
.
from_string
(
destination_course_key_string
)
# use the split modulestore as the store for the rerun course,
# as the Mongo modulestore doesn't support multiple runs of the same course.
store
=
modulestore
()
with
store
.
default_store
(
'split'
):
store
.
clone_course
(
source_course_key
,
destination_course_key
,
user_id
,
fields
=
fields
)
# set initial permissions for the user to access the course.
# set initial permissions for the user to access the course.
initialize_permissions
(
destination_course_key
,
User
.
objects
.
get
(
id
=
user_id
))
initialize_permissions
(
destination_course_key
,
User
.
objects
.
get
(
id
=
user_id
))
...
@@ -23,10 +33,24 @@ def rerun_course(source_course_key, destination_course_key, user_id, fields=None
...
@@ -23,10 +33,24 @@ def rerun_course(source_course_key, destination_course_key, user_id, fields=None
# update state: Succeeded
# update state: Succeeded
CourseRerunState
.
objects
.
succeeded
(
course_key
=
destination_course_key
)
CourseRerunState
.
objects
.
succeeded
(
course_key
=
destination_course_key
)
return
"succeeded"
except
DuplicateCourseError
as
exc
:
# do NOT delete the original course, only update the status
CourseRerunState
.
objects
.
failed
(
course_key
=
destination_course_key
,
exception
=
exc
)
return
"duplicate course"
# catch all exceptions so we can update the state and properly cleanup the course.
# catch all exceptions so we can update the state and properly cleanup the course.
except
Exception
as
exc
:
# pylint: disable=broad-except
except
Exception
as
exc
:
# pylint: disable=broad-except
# update state: Failed
# update state: Failed
CourseRerunState
.
objects
.
failed
(
course_key
=
destination_course_key
,
exception
=
exc
)
CourseRerunState
.
objects
.
failed
(
course_key
=
destination_course_key
,
exception
=
exc
)
try
:
# cleanup any remnants of the course
# cleanup any remnants of the course
modulestore
()
.
delete_course
(
destination_course_key
,
user_id
)
modulestore
()
.
delete_course
(
destination_course_key
,
user_id
)
except
ItemNotFoundError
:
# it's possible there was an error even before the course module was created
pass
return
"exception: "
+
unicode
(
exc
)
cms/envs/bok_choy.auth.json
View file @
9efe5d92
...
@@ -51,7 +51,6 @@
...
@@ -51,7 +51,6 @@
"ENGINE"
:
"xmodule.modulestore.mixed.MixedModuleStore"
,
"ENGINE"
:
"xmodule.modulestore.mixed.MixedModuleStore"
,
"OPTIONS"
:
{
"OPTIONS"
:
{
"mappings"
:
{},
"mappings"
:
{},
"reference_type"
:
"Location"
,
"stores"
:
[
"stores"
:
[
{
{
"NAME"
:
"draft"
,
"NAME"
:
"draft"
,
...
...
cms/envs/common.py
View file @
9efe5d92
...
@@ -106,6 +106,9 @@ FEATURES = {
...
@@ -106,6 +106,9 @@ FEATURES = {
# Toggles Group Configuration editing functionality
# Toggles Group Configuration editing functionality
'ENABLE_GROUP_CONFIGURATIONS'
:
os
.
environ
.
get
(
'FEATURE_GROUP_CONFIGURATIONS'
),
'ENABLE_GROUP_CONFIGURATIONS'
:
os
.
environ
.
get
(
'FEATURE_GROUP_CONFIGURATIONS'
),
# Modulestore to use for new courses
'DEFAULT_STORE_FOR_NEW_COURSE'
:
'mongo'
,
}
}
ENABLE_JASMINE
=
False
ENABLE_JASMINE
=
False
...
...
common/djangoapps/cache_toolbox/core.py
View file @
9efe5d92
...
@@ -10,6 +10,7 @@ Core methods
...
@@ -10,6 +10,7 @@ Core methods
from
django.core.cache
import
cache
from
django.core.cache
import
cache
from
django.db
import
DEFAULT_DB_ALIAS
from
django.db
import
DEFAULT_DB_ALIAS
from
opaque_keys
import
InvalidKeyError
from
.
import
app_settings
from
.
import
app_settings
...
@@ -118,9 +119,19 @@ def get_cached_content(location):
...
@@ -118,9 +119,19 @@ def get_cached_content(location):
def
del_cached_content
(
location
):
def
del_cached_content
(
location
):
# delete content for the given location, as well as for content with run=None.
"""
# it's possible that the content could have been cached without knowing the
delete content for the given location, as well as for content with run=None.
# course_key - and so without having the run.
it's possible that the content could have been cached without knowing the
cache
.
delete_many
(
course_key - and so without having the run.
[
unicode
(
loc
)
.
encode
(
"utf-8"
)
for
loc
in
[
location
,
location
.
replace
(
run
=
None
)]]
"""
)
def
location_str
(
loc
):
return
unicode
(
loc
)
.
encode
(
"utf-8"
)
locations
=
[
location_str
(
location
)]
try
:
locations
.
append
(
location_str
(
location
.
replace
(
run
=
None
)))
except
InvalidKeyError
:
# although deprecated keys allowed run=None, new keys don't if there is no version.
pass
cache
.
delete_many
(
locations
)
common/djangoapps/contentserver/middleware.py
View file @
9efe5d92
...
@@ -6,6 +6,7 @@ from xmodule.contentstore.django import contentstore
...
@@ -6,6 +6,7 @@ from xmodule.contentstore.django import contentstore
from
xmodule.contentstore.content
import
StaticContent
,
XASSET_LOCATION_TAG
from
xmodule.contentstore.content
import
StaticContent
,
XASSET_LOCATION_TAG
from
xmodule.modulestore
import
InvalidLocationError
from
xmodule.modulestore
import
InvalidLocationError
from
opaque_keys
import
InvalidKeyError
from
opaque_keys
import
InvalidKeyError
from
opaque_keys.edx.locator
import
AssetLocator
from
cache_toolbox.core
import
get_cached_content
,
set_cached_content
from
cache_toolbox.core
import
get_cached_content
,
set_cached_content
from
xmodule.exceptions
import
NotFoundError
from
xmodule.exceptions
import
NotFoundError
...
@@ -14,8 +15,11 @@ from xmodule.exceptions import NotFoundError
...
@@ -14,8 +15,11 @@ from xmodule.exceptions import NotFoundError
class
StaticContentServer
(
object
):
class
StaticContentServer
(
object
):
def
process_request
(
self
,
request
):
def
process_request
(
self
,
request
):
# look to see if the request is prefixed with 'c4x' tag
# look to see if the request is prefixed with an asset prefix tag
if
request
.
path
.
startswith
(
'/'
+
XASSET_LOCATION_TAG
+
'/'
):
if
(
request
.
path
.
startswith
(
'/'
+
XASSET_LOCATION_TAG
+
'/'
)
or
request
.
path
.
startswith
(
'/'
+
AssetLocator
.
CANONICAL_NAMESPACE
)
):
try
:
try
:
loc
=
StaticContent
.
get_location_from_path
(
request
.
path
)
loc
=
StaticContent
.
get_location_from_path
(
request
.
path
)
except
(
InvalidLocationError
,
InvalidKeyError
):
except
(
InvalidLocationError
,
InvalidKeyError
):
...
...
common/djangoapps/user_api/middleware.py
View file @
9efe5d92
...
@@ -5,7 +5,7 @@ Adds user's tags to tracking event context.
...
@@ -5,7 +5,7 @@ Adds user's tags to tracking event context.
from
eventtracking
import
tracker
from
eventtracking
import
tracker
from
opaque_keys
import
InvalidKeyError
from
opaque_keys
import
InvalidKeyError
from
opaque_keys.edx.
locations
import
SlashSeparated
CourseKey
from
opaque_keys.edx.
keys
import
CourseKey
from
track.contexts
import
COURSE_REGEX
from
track.contexts
import
COURSE_REGEX
from
user_api.models
import
UserCourseTag
from
user_api.models
import
UserCourseTag
...
@@ -24,7 +24,7 @@ class UserTagsEventContextMiddleware(object):
...
@@ -24,7 +24,7 @@ class UserTagsEventContextMiddleware(object):
if
match
:
if
match
:
course_id
=
match
.
group
(
'course_id'
)
course_id
=
match
.
group
(
'course_id'
)
try
:
try
:
course_key
=
SlashSeparatedCourseKey
.
from_deprecated
_string
(
course_id
)
course_key
=
CourseKey
.
from
_string
(
course_id
)
except
InvalidKeyError
:
except
InvalidKeyError
:
course_id
=
None
course_id
=
None
course_key
=
None
course_key
=
None
...
...
common/djangoapps/xmodule_django/models.py
View file @
9efe5d92
...
@@ -2,6 +2,7 @@ from django.db import models
...
@@ -2,6 +2,7 @@ from django.db import models
from
django.core.exceptions
import
ValidationError
from
django.core.exceptions
import
ValidationError
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
,
Location
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
,
Location
from
opaque_keys.edx.keys
import
CourseKey
,
UsageKey
from
opaque_keys.edx.keys
import
CourseKey
,
UsageKey
from
opaque_keys.edx.locator
import
Locator
from
south.modelsinspector
import
add_introspection_rules
from
south.modelsinspector
import
add_introspection_rules
add_introspection_rules
([],
[
"^xmodule_django
\
.models
\
.CourseKeyField"
])
add_introspection_rules
([],
[
"^xmodule_django
\
.models
\
.CourseKeyField"
])
...
@@ -44,6 +45,28 @@ class NoneToEmptyQuerySet(models.query.QuerySet):
...
@@ -44,6 +45,28 @@ class NoneToEmptyQuerySet(models.query.QuerySet):
return
super
(
NoneToEmptyQuerySet
,
self
)
.
_filter_or_exclude
(
*
args
,
**
kwargs
)
return
super
(
NoneToEmptyQuerySet
,
self
)
.
_filter_or_exclude
(
*
args
,
**
kwargs
)
def
_strip_object
(
key
):
"""
Strips branch and version info if the given key supports those attributes.
"""
if
hasattr
(
key
,
'version_agnostic'
)
and
hasattr
(
key
,
'for_branch'
):
return
key
.
for_branch
(
None
)
.
version_agnostic
()
else
:
return
key
def
_strip_value
(
value
,
lookup
=
'exact'
):
"""
Helper function to remove the branch and version information from the given value,
which could be a single object or a list.
"""
if
lookup
==
'in'
:
stripped_value
=
[
_strip_object
(
el
)
for
el
in
value
]
else
:
stripped_value
=
_strip_object
(
value
)
return
stripped_value
class
CourseKeyField
(
models
.
CharField
):
class
CourseKeyField
(
models
.
CharField
):
description
=
"A SlashSeparatedCourseKey object, saved to the DB in the form of a string"
description
=
"A SlashSeparatedCourseKey object, saved to the DB in the form of a string"
...
@@ -69,14 +92,18 @@ class CourseKeyField(models.CharField):
...
@@ -69,14 +92,18 @@ class CourseKeyField(models.CharField):
if
lookup
==
'isnull'
:
if
lookup
==
'isnull'
:
raise
TypeError
(
'Use CourseKeyField.Empty rather than None to query for a missing CourseKeyField'
)
raise
TypeError
(
'Use CourseKeyField.Empty rather than None to query for a missing CourseKeyField'
)
return
super
(
CourseKeyField
,
self
)
.
get_prep_lookup
(
lookup
,
value
)
return
super
(
CourseKeyField
,
self
)
.
get_prep_lookup
(
lookup
,
# strip key before comparing
_strip_value
(
value
,
lookup
)
)
def
get_prep_value
(
self
,
value
):
def
get_prep_value
(
self
,
value
):
if
value
is
self
.
Empty
or
value
is
None
:
if
value
is
self
.
Empty
or
value
is
None
:
return
''
# CharFields should use '' as their empty value, rather than None
return
''
# CharFields should use '' as their empty value, rather than None
assert
isinstance
(
value
,
CourseKey
)
assert
isinstance
(
value
,
CourseKey
)
return
value
.
to_deprecated_string
(
)
return
unicode
(
_strip_value
(
value
)
)
def
validate
(
self
,
value
,
model_instance
):
def
validate
(
self
,
value
,
model_instance
):
"""Validate Empty values, otherwise defer to the parent"""
"""Validate Empty values, otherwise defer to the parent"""
...
@@ -119,14 +146,19 @@ class LocationKeyField(models.CharField):
...
@@ -119,14 +146,19 @@ class LocationKeyField(models.CharField):
if
lookup
==
'isnull'
:
if
lookup
==
'isnull'
:
raise
TypeError
(
'Use LocationKeyField.Empty rather than None to query for a missing LocationKeyField'
)
raise
TypeError
(
'Use LocationKeyField.Empty rather than None to query for a missing LocationKeyField'
)
return
super
(
LocationKeyField
,
self
)
.
get_prep_lookup
(
lookup
,
value
)
# remove version and branch info before comparing keys
return
super
(
LocationKeyField
,
self
)
.
get_prep_lookup
(
lookup
,
# strip key before comparing
_strip_value
(
value
,
lookup
)
)
def
get_prep_value
(
self
,
value
):
def
get_prep_value
(
self
,
value
):
if
value
is
self
.
Empty
:
if
value
is
self
.
Empty
:
return
''
return
''
assert
isinstance
(
value
,
UsageKey
)
assert
isinstance
(
value
,
UsageKey
)
return
value
.
to_deprecated_string
(
)
return
unicode
(
_strip_value
(
value
)
)
def
validate
(
self
,
value
,
model_instance
):
def
validate
(
self
,
value
,
model_instance
):
"""Validate Empty values, otherwise defer to the parent"""
"""Validate Empty values, otherwise defer to the parent"""
...
...
common/lib/xmodule/xmodule/contentstore/content.py
View file @
9efe5d92
...
@@ -10,8 +10,9 @@ import StringIO
...
@@ -10,8 +10,9 @@ import StringIO
from
urlparse
import
urlparse
,
urlunparse
,
parse_qsl
from
urlparse
import
urlparse
,
urlunparse
,
parse_qsl
from
urllib
import
urlencode
from
urllib
import
urlencode
from
opaque_keys.edx.locations
import
AssetLocation
from
opaque_keys.edx.locator
import
AssetLocator
from
opaque_keys.edx.keys
import
CourseKey
from
opaque_keys.edx.keys
import
CourseKey
,
AssetKey
from
opaque_keys
import
InvalidKeyError
from
PIL
import
Image
from
PIL
import
Image
...
@@ -52,12 +53,10 @@ class StaticContent(object):
...
@@ -52,12 +53,10 @@ class StaticContent(object):
asset
asset
"""
"""
path
=
path
.
replace
(
'/'
,
'_'
)
path
=
path
.
replace
(
'/'
,
'_'
)
return
AssetLocation
(
return
course_key
.
make_asset_key
(
course_key
.
org
,
course_key
.
course
,
course_key
.
run
,
'asset'
if
not
is_thumbnail
else
'thumbnail'
,
'asset'
if
not
is_thumbnail
else
'thumbnail'
,
AssetLocation
.
clean_keeping_underscores
(
path
),
AssetLocator
.
clean_keeping_underscores
(
path
)
revision
)
.
for_branch
(
None
)
)
def
get_id
(
self
):
def
get_id
(
self
):
return
self
.
location
return
self
.
location
...
@@ -104,16 +103,22 @@ class StaticContent(object):
...
@@ -104,16 +103,22 @@ class StaticContent(object):
return
None
return
None
assert
(
isinstance
(
course_key
,
CourseKey
))
assert
(
isinstance
(
course_key
,
CourseKey
))
return
course_key
.
make_asset_key
(
'asset'
,
''
)
.
to_deprecated_string
()
# create a dummy asset location and then strip off the last character: 'a',
# since the AssetLocator rejects the empty string as a legal value for the block_id.
return
course_key
.
make_asset_key
(
'asset'
,
'a'
)
.
for_branch
(
None
)
.
to_deprecated_string
()[:
-
1
]
@staticmethod
@staticmethod
def
get_location_from_path
(
path
):
def
get_location_from_path
(
path
):
"""
"""
Generate an AssetKey for the given path (old c4x/org/course/asset/name syntax)
Generate an AssetKey for the given path (old c4x/org/course/asset/name syntax)
"""
"""
# TODO OpaqueKeys after opaque keys deprecation is working
try
:
# return AssetLocation.from_string(path)
return
AssetKey
.
from_string
(
path
)
return
AssetLocation
.
from_deprecated_string
(
path
)
except
InvalidKeyError
:
# TODO - re-address this once LMS-11198 is tackled.
if
path
.
startswith
(
'/'
):
# try stripping off the leading slash and try again
return
AssetKey
.
from_string
(
path
[
1
:])
@staticmethod
@staticmethod
def
convert_legacy_static_url_with_course_id
(
path
,
course_id
):
def
convert_legacy_static_url_with_course_id
(
path
,
course_id
):
...
...
common/lib/xmodule/xmodule/contentstore/mongo.py
View file @
9efe5d92
...
@@ -94,7 +94,10 @@ class MongoContentStore(ContentStore):
...
@@ -94,7 +94,10 @@ class MongoContentStore(ContentStore):
fp
=
self
.
fs
.
get
(
content_id
)
fp
=
self
.
fs
.
get
(
content_id
)
thumbnail_location
=
getattr
(
fp
,
'thumbnail_location'
,
None
)
thumbnail_location
=
getattr
(
fp
,
'thumbnail_location'
,
None
)
if
thumbnail_location
:
if
thumbnail_location
:
thumbnail_location
=
location
.
course_key
.
make_asset_key
(
'thumbnail'
,
thumbnail_location
[
4
])
thumbnail_location
=
location
.
course_key
.
make_asset_key
(
'thumbnail'
,
thumbnail_location
[
4
]
)
return
StaticContentStream
(
return
StaticContentStream
(
location
,
fp
.
displayname
,
fp
.
content_type
,
fp
,
last_modified_at
=
fp
.
uploadDate
,
location
,
fp
.
displayname
,
fp
.
content_type
,
fp
,
last_modified_at
=
fp
.
uploadDate
,
thumbnail_location
=
thumbnail_location
,
thumbnail_location
=
thumbnail_location
,
...
@@ -105,7 +108,10 @@ class MongoContentStore(ContentStore):
...
@@ -105,7 +108,10 @@ class MongoContentStore(ContentStore):
with
self
.
fs
.
get
(
content_id
)
as
fp
:
with
self
.
fs
.
get
(
content_id
)
as
fp
:
thumbnail_location
=
getattr
(
fp
,
'thumbnail_location'
,
None
)
thumbnail_location
=
getattr
(
fp
,
'thumbnail_location'
,
None
)
if
thumbnail_location
:
if
thumbnail_location
:
thumbnail_location
=
location
.
course_key
.
make_asset_key
(
'thumbnail'
,
thumbnail_location
[
4
])
thumbnail_location
=
location
.
course_key
.
make_asset_key
(
'thumbnail'
,
thumbnail_location
[
4
]
)
return
StaticContent
(
return
StaticContent
(
location
,
fp
.
displayname
,
fp
.
content_type
,
fp
.
read
(),
last_modified_at
=
fp
.
uploadDate
,
location
,
fp
.
displayname
,
fp
.
content_type
,
fp
.
read
(),
last_modified_at
=
fp
.
uploadDate
,
thumbnail_location
=
thumbnail_location
,
thumbnail_location
=
thumbnail_location
,
...
@@ -304,7 +310,9 @@ class MongoContentStore(ContentStore):
...
@@ -304,7 +310,9 @@ class MongoContentStore(ContentStore):
asset_id
=
asset_key
asset_id
=
asset_key
else
:
# add the run, since it's the last field, we're golden
else
:
# add the run, since it's the last field, we're golden
asset_key
[
'run'
]
=
dest_course_key
.
run
asset_key
[
'run'
]
=
dest_course_key
.
run
asset_id
=
unicode
(
dest_course_key
.
make_asset_key
(
asset_key
[
'category'
],
asset_key
[
'name'
]))
asset_id
=
unicode
(
dest_course_key
.
make_asset_key
(
asset_key
[
'category'
],
asset_key
[
'name'
])
.
for_branch
(
None
)
)
self
.
fs
.
put
(
self
.
fs
.
put
(
source_content
.
read
(),
source_content
.
read
(),
...
@@ -347,7 +355,7 @@ class MongoContentStore(ContentStore):
...
@@ -347,7 +355,7 @@ class MongoContentStore(ContentStore):
# NOTE, there's no need to state that run doesn't exist in the negative case b/c access via
# NOTE, there's no need to state that run doesn't exist in the negative case b/c access via
# SON requires equivalence (same keys and values in exact same order)
# SON requires equivalence (same keys and values in exact same order)
dbkey
[
'run'
]
=
location
.
run
dbkey
[
'run'
]
=
location
.
run
content_id
=
unicode
(
location
)
content_id
=
unicode
(
location
.
for_branch
(
None
)
)
return
content_id
,
dbkey
return
content_id
,
dbkey
def
make_id_son
(
self
,
fs_entry
):
def
make_id_son
(
self
,
fs_entry
):
...
...
common/lib/xmodule/xmodule/modulestore/__init__.py
View file @
9efe5d92
...
@@ -116,7 +116,7 @@ class ModuleStoreRead(object):
...
@@ -116,7 +116,7 @@ class ModuleStoreRead(object):
pass
pass
@abstractmethod
@abstractmethod
def
get_item
(
self
,
usage_key
,
depth
=
0
):
def
get_item
(
self
,
usage_key
,
depth
=
0
,
**
kwargs
):
"""
"""
Returns an XModuleDescriptor instance for the item at location.
Returns an XModuleDescriptor instance for the item at location.
...
@@ -150,7 +150,7 @@ class ModuleStoreRead(object):
...
@@ -150,7 +150,7 @@ class ModuleStoreRead(object):
pass
pass
@abstractmethod
@abstractmethod
def
get_items
(
self
,
location
,
course_id
=
None
,
depth
=
0
,
qualifiers
=
None
):
def
get_items
(
self
,
location
,
course_id
=
None
,
depth
=
0
,
qualifiers
=
None
,
**
kwargs
):
"""
"""
Returns a list of XModuleDescriptor instances for the items
Returns a list of XModuleDescriptor instances for the items
that match location. Any element of location that is None is treated
that match location. Any element of location that is None is treated
...
@@ -228,7 +228,17 @@ class ModuleStoreRead(object):
...
@@ -228,7 +228,17 @@ class ModuleStoreRead(object):
return
criteria
==
target
return
criteria
==
target
@abstractmethod
@abstractmethod
def
get_courses
(
self
):
def
make_course_key
(
self
,
org
,
course
,
run
):
"""
Return a valid :class:`~opaque_keys.edx.keys.CourseKey` for this modulestore
that matches the supplied `org`, `course`, and `run`.
This key may represent a course that doesn't exist in this modulestore.
"""
pass
@abstractmethod
def
get_courses
(
self
,
**
kwargs
):
'''
'''
Returns a list containing the top level XModuleDescriptors of the courses
Returns a list containing the top level XModuleDescriptors of the courses
in this modulestore.
in this modulestore.
...
@@ -236,7 +246,7 @@ class ModuleStoreRead(object):
...
@@ -236,7 +246,7 @@ class ModuleStoreRead(object):
pass
pass
@abstractmethod
@abstractmethod
def
get_course
(
self
,
course_id
,
depth
=
0
):
def
get_course
(
self
,
course_id
,
depth
=
0
,
**
kwargs
):
'''
'''
Look for a specific course by its id (:class:`CourseKey`).
Look for a specific course by its id (:class:`CourseKey`).
Returns the course descriptor, or None if not found.
Returns the course descriptor, or None if not found.
...
@@ -244,7 +254,7 @@ class ModuleStoreRead(object):
...
@@ -244,7 +254,7 @@ class ModuleStoreRead(object):
pass
pass
@abstractmethod
@abstractmethod
def
has_course
(
self
,
course_id
,
ignore_case
=
False
):
def
has_course
(
self
,
course_id
,
ignore_case
=
False
,
**
kwargs
):
'''
'''
Look for a specific course id. Returns whether it exists.
Look for a specific course id. Returns whether it exists.
Args:
Args:
...
@@ -256,13 +266,14 @@ class ModuleStoreRead(object):
...
@@ -256,13 +266,14 @@ class ModuleStoreRead(object):
@abstractmethod
@abstractmethod
def
get_parent_location
(
self
,
location
,
**
kwargs
):
def
get_parent_location
(
self
,
location
,
**
kwargs
):
'''Find the location that is the parent of this location in this
'''
Find the location that is the parent of this location in this
course. Needed for path_to_location().
course. Needed for path_to_location().
'''
'''
pass
pass
@abstractmethod
@abstractmethod
def
get_orphans
(
self
,
course_key
):
def
get_orphans
(
self
,
course_key
,
**
kwargs
):
"""
"""
Get all of the xblocks in the given course which have no parents and are not of types which are
Get all of the xblocks in the given course which have no parents and are not of types which are
usually orphaned. NOTE: may include xblocks which still have references via xblocks which don't
usually orphaned. NOTE: may include xblocks which still have references via xblocks which don't
...
@@ -287,7 +298,7 @@ class ModuleStoreRead(object):
...
@@ -287,7 +298,7 @@ class ModuleStoreRead(object):
pass
pass
@abstractmethod
@abstractmethod
def
get_courses_for_wiki
(
self
,
wiki_slug
):
def
get_courses_for_wiki
(
self
,
wiki_slug
,
**
kwargs
):
"""
"""
Return the list of courses which use this wiki_slug
Return the list of courses which use this wiki_slug
:param wiki_slug: the course wiki root slug
:param wiki_slug: the course wiki root slug
...
@@ -325,7 +336,7 @@ class ModuleStoreWrite(ModuleStoreRead):
...
@@ -325,7 +336,7 @@ class ModuleStoreWrite(ModuleStoreRead):
__metaclass__
=
ABCMeta
__metaclass__
=
ABCMeta
@abstractmethod
@abstractmethod
def
update_item
(
self
,
xblock
,
user_id
,
allow_not_found
=
False
,
force
=
False
):
def
update_item
(
self
,
xblock
,
user_id
,
allow_not_found
=
False
,
force
=
False
,
**
kwargs
):
"""
"""
Update the given xblock's persisted repr. Pass the user's unique id which the persistent store
Update the given xblock's persisted repr. Pass the user's unique id which the persistent store
should save with the update if it has that ability.
should save with the update if it has that ability.
...
@@ -413,7 +424,7 @@ class ModuleStoreWrite(ModuleStoreRead):
...
@@ -413,7 +424,7 @@ class ModuleStoreWrite(ModuleStoreRead):
pass
pass
@abstractmethod
@abstractmethod
def
delete_course
(
self
,
course_key
,
user_id
):
def
delete_course
(
self
,
course_key
,
user_id
,
**
kwargs
):
"""
"""
Deletes the course. It may be a soft or hard delete. It may or may not remove the xblock definitions
Deletes the course. It may be a soft or hard delete. It may or may not remove the xblock definitions
depending on the persistence layer and how tightly bound the xblocks are to the course.
depending on the persistence layer and how tightly bound the xblocks are to the course.
...
@@ -480,19 +491,19 @@ class ModuleStoreReadBase(ModuleStoreRead):
...
@@ -480,19 +491,19 @@ class ModuleStoreReadBase(ModuleStoreRead):
"""
"""
return
{}
return
{}
def
get_course
(
self
,
course_id
,
depth
=
0
):
def
get_course
(
self
,
course_id
,
depth
=
0
,
**
kwargs
):
"""
"""
See ModuleStoreRead.get_course
See ModuleStoreRead.get_course
Default impl--linear search through course list
Default impl--linear search through course list
"""
"""
assert
(
isinstance
(
course_id
,
CourseKey
))
assert
(
isinstance
(
course_id
,
CourseKey
))
for
course
in
self
.
get_courses
():
for
course
in
self
.
get_courses
(
**
kwargs
):
if
course
.
id
==
course_id
:
if
course
.
id
==
course_id
:
return
course
return
course
return
None
return
None
def
has_course
(
self
,
course_id
,
ignore_case
=
False
):
def
has_course
(
self
,
course_id
,
ignore_case
=
False
,
**
kwargs
):
"""
"""
Returns the course_id of the course if it was found, else None
Returns the course_id of the course if it was found, else None
Args:
Args:
...
@@ -577,7 +588,7 @@ class ModuleStoreWriteBase(ModuleStoreReadBase, ModuleStoreWrite):
...
@@ -577,7 +588,7 @@ class ModuleStoreWriteBase(ModuleStoreReadBase, ModuleStoreWrite):
result
[
field
.
scope
][
field_name
]
=
value
result
[
field
.
scope
][
field_name
]
=
value
return
result
return
result
def
clone_course
(
self
,
source_course_id
,
dest_course_id
,
user_id
,
fields
=
None
):
def
clone_course
(
self
,
source_course_id
,
dest_course_id
,
user_id
,
fields
=
None
,
**
kwargs
):
"""
"""
This base method just copies the assets. The lower level impls must do the actual cloning of
This base method just copies the assets. The lower level impls must do the actual cloning of
content.
content.
...
@@ -587,7 +598,7 @@ class ModuleStoreWriteBase(ModuleStoreReadBase, ModuleStoreWrite):
...
@@ -587,7 +598,7 @@ class ModuleStoreWriteBase(ModuleStoreReadBase, ModuleStoreWrite):
self
.
contentstore
.
copy_all_course_assets
(
source_course_id
,
dest_course_id
)
self
.
contentstore
.
copy_all_course_assets
(
source_course_id
,
dest_course_id
)
return
dest_course_id
return
dest_course_id
def
delete_course
(
self
,
course_key
,
user_id
):
def
delete_course
(
self
,
course_key
,
user_id
,
**
kwargs
):
"""
"""
This base method just deletes the assets. The lower level impls must do the actual deleting of
This base method just deletes the assets. The lower level impls must do the actual deleting of
content.
content.
...
...
common/lib/xmodule/xmodule/modulestore/mixed.py
View file @
9efe5d92
...
@@ -8,6 +8,7 @@ In this way, courses can be served up both - say - XMLModuleStore or MongoModule
...
@@ -8,6 +8,7 @@ In this way, courses can be served up both - say - XMLModuleStore or MongoModule
import
logging
import
logging
from
contextlib
import
contextmanager
from
contextlib
import
contextmanager
import
itertools
import
itertools
import
functools
from
opaque_keys
import
InvalidKeyError
from
opaque_keys
import
InvalidKeyError
from
opaque_keys.edx.keys
import
CourseKey
from
opaque_keys.edx.keys
import
CourseKey
...
@@ -15,7 +16,7 @@ from opaque_keys.edx.locations import SlashSeparatedCourseKey
...
@@ -15,7 +16,7 @@ from opaque_keys.edx.locations import SlashSeparatedCourseKey
from
.
import
ModuleStoreWriteBase
from
.
import
ModuleStoreWriteBase
from
.
import
ModuleStoreEnum
from
.
import
ModuleStoreEnum
from
.exceptions
import
ItemNotFoundError
from
.exceptions
import
ItemNotFoundError
,
DuplicateCourseError
from
.draft_and_published
import
ModuleStoreDraftAndPublished
from
.draft_and_published
import
ModuleStoreDraftAndPublished
from
.split_migrator
import
SplitMigrator
from
.split_migrator
import
SplitMigrator
...
@@ -23,6 +24,69 @@ from .split_migrator import SplitMigrator
...
@@ -23,6 +24,69 @@ from .split_migrator import SplitMigrator
log
=
logging
.
getLogger
(
__name__
)
log
=
logging
.
getLogger
(
__name__
)
def
strip_key
(
func
):
"""
A decorator for stripping version and branch information from return values that are, or contain, UsageKeys or
CourseKeys.
Additionally, the decorated function is called with an optional 'field_decorator' parameter that can be used
to strip any location(-containing) fields, which are not directly returned by the function.
The behavior can be controlled by passing 'remove_version' and 'remove_branch' booleans to the decorated
function's kwargs.
"""
@functools.wraps
(
func
)
def
inner
(
*
args
,
**
kwargs
):
"""
Supported kwargs:
remove_version - If True, calls 'version_agnostic' on all return values, including those in lists and dicts.
remove_branch - If True, calls 'for_branch(None)' on all return values, including those in lists and dicts.
Note: The 'field_decorator' parameter passed to the decorated function is a function that honors the
values of these kwargs.
"""
# remove version and branch, by default
rem_vers
=
kwargs
.
pop
(
'remove_version'
,
True
)
rem_branch
=
kwargs
.
pop
(
'remove_branch'
,
False
)
# helper function for stripping individual values
def
strip_key_func
(
val
):
"""
Strips the version and branch information according to the settings of rem_vers and rem_branch.
Recursively calls this function if the given value has a 'location' attribute.
"""
retval
=
val
if
rem_vers
and
hasattr
(
retval
,
'version_agnostic'
):
retval
=
retval
.
version_agnostic
()
if
rem_branch
and
hasattr
(
retval
,
'for_branch'
):
retval
=
retval
.
for_branch
(
None
)
if
hasattr
(
retval
,
'location'
):
retval
.
location
=
strip_key_func
(
retval
.
location
)
return
retval
# function for stripping both, collection of, and individual, values
def
strip_key_collection
(
field_value
):
"""
Calls strip_key_func for each element in the given value.
"""
if
rem_vers
or
rem_branch
:
if
isinstance
(
field_value
,
list
):
field_value
=
[
strip_key_func
(
fv
)
for
fv
in
field_value
]
elif
isinstance
(
field_value
,
dict
):
for
key
,
val
in
field_value
.
iteritems
():
field_value
[
key
]
=
strip_key_func
(
val
)
else
:
field_value
=
strip_key_func
(
field_value
)
return
field_value
# call the decorated function
retval
=
func
(
field_decorator
=
strip_key_collection
,
*
args
,
**
kwargs
)
# strip the return value
return
strip_key_collection
(
retval
)
return
inner
class
MixedModuleStore
(
ModuleStoreDraftAndPublished
,
ModuleStoreWriteBase
):
class
MixedModuleStore
(
ModuleStoreDraftAndPublished
,
ModuleStoreWriteBase
):
"""
"""
ModuleStore knows how to route requests to the right persistence ms
ModuleStore knows how to route requests to the right persistence ms
...
@@ -100,12 +164,12 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
...
@@ -100,12 +164,12 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
return
mapping
return
mapping
else
:
else
:
for
store
in
self
.
modulestores
:
for
store
in
self
.
modulestores
:
if
isinstance
(
course_id
,
store
.
reference_type
)
and
store
.
has_course
(
course_id
):
if
store
.
has_course
(
course_id
):
self
.
mappings
[
course_id
]
=
store
self
.
mappings
[
course_id
]
=
store
return
store
return
store
# return the
first store, as the default
# return the
default store
return
self
.
modulestores
[
0
]
return
self
.
default_modulestore
def
_get_modulestore_by_type
(
self
,
modulestore_type
):
def
_get_modulestore_by_type
(
self
,
modulestore_type
):
"""
"""
...
@@ -127,7 +191,6 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
...
@@ -127,7 +191,6 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
return
course_key
return
course_key
return
store
.
fill_in_run
(
course_key
)
return
store
.
fill_in_run
(
course_key
)
def
has_item
(
self
,
usage_key
,
**
kwargs
):
def
has_item
(
self
,
usage_key
,
**
kwargs
):
"""
"""
Does the course include the xblock who's id is reference?
Does the course include the xblock who's id is reference?
...
@@ -135,6 +198,7 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
...
@@ -135,6 +198,7 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
store
=
self
.
_get_modulestore_for_courseid
(
usage_key
.
course_key
)
store
=
self
.
_get_modulestore_for_courseid
(
usage_key
.
course_key
)
return
store
.
has_item
(
usage_key
,
**
kwargs
)
return
store
.
has_item
(
usage_key
,
**
kwargs
)
@strip_key
def
get_item
(
self
,
usage_key
,
depth
=
0
,
**
kwargs
):
def
get_item
(
self
,
usage_key
,
depth
=
0
,
**
kwargs
):
"""
"""
see parent doc
see parent doc
...
@@ -142,6 +206,7 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
...
@@ -142,6 +206,7 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
store
=
self
.
_get_modulestore_for_courseid
(
usage_key
.
course_key
)
store
=
self
.
_get_modulestore_for_courseid
(
usage_key
.
course_key
)
return
store
.
get_item
(
usage_key
,
depth
,
**
kwargs
)
return
store
.
get_item
(
usage_key
,
depth
,
**
kwargs
)
@strip_key
def
get_items
(
self
,
course_key
,
**
kwargs
):
def
get_items
(
self
,
course_key
,
**
kwargs
):
"""
"""
Returns:
Returns:
...
@@ -158,7 +223,7 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
...
@@ -158,7 +223,7 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
and rules as kwargs below
and rules as kwargs below
content (dict): fields to look for which have content scope. Follows same syntax and
content (dict): fields to look for which have content scope. Follows same syntax and
rules as kwargs below.
rules as kwargs below.
additional kwargs (key=value
): what to look for within the course.
qualifiers (dict
): what to look for within the course.
Common qualifiers are ``category`` or any field name. if the target field is a list,
Common qualifiers are ``category`` or any field name. if the target field is a list,
then it searches for the given value in the list not list equivalence.
then it searches for the given value in the list not list equivalence.
Substring matching pass a regex object.
Substring matching pass a regex object.
...
@@ -173,32 +238,39 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
...
@@ -173,32 +238,39 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
store
=
self
.
_get_modulestore_for_courseid
(
course_key
)
store
=
self
.
_get_modulestore_for_courseid
(
course_key
)
return
store
.
get_items
(
course_key
,
**
kwargs
)
return
store
.
get_items
(
course_key
,
**
kwargs
)
def
get_courses
(
self
):
@strip_key
def
get_courses
(
self
,
**
kwargs
):
'''
'''
Returns a list containing the top level XModuleDescriptors of the courses in this modulestore.
Returns a list containing the top level XModuleDescriptors of the courses in this modulestore.
'''
'''
courses
=
{}
# a dictionary of course keys to course objects
courses
=
{}
# first populate with the ones in mappings as the mapping override discovery
for
course_id
,
store
in
self
.
mappings
.
iteritems
():
course
=
store
.
get_course
(
course_id
)
# check if the course is not None - possible if the mappings file is outdated
# TODO - log an error if the course is None, but move it to an initialization method to keep it less noisy
if
course
is
not
None
:
courses
[
course_id
]
=
course
for
store
in
self
.
modulestores
:
for
store
in
self
.
modulestores
:
# filter out ones which were fetched from earlier stores but locations may not be ==
# filter out ones which were fetched from earlier stores but locations may not be ==
for
course
in
store
.
get_courses
():
for
course
in
store
.
get_courses
(
**
kwargs
):
course_id
=
self
.
_clean_course_id_for_mapping
(
course
.
id
)
course_id
=
self
.
_clean_course_id_for_mapping
(
course
.
id
)
if
course_id
not
in
courses
:
if
course_id
not
in
courses
:
# course is indeed unique. save it in result
# course is indeed unique. save it in result
courses
[
course_id
]
=
course
courses
[
course_id
]
=
course
return
courses
.
values
()
return
courses
.
values
()
def
get_course
(
self
,
course_key
,
depth
=
0
):
def
make_course_key
(
self
,
org
,
course
,
run
):
"""
Return a valid :class:`~opaque_keys.edx.keys.CourseKey` for this modulestore
that matches the supplied `org`, `course`, and `run`.
This key may represent a course that doesn't exist in this modulestore.
"""
# If there is a mapping that match this org/course/run, use that
for
course_id
,
store
in
self
.
mappings
.
iteritems
():
candidate_key
=
store
.
make_course_key
(
org
,
course
,
run
)
if
candidate_key
==
course_id
:
return
candidate_key
# Otherwise, return the key created by the default store
return
self
.
default_modulestore
.
make_course_key
(
org
,
course
,
run
)
@strip_key
def
get_course
(
self
,
course_key
,
depth
=
0
,
**
kwargs
):
"""
"""
returns the course module associated with the course_id. If no such course exists,
returns the course module associated with the course_id. If no such course exists,
it returns None
it returns None
...
@@ -208,11 +280,12 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
...
@@ -208,11 +280,12 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
assert
(
isinstance
(
course_key
,
CourseKey
))
assert
(
isinstance
(
course_key
,
CourseKey
))
store
=
self
.
_get_modulestore_for_courseid
(
course_key
)
store
=
self
.
_get_modulestore_for_courseid
(
course_key
)
try
:
try
:
return
store
.
get_course
(
course_key
,
depth
=
depth
)
return
store
.
get_course
(
course_key
,
depth
=
depth
,
**
kwargs
)
except
ItemNotFoundError
:
except
ItemNotFoundError
:
return
None
return
None
def
has_course
(
self
,
course_id
,
ignore_case
=
False
):
@strip_key
def
has_course
(
self
,
course_id
,
ignore_case
=
False
,
**
kwargs
):
"""
"""
returns the course_id of the course if it was found, else None
returns the course_id of the course if it was found, else None
Note: we return the course_id instead of a boolean here since the found course may have
Note: we return the course_id instead of a boolean here since the found course may have
...
@@ -225,7 +298,7 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
...
@@ -225,7 +298,7 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
"""
"""
assert
(
isinstance
(
course_id
,
CourseKey
))
assert
(
isinstance
(
course_id
,
CourseKey
))
store
=
self
.
_get_modulestore_for_courseid
(
course_id
)
store
=
self
.
_get_modulestore_for_courseid
(
course_id
)
return
store
.
has_course
(
course_id
,
ignore_case
)
return
store
.
has_course
(
course_id
,
ignore_case
,
**
kwargs
)
def
delete_course
(
self
,
course_key
,
user_id
):
def
delete_course
(
self
,
course_key
,
user_id
):
"""
"""
...
@@ -235,6 +308,7 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
...
@@ -235,6 +308,7 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
store
=
self
.
_get_modulestore_for_courseid
(
course_key
)
store
=
self
.
_get_modulestore_for_courseid
(
course_key
)
return
store
.
delete_course
(
course_key
,
user_id
)
return
store
.
delete_course
(
course_key
,
user_id
)
@strip_key
def
get_parent_location
(
self
,
location
,
**
kwargs
):
def
get_parent_location
(
self
,
location
,
**
kwargs
):
"""
"""
returns the parent locations for a given location
returns the parent locations for a given location
...
@@ -252,14 +326,15 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
...
@@ -252,14 +326,15 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
"""
"""
return
self
.
_get_modulestore_for_courseid
(
course_id
)
.
get_modulestore_type
()
return
self
.
_get_modulestore_for_courseid
(
course_id
)
.
get_modulestore_type
()
def
get_orphans
(
self
,
course_key
):
@strip_key
def
get_orphans
(
self
,
course_key
,
**
kwargs
):
"""
"""
Get all of the xblocks in the given course which have no parents and are not of types which are
Get all of the xblocks in the given course which have no parents and are not of types which are
usually orphaned. NOTE: may include xblocks which still have references via xblocks which don't
usually orphaned. NOTE: may include xblocks which still have references via xblocks which don't
use children to point to their dependents.
use children to point to their dependents.
"""
"""
store
=
self
.
_get_modulestore_for_courseid
(
course_key
)
store
=
self
.
_get_modulestore_for_courseid
(
course_key
)
return
store
.
get_orphans
(
course_key
)
return
store
.
get_orphans
(
course_key
,
**
kwargs
)
def
get_errored_courses
(
self
):
def
get_errored_courses
(
self
):
"""
"""
...
@@ -271,6 +346,7 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
...
@@ -271,6 +346,7 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
errs
.
update
(
store
.
get_errored_courses
())
errs
.
update
(
store
.
get_errored_courses
())
return
errs
return
errs
@strip_key
def
create_course
(
self
,
org
,
course
,
run
,
user_id
,
**
kwargs
):
def
create_course
(
self
,
org
,
course
,
run
,
user_id
,
**
kwargs
):
"""
"""
Creates and returns the course.
Creates and returns the course.
...
@@ -285,10 +361,22 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
...
@@ -285,10 +361,22 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
Returns: a CourseDescriptor
Returns: a CourseDescriptor
"""
"""
# first make sure an existing course doesn't already exist in the mapping
course_key
=
self
.
make_course_key
(
org
,
course
,
run
)
if
course_key
in
self
.
mappings
:
raise
DuplicateCourseError
(
course_key
,
course_key
)
# create the course
store
=
self
.
_verify_modulestore_support
(
None
,
'create_course'
)
store
=
self
.
_verify_modulestore_support
(
None
,
'create_course'
)
return
store
.
create_course
(
org
,
course
,
run
,
user_id
,
**
kwargs
)
course
=
store
.
create_course
(
org
,
course
,
run
,
user_id
,
**
kwargs
)
def
clone_course
(
self
,
source_course_id
,
dest_course_id
,
user_id
,
fields
=
None
):
# add new course to the mapping
self
.
mappings
[
course_key
]
=
store
return
course
@strip_key
def
clone_course
(
self
,
source_course_id
,
dest_course_id
,
user_id
,
fields
=
None
,
**
kwargs
):
"""
"""
See the superclass for the general documentation.
See the superclass for the general documentation.
...
@@ -303,18 +391,19 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
...
@@ -303,18 +391,19 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
# to have only course re-runs go to split. This code, however, uses the config'd priority
# to have only course re-runs go to split. This code, however, uses the config'd priority
dest_modulestore
=
self
.
_get_modulestore_for_courseid
(
dest_course_id
)
dest_modulestore
=
self
.
_get_modulestore_for_courseid
(
dest_course_id
)
if
source_modulestore
==
dest_modulestore
:
if
source_modulestore
==
dest_modulestore
:
return
source_modulestore
.
clone_course
(
source_course_id
,
dest_course_id
,
user_id
,
fields
)
return
source_modulestore
.
clone_course
(
source_course_id
,
dest_course_id
,
user_id
,
fields
,
**
kwargs
)
# ensure super's only called once. The delegation above probably calls it; so, don't move
# ensure super's only called once. The delegation above probably calls it; so, don't move
# the invocation above the delegation call
# the invocation above the delegation call
super
(
MixedModuleStore
,
self
)
.
clone_course
(
source_course_id
,
dest_course_id
,
user_id
,
fields
)
super
(
MixedModuleStore
,
self
)
.
clone_course
(
source_course_id
,
dest_course_id
,
user_id
,
fields
,
**
kwargs
)
if
dest_modulestore
.
get_modulestore_type
()
==
ModuleStoreEnum
.
Type
.
split
:
if
dest_modulestore
.
get_modulestore_type
()
==
ModuleStoreEnum
.
Type
.
split
:
split_migrator
=
SplitMigrator
(
dest_modulestore
,
source_modulestore
)
split_migrator
=
SplitMigrator
(
dest_modulestore
,
source_modulestore
)
split_migrator
.
migrate_mongo_course
(
split_migrator
.
migrate_mongo_course
(
source_course_id
,
user_id
,
dest_course_id
.
org
,
dest_course_id
.
course
,
dest_course_id
.
run
,
fields
source_course_id
,
user_id
,
dest_course_id
.
org
,
dest_course_id
.
course
,
dest_course_id
.
run
,
fields
,
**
kwargs
)
)
@strip_key
def
create_item
(
self
,
user_id
,
course_key
,
block_type
,
block_id
=
None
,
fields
=
None
,
**
kwargs
):
def
create_item
(
self
,
user_id
,
course_key
,
block_type
,
block_id
=
None
,
fields
=
None
,
**
kwargs
):
"""
"""
Creates and saves a new item in a course.
Creates and saves a new item in a course.
...
@@ -334,6 +423,7 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
...
@@ -334,6 +423,7 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
modulestore
=
self
.
_verify_modulestore_support
(
course_key
,
'create_item'
)
modulestore
=
self
.
_verify_modulestore_support
(
course_key
,
'create_item'
)
return
modulestore
.
create_item
(
user_id
,
course_key
,
block_type
,
block_id
=
block_id
,
fields
=
fields
,
**
kwargs
)
return
modulestore
.
create_item
(
user_id
,
course_key
,
block_type
,
block_id
=
block_id
,
fields
=
fields
,
**
kwargs
)
@strip_key
def
create_child
(
self
,
user_id
,
parent_usage_key
,
block_type
,
block_id
=
None
,
fields
=
None
,
**
kwargs
):
def
create_child
(
self
,
user_id
,
parent_usage_key
,
block_type
,
block_id
=
None
,
fields
=
None
,
**
kwargs
):
"""
"""
Creates and saves a new xblock that is a child of the specified block
Creates and saves a new xblock that is a child of the specified block
...
@@ -353,20 +443,22 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
...
@@ -353,20 +443,22 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
modulestore
=
self
.
_verify_modulestore_support
(
parent_usage_key
.
course_key
,
'create_child'
)
modulestore
=
self
.
_verify_modulestore_support
(
parent_usage_key
.
course_key
,
'create_child'
)
return
modulestore
.
create_child
(
user_id
,
parent_usage_key
,
block_type
,
block_id
=
block_id
,
fields
=
fields
,
**
kwargs
)
return
modulestore
.
create_child
(
user_id
,
parent_usage_key
,
block_type
,
block_id
=
block_id
,
fields
=
fields
,
**
kwargs
)
def
update_item
(
self
,
xblock
,
user_id
,
allow_not_found
=
False
):
@strip_key
def
update_item
(
self
,
xblock
,
user_id
,
allow_not_found
=
False
,
**
kwargs
):
"""
"""
Update the xblock persisted to be the same as the given for all types of fields
Update the xblock persisted to be the same as the given for all types of fields
(content, children, and metadata) attribute the change to the given user.
(content, children, and metadata) attribute the change to the given user.
"""
"""
store
=
self
.
_verify_modulestore_support
(
xblock
.
location
.
course_key
,
'update_item'
)
store
=
self
.
_verify_modulestore_support
(
xblock
.
location
.
course_key
,
'update_item'
)
return
store
.
update_item
(
xblock
,
user_id
,
allow_not_found
)
return
store
.
update_item
(
xblock
,
user_id
,
allow_not_found
,
**
kwargs
)
@strip_key
def
delete_item
(
self
,
location
,
user_id
,
**
kwargs
):
def
delete_item
(
self
,
location
,
user_id
,
**
kwargs
):
"""
"""
Delete the given item from persistence. kwargs allow modulestore specific parameters.
Delete the given item from persistence. kwargs allow modulestore specific parameters.
"""
"""
store
=
self
.
_verify_modulestore_support
(
location
.
course_key
,
'delete_item'
)
store
=
self
.
_verify_modulestore_support
(
location
.
course_key
,
'delete_item'
)
store
.
delete_item
(
location
,
user_id
=
user_id
,
**
kwargs
)
return
store
.
delete_item
(
location
,
user_id
=
user_id
,
**
kwargs
)
def
revert_to_published
(
self
,
location
,
user_id
):
def
revert_to_published
(
self
,
location
,
user_id
):
"""
"""
...
@@ -398,6 +490,7 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
...
@@ -398,6 +490,7 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
if
hasattr
(
modulestore
,
'_drop_database'
):
if
hasattr
(
modulestore
,
'_drop_database'
):
modulestore
.
_drop_database
()
# pylint: disable=protected-access
modulestore
.
_drop_database
()
# pylint: disable=protected-access
@strip_key
def
create_xmodule
(
self
,
location
,
definition_data
=
None
,
metadata
=
None
,
runtime
=
None
,
fields
=
{},
**
kwargs
):
def
create_xmodule
(
self
,
location
,
definition_data
=
None
,
metadata
=
None
,
runtime
=
None
,
fields
=
{},
**
kwargs
):
"""
"""
Create the new xmodule but don't save it. Returns the new module.
Create the new xmodule but don't save it. Returns the new module.
...
@@ -411,7 +504,8 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
...
@@ -411,7 +504,8 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
store
=
self
.
_verify_modulestore_support
(
location
.
course_key
,
'create_xmodule'
)
store
=
self
.
_verify_modulestore_support
(
location
.
course_key
,
'create_xmodule'
)
return
store
.
create_xmodule
(
location
,
definition_data
,
metadata
,
runtime
,
fields
,
**
kwargs
)
return
store
.
create_xmodule
(
location
,
definition_data
,
metadata
,
runtime
,
fields
,
**
kwargs
)
def
get_courses_for_wiki
(
self
,
wiki_slug
):
@strip_key
def
get_courses_for_wiki
(
self
,
wiki_slug
,
**
kwargs
):
"""
"""
Return the list of courses which use this wiki_slug
Return the list of courses which use this wiki_slug
:param wiki_slug: the course wiki root slug
:param wiki_slug: the course wiki root slug
...
@@ -419,7 +513,7 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
...
@@ -419,7 +513,7 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
"""
"""
courses
=
[]
courses
=
[]
for
modulestore
in
self
.
modulestores
:
for
modulestore
in
self
.
modulestores
:
courses
.
extend
(
modulestore
.
get_courses_for_wiki
(
wiki_slug
))
courses
.
extend
(
modulestore
.
get_courses_for_wiki
(
wiki_slug
,
**
kwargs
))
return
courses
return
courses
def
heartbeat
(
self
):
def
heartbeat
(
self
):
...
@@ -448,21 +542,23 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
...
@@ -448,21 +542,23 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
store
=
self
.
_get_modulestore_for_courseid
(
course_id
)
store
=
self
.
_get_modulestore_for_courseid
(
course_id
)
return
store
.
compute_publish_state
(
xblock
)
return
store
.
compute_publish_state
(
xblock
)
def
publish
(
self
,
location
,
user_id
):
@strip_key
def
publish
(
self
,
location
,
user_id
,
**
kwargs
):
"""
"""
Save a current draft to the underlying modulestore
Save a current draft to the underlying modulestore
Returns the newly published item.
Returns the newly published item.
"""
"""
store
=
self
.
_verify_modulestore_support
(
location
.
course_key
,
'publish'
)
store
=
self
.
_verify_modulestore_support
(
location
.
course_key
,
'publish'
)
return
store
.
publish
(
location
,
user_id
)
return
store
.
publish
(
location
,
user_id
,
**
kwargs
)
def
unpublish
(
self
,
location
,
user_id
):
@strip_key
def
unpublish
(
self
,
location
,
user_id
,
**
kwargs
):
"""
"""
Save a current draft to the underlying modulestore
Save a current draft to the underlying modulestore
Returns the newly unpublished item.
Returns the newly unpublished item.
"""
"""
store
=
self
.
_verify_modulestore_support
(
location
.
course_key
,
'unpublish'
)
store
=
self
.
_verify_modulestore_support
(
location
.
course_key
,
'unpublish'
)
return
store
.
unpublish
(
location
,
user_id
)
return
store
.
unpublish
(
location
,
user_id
,
**
kwargs
)
def
convert_to_draft
(
self
,
location
,
user_id
):
def
convert_to_draft
(
self
,
location
,
user_id
):
"""
"""
...
@@ -496,23 +592,35 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
...
@@ -496,23 +592,35 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
else
:
else
:
raise
NotImplementedError
(
u"Cannot call {} on store {}"
.
format
(
method
,
store
))
raise
NotImplementedError
(
u"Cannot call {} on store {}"
.
format
(
method
,
store
))
@property
def
default_modulestore
(
self
):
"""
Return the default modulestore
"""
thread_local_default_store
=
getattr
(
self
.
thread_cache
,
'default_store'
,
None
)
if
thread_local_default_store
:
# return the thread-local cache, if found
return
thread_local_default_store
else
:
# else return the default store
return
self
.
modulestores
[
0
]
@contextmanager
@contextmanager
def
default_store
(
self
,
store_type
):
def
default_store
(
self
,
store_type
):
"""
"""
A context manager for temporarily changing the default store in the Mixed modulestore to the given store type
A context manager for temporarily changing the default store in the Mixed modulestore to the given store type
"""
"""
previous_store_list
=
self
.
modulestores
# find the store corresponding to the given type
found
=
False
store
=
next
((
store
for
store
in
self
.
modulestores
if
store
.
get_modulestore_type
()
==
store_type
),
None
)
if
not
store
:
raise
Exception
(
u"Cannot find store of type {}"
.
format
(
store_type
))
prev_thread_local_store
=
getattr
(
self
.
thread_cache
,
'default_store'
,
None
)
try
:
try
:
for
i
,
store
in
enumerate
(
self
.
modulestores
):
self
.
thread_cache
.
default_store
=
store
if
store
.
get_modulestore_type
()
==
store_type
:
self
.
modulestores
.
insert
(
0
,
self
.
modulestores
.
pop
(
i
))
found
=
True
yield
yield
if
not
found
:
raise
Exception
(
u"Cannot find store of type {}"
.
format
(
store_type
))
finally
:
finally
:
self
.
modulestores
=
previous_store_list
self
.
thread_cache
.
default_store
=
prev_thread_local_store
@contextmanager
@contextmanager
def
branch_setting
(
self
,
branch_setting
,
course_id
=
None
):
def
branch_setting
(
self
,
branch_setting
,
course_id
=
None
):
...
...
common/lib/xmodule/xmodule/modulestore/modulestore_settings.py
View file @
9efe5d92
...
@@ -3,6 +3,7 @@ This file contains helper functions for configuring module_store_setting setting
...
@@ -3,6 +3,7 @@ This file contains helper functions for configuring module_store_setting setting
"""
"""
import
warnings
import
warnings
import
copy
def
convert_module_store_setting_if_needed
(
module_store_setting
):
def
convert_module_store_setting_if_needed
(
module_store_setting
):
...
@@ -42,7 +43,6 @@ def convert_module_store_setting_if_needed(module_store_setting):
...
@@ -42,7 +43,6 @@ def convert_module_store_setting_if_needed(module_store_setting):
"ENGINE"
:
"xmodule.modulestore.mixed.MixedModuleStore"
,
"ENGINE"
:
"xmodule.modulestore.mixed.MixedModuleStore"
,
"OPTIONS"
:
{
"OPTIONS"
:
{
"mappings"
:
{},
"mappings"
:
{},
"reference_type"
:
"Location"
,
"stores"
:
[]
"stores"
:
[]
}
}
}
}
...
@@ -66,6 +66,27 @@ def convert_module_store_setting_if_needed(module_store_setting):
...
@@ -66,6 +66,27 @@ def convert_module_store_setting_if_needed(module_store_setting):
)
)
assert
isinstance
(
module_store_setting
[
'default'
][
'OPTIONS'
][
'stores'
],
list
)
assert
isinstance
(
module_store_setting
[
'default'
][
'OPTIONS'
][
'stores'
],
list
)
# If Split is not defined but the DraftMongoModuleStore is configured, add Split as a copy of Draft
mixed_stores
=
module_store_setting
[
'default'
][
'OPTIONS'
][
'stores'
]
is_split_defined
=
any
((
store
[
'ENGINE'
]
.
endswith
(
'.DraftVersioningModuleStore'
))
for
store
in
mixed_stores
)
if
not
is_split_defined
:
# find first setting of mongo store
mongo_store
=
next
(
(
store
for
store
in
mixed_stores
if
(
store
[
'ENGINE'
]
.
endswith
(
'.DraftMongoModuleStore'
)
or
store
[
'ENGINE'
]
.
endswith
(
'.DraftModuleStore'
)
)),
None
)
if
mongo_store
:
# deepcopy mongo -> split
split_store
=
copy
.
deepcopy
(
mongo_store
)
# update the ENGINE and NAME fields
split_store
[
'ENGINE'
]
=
'xmodule.modulestore.split_mongo.split_draft.DraftVersioningModuleStore'
split_store
[
'NAME'
]
=
'split'
# add split to the end of the list
mixed_stores
.
append
(
split_store
)
return
module_store_setting
return
module_store_setting
...
...
common/lib/xmodule/xmodule/modulestore/mongo/base.py
View file @
9efe5d92
...
@@ -37,10 +37,11 @@ from xblock.fields import Scope, ScopeIds, Reference, ReferenceList, ReferenceVa
...
@@ -37,10 +37,11 @@ from xblock.fields import Scope, ScopeIds, Reference, ReferenceList, ReferenceVa
from
xmodule.modulestore
import
ModuleStoreWriteBase
,
ModuleStoreEnum
from
xmodule.modulestore
import
ModuleStoreWriteBase
,
ModuleStoreEnum
from
xmodule.modulestore.draft_and_published
import
ModuleStoreDraftAndPublished
,
DIRECT_ONLY_CATEGORIES
from
xmodule.modulestore.draft_and_published
import
ModuleStoreDraftAndPublished
,
DIRECT_ONLY_CATEGORIES
from
opaque_keys.edx.locations
import
Location
from
opaque_keys.edx.locations
import
Location
from
xmodule.modulestore.exceptions
import
ItemNotFoundError
,
InvalidLocation
Error
,
ReferentialIntegrityError
from
xmodule.modulestore.exceptions
import
ItemNotFoundError
,
DuplicateCourse
Error
,
ReferentialIntegrityError
from
xmodule.modulestore.inheritance
import
own_metadata
,
InheritanceMixin
,
inherit_metadata
,
InheritanceKeyValueStore
from
xmodule.modulestore.inheritance
import
own_metadata
,
InheritanceMixin
,
inherit_metadata
,
InheritanceKeyValueStore
from
xblock.core
import
XBlock
from
xblock.core
import
XBlock
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
from
opaque_keys.edx.locator
import
CourseLocator
from
opaque_keys.edx.keys
import
UsageKey
,
CourseKey
from
opaque_keys.edx.keys
import
UsageKey
,
CourseKey
from
xmodule.exceptions
import
HeartbeatFailure
from
xmodule.exceptions
import
HeartbeatFailure
...
@@ -354,8 +355,6 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
...
@@ -354,8 +355,6 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
"""
"""
A Mongodb backed ModuleStore
A Mongodb backed ModuleStore
"""
"""
reference_type
=
SlashSeparatedCourseKey
# TODO (cpennington): Enable non-filesystem filestores
# TODO (cpennington): Enable non-filesystem filestores
# pylint: disable=C0103
# pylint: disable=C0103
# pylint: disable=W0201
# pylint: disable=W0201
...
@@ -716,7 +715,7 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
...
@@ -716,7 +715,7 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
for
item
in
items
for
item
in
items
]
]
def
get_courses
(
self
):
def
get_courses
(
self
,
**
kwargs
):
'''
'''
Returns a list of course descriptors.
Returns a list of course descriptors.
'''
'''
...
@@ -751,7 +750,16 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
...
@@ -751,7 +750,16 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
raise
ItemNotFoundError
(
location
)
raise
ItemNotFoundError
(
location
)
return
item
return
item
def
get_course
(
self
,
course_key
,
depth
=
0
):
def
make_course_key
(
self
,
org
,
course
,
run
):
"""
Return a valid :class:`~opaque_keys.edx.keys.CourseKey` for this modulestore
that matches the supplied `org`, `course`, and `run`.
This key may represent a course that doesn't exist in this modulestore.
"""
return
CourseLocator
(
org
,
course
,
run
,
deprecated
=
True
)
def
get_course
(
self
,
course_key
,
depth
=
0
,
**
kwargs
):
"""
"""
Get the course with the given courseid (org/course/run)
Get the course with the given courseid (org/course/run)
"""
"""
...
@@ -763,7 +771,7 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
...
@@ -763,7 +771,7 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
except
ItemNotFoundError
:
except
ItemNotFoundError
:
return
None
return
None
def
has_course
(
self
,
course_key
,
ignore_case
=
False
):
def
has_course
(
self
,
course_key
,
ignore_case
=
False
,
**
kwargs
):
"""
"""
Returns the course_id of the course if it was found, else None
Returns the course_id of the course if it was found, else None
Note: we return the course_id instead of a boolean here since the found course may have
Note: we return the course_id instead of a boolean here since the found course may have
...
@@ -838,7 +846,15 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
...
@@ -838,7 +846,15 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
for
key
in
(
'tag'
,
'org'
,
'course'
,
'category'
,
'name'
,
'revision'
)
for
key
in
(
'tag'
,
'org'
,
'course'
,
'category'
,
'name'
,
'revision'
)
])
])
def
get_items
(
self
,
course_id
,
settings
=
None
,
content
=
None
,
key_revision
=
MongoRevisionKey
.
published
,
**
kwargs
):
def
get_items
(
self
,
course_id
,
settings
=
None
,
content
=
None
,
key_revision
=
MongoRevisionKey
.
published
,
qualifiers
=
None
,
**
kwargs
):
"""
"""
Returns:
Returns:
list of XModuleDescriptor instances for the matching items within the course with
list of XModuleDescriptor instances for the matching items within the course with
...
@@ -853,15 +869,15 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
...
@@ -853,15 +869,15 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
Args:
Args:
course_id (CourseKey): the course identifier
course_id (CourseKey): the course identifier
settings (dict): fields to look for which have settings scope. Follows same syntax
settings (dict): fields to look for which have settings scope. Follows same syntax
and rules as
kwarg
s below
and rules as
qualifier
s below
content (dict): fields to look for which have content scope. Follows same syntax and
content (dict): fields to look for which have content scope. Follows same syntax and
rules as
kwarg
s below.
rules as
qualifier
s below.
key_revision (str): the revision of the items you're looking for.
key_revision (str): the revision of the items you're looking for.
MongoRevisionKey.draft - only returns drafts
MongoRevisionKey.draft - only returns drafts
MongoRevisionKey.published (equates to None) - only returns published
MongoRevisionKey.published (equates to None) - only returns published
If you want one of each matching xblock but preferring draft to published, call this same method
If you want one of each matching xblock but preferring draft to published, call this same method
on the draft modulestore with ModuleStoreEnum.RevisionOption.draft_preferred.
on the draft modulestore with ModuleStoreEnum.RevisionOption.draft_preferred.
kwargs (key=value
): what to look for within the course.
qualifiers (dict
): what to look for within the course.
Common qualifiers are ``category`` or any field name. if the target field is a list,
Common qualifiers are ``category`` or any field name. if the target field is a list,
then it searches for the given value in the list not list equivalence.
then it searches for the given value in the list not list equivalence.
Substring matching pass a regex object.
Substring matching pass a regex object.
...
@@ -869,20 +885,21 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
...
@@ -869,20 +885,21 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
This modulestore does not allow searching dates by comparison or edited_by, previous_version,
This modulestore does not allow searching dates by comparison or edited_by, previous_version,
update_version info.
update_version info.
"""
"""
qualifiers
=
qualifiers
.
copy
()
if
qualifiers
else
{}
# copy the qualifiers (destructively manipulated here)
query
=
self
.
_course_key_to_son
(
course_id
)
query
=
self
.
_course_key_to_son
(
course_id
)
query
[
'_id.revision'
]
=
key_revision
query
[
'_id.revision'
]
=
key_revision
for
field
in
[
'category'
,
'name'
]:
for
field
in
[
'category'
,
'name'
]:
if
field
in
kwarg
s
:
if
field
in
qualifier
s
:
query
[
'_id.'
+
field
]
=
kwarg
s
.
pop
(
field
)
query
[
'_id.'
+
field
]
=
qualifier
s
.
pop
(
field
)
for
key
,
value
in
(
settings
or
{})
.
iteritems
():
for
key
,
value
in
(
settings
or
{})
.
iteritems
():
query
[
'metadata.'
+
key
]
=
value
query
[
'metadata.'
+
key
]
=
value
for
key
,
value
in
(
content
or
{})
.
iteritems
():
for
key
,
value
in
(
content
or
{})
.
iteritems
():
query
[
'definition.data.'
+
key
]
=
value
query
[
'definition.data.'
+
key
]
=
value
if
'children'
in
kwarg
s
:
if
'children'
in
qualifier
s
:
query
[
'definition.children'
]
=
kwarg
s
.
pop
(
'children'
)
query
[
'definition.children'
]
=
qualifier
s
.
pop
(
'children'
)
query
.
update
(
kwarg
s
)
query
.
update
(
qualifier
s
)
items
=
self
.
collection
.
find
(
items
=
self
.
collection
.
find
(
query
,
query
,
sort
=
[
SORT_REVISION_FAVOR_DRAFT
],
sort
=
[
SORT_REVISION_FAVOR_DRAFT
],
...
@@ -919,10 +936,7 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
...
@@ -919,10 +936,7 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
])
])
courses
=
self
.
collection
.
find
(
course_search_location
,
fields
=
(
'_id'
))
courses
=
self
.
collection
.
find
(
course_search_location
,
fields
=
(
'_id'
))
if
courses
.
count
()
>
0
:
if
courses
.
count
()
>
0
:
raise
InvalidLocationError
(
raise
DuplicateCourseError
(
course_id
,
courses
[
0
][
'_id'
])
"There are already courses with the given org and course id: {}"
.
format
([
course
[
'_id'
]
for
course
in
courses
]))
location
=
course_id
.
make_usage_key
(
'course'
,
course_id
.
run
)
location
=
course_id
.
make_usage_key
(
'course'
,
course_id
.
run
)
course
=
self
.
create_xmodule
(
course
=
self
.
create_xmodule
(
...
@@ -1253,7 +1267,7 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
...
@@ -1253,7 +1267,7 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
"""
"""
return
ModuleStoreEnum
.
Type
.
mongo
return
ModuleStoreEnum
.
Type
.
mongo
def
get_orphans
(
self
,
course_key
):
def
get_orphans
(
self
,
course_key
,
**
kwargs
):
"""
"""
Return an array of all of the locations for orphans in the course.
Return an array of all of the locations for orphans in the course.
"""
"""
...
@@ -1274,7 +1288,7 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
...
@@ -1274,7 +1288,7 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
item_locs
-=
all_reachable
item_locs
-=
all_reachable
return
[
course_key
.
make_usage_key_from_deprecated_string
(
item_loc
)
for
item_loc
in
item_locs
]
return
[
course_key
.
make_usage_key_from_deprecated_string
(
item_loc
)
for
item_loc
in
item_locs
]
def
get_courses_for_wiki
(
self
,
wiki_slug
):
def
get_courses_for_wiki
(
self
,
wiki_slug
,
**
kwargs
):
"""
"""
Return the list of courses which use this wiki_slug
Return the list of courses which use this wiki_slug
:param wiki_slug: the course wiki root slug
:param wiki_slug: the course wiki root slug
...
...
common/lib/xmodule/xmodule/modulestore/mongo/draft.py
View file @
9efe5d92
...
@@ -47,7 +47,7 @@ class DraftModuleStore(MongoModuleStore):
...
@@ -47,7 +47,7 @@ class DraftModuleStore(MongoModuleStore):
This module also includes functionality to promote DRAFT modules (and their children)
This module also includes functionality to promote DRAFT modules (and their children)
to published modules.
to published modules.
"""
"""
def
get_item
(
self
,
usage_key
,
depth
=
0
,
revision
=
None
):
def
get_item
(
self
,
usage_key
,
depth
=
0
,
revision
=
None
,
**
kwargs
):
"""
"""
Returns an XModuleDescriptor instance for the item at usage_key.
Returns an XModuleDescriptor instance for the item at usage_key.
...
@@ -155,7 +155,7 @@ class DraftModuleStore(MongoModuleStore):
...
@@ -155,7 +155,7 @@ class DraftModuleStore(MongoModuleStore):
course_query
=
self
.
_course_key_to_son
(
course_key
)
course_query
=
self
.
_course_key_to_son
(
course_key
)
self
.
collection
.
remove
(
course_query
,
multi
=
True
)
self
.
collection
.
remove
(
course_query
,
multi
=
True
)
def
clone_course
(
self
,
source_course_id
,
dest_course_id
,
user_id
,
fields
=
None
):
def
clone_course
(
self
,
source_course_id
,
dest_course_id
,
user_id
,
fields
=
None
,
**
kwargs
):
"""
"""
Only called if cloning within this store or if env doesn't set up mixed.
Only called if cloning within this store or if env doesn't set up mixed.
* copy the courseware
* copy the courseware
...
@@ -331,11 +331,6 @@ class DraftModuleStore(MongoModuleStore):
...
@@ -331,11 +331,6 @@ class DraftModuleStore(MongoModuleStore):
returns only Published items
returns only Published items
if the branch setting is ModuleStoreEnum.Branch.draft_preferred,
if the branch setting is ModuleStoreEnum.Branch.draft_preferred,
returns either Draft or Published, preferring Draft items.
returns either Draft or Published, preferring Draft items.
kwargs (key=value): what to look for within the course.
Common qualifiers are ``category`` or any field name. if the target field is a list,
then it searches for the given value in the list not list equivalence.
Substring matching pass a regex object.
``name`` is another commonly provided key (Location based stores)
"""
"""
def
base_get_items
(
key_revision
):
def
base_get_items
(
key_revision
):
return
super
(
DraftModuleStore
,
self
)
.
get_items
(
course_key
,
key_revision
=
key_revision
,
**
kwargs
)
return
super
(
DraftModuleStore
,
self
)
.
get_items
(
course_key
,
key_revision
=
key_revision
,
**
kwargs
)
...
@@ -439,7 +434,7 @@ class DraftModuleStore(MongoModuleStore):
...
@@ -439,7 +434,7 @@ class DraftModuleStore(MongoModuleStore):
# convert the subtree using the original item as the root
# convert the subtree using the original item as the root
self
.
_breadth_first
(
convert_item
,
[
location
])
self
.
_breadth_first
(
convert_item
,
[
location
])
def
update_item
(
self
,
xblock
,
user_id
,
allow_not_found
=
False
,
force
=
False
,
isPublish
=
False
):
def
update_item
(
self
,
xblock
,
user_id
,
allow_not_found
=
False
,
force
=
False
,
isPublish
=
False
,
**
kwargs
):
"""
"""
See superclass doc.
See superclass doc.
In addition to the superclass's behavior, this method converts the unit to draft if it's not
In addition to the superclass's behavior, this method converts the unit to draft if it's not
...
@@ -616,7 +611,7 @@ class DraftModuleStore(MongoModuleStore):
...
@@ -616,7 +611,7 @@ class DraftModuleStore(MongoModuleStore):
else
:
else
:
return
False
return
False
def
publish
(
self
,
location
,
user_id
):
def
publish
(
self
,
location
,
user_id
,
**
kwargs
):
"""
"""
Publish the subtree rooted at location to the live course and remove the drafts.
Publish the subtree rooted at location to the live course and remove the drafts.
Such publishing may cause the deletion of previously published but subsequently deleted
Such publishing may cause the deletion of previously published but subsequently deleted
...
@@ -690,7 +685,7 @@ class DraftModuleStore(MongoModuleStore):
...
@@ -690,7 +685,7 @@ class DraftModuleStore(MongoModuleStore):
self
.
collection
.
remove
({
'_id'
:
{
'$in'
:
to_be_deleted
}})
self
.
collection
.
remove
({
'_id'
:
{
'$in'
:
to_be_deleted
}})
return
self
.
get_item
(
as_published
(
location
))
return
self
.
get_item
(
as_published
(
location
))
def
unpublish
(
self
,
location
,
user_id
):
def
unpublish
(
self
,
location
,
user_id
,
**
kwargs
):
"""
"""
Turn the published version into a draft, removing the published version.
Turn the published version into a draft, removing the published version.
...
...
common/lib/xmodule/xmodule/modulestore/split_migrator.py
View file @
9efe5d92
...
@@ -25,7 +25,9 @@ class SplitMigrator(object):
...
@@ -25,7 +25,9 @@ class SplitMigrator(object):
self
.
split_modulestore
=
split_modulestore
self
.
split_modulestore
=
split_modulestore
self
.
source_modulestore
=
source_modulestore
self
.
source_modulestore
=
source_modulestore
def
migrate_mongo_course
(
self
,
source_course_key
,
user_id
,
new_org
=
None
,
new_course
=
None
,
new_run
=
None
,
fields
=
None
):
def
migrate_mongo_course
(
self
,
source_course_key
,
user_id
,
new_org
=
None
,
new_course
=
None
,
new_run
=
None
,
fields
=
None
,
**
kwargs
):
"""
"""
Create a new course in split_mongo representing the published and draft versions of the course from the
Create a new course in split_mongo representing the published and draft versions of the course from the
original mongo store. And return the new CourseLocator
original mongo store. And return the new CourseLocator
...
@@ -43,7 +45,7 @@ class SplitMigrator(object):
...
@@ -43,7 +45,7 @@ class SplitMigrator(object):
# locations are in location, children, conditionals, course.tab
# locations are in location, children, conditionals, course.tab
# create the course: set fields to explicitly_set for each scope, id_root = new_course_locator, master_branch = 'production'
# create the course: set fields to explicitly_set for each scope, id_root = new_course_locator, master_branch = 'production'
original_course
=
self
.
source_modulestore
.
get_course
(
source_course_key
)
original_course
=
self
.
source_modulestore
.
get_course
(
source_course_key
,
**
kwargs
)
if
new_org
is
None
:
if
new_org
is
None
:
new_org
=
source_course_key
.
org
new_org
=
source_course_key
.
org
...
@@ -60,17 +62,20 @@ class SplitMigrator(object):
...
@@ -60,17 +62,20 @@ class SplitMigrator(object):
new_org
,
new_course
,
new_run
,
user_id
,
new_org
,
new_course
,
new_run
,
user_id
,
fields
=
new_fields
,
fields
=
new_fields
,
master_branch
=
ModuleStoreEnum
.
BranchName
.
published
,
master_branch
=
ModuleStoreEnum
.
BranchName
.
published
,
**
kwargs
)
)
with
self
.
split_modulestore
.
bulk_write_operations
(
new_course
.
id
):
with
self
.
split_modulestore
.
bulk_write_operations
(
new_course
.
id
):
self
.
_copy_published_modules_to_course
(
new_course
,
original_course
.
location
,
source_course_key
,
user_id
)
self
.
_copy_published_modules_to_course
(
new_course
,
original_course
.
location
,
source_course_key
,
user_id
,
**
kwargs
)
# create a new version for the drafts
# create a new version for the drafts
with
self
.
split_modulestore
.
bulk_write_operations
(
new_course
.
id
):
with
self
.
split_modulestore
.
bulk_write_operations
(
new_course
.
id
):
self
.
_add_draft_modules_to_course
(
new_course
.
location
,
source_course_key
,
user_id
)
self
.
_add_draft_modules_to_course
(
new_course
.
location
,
source_course_key
,
user_id
,
**
kwargs
)
return
new_course
.
id
return
new_course
.
id
def
_copy_published_modules_to_course
(
self
,
new_course
,
old_course_loc
,
source_course_key
,
user_id
):
def
_copy_published_modules_to_course
(
self
,
new_course
,
old_course_loc
,
source_course_key
,
user_id
,
**
kwargs
):
"""
"""
Copy all of the modules from the 'direct' version of the course to the new split course.
Copy all of the modules from the 'direct' version of the course to the new split course.
"""
"""
...
@@ -79,7 +84,7 @@ class SplitMigrator(object):
...
@@ -79,7 +84,7 @@ class SplitMigrator(object):
# iterate over published course elements. Wildcarding rather than descending b/c some elements are orphaned (e.g.,
# iterate over published course elements. Wildcarding rather than descending b/c some elements are orphaned (e.g.,
# course about pages, conditionals)
# course about pages, conditionals)
for
module
in
self
.
source_modulestore
.
get_items
(
for
module
in
self
.
source_modulestore
.
get_items
(
source_course_key
,
revision
=
ModuleStoreEnum
.
RevisionOption
.
published_only
source_course_key
,
revision
=
ModuleStoreEnum
.
RevisionOption
.
published_only
,
**
kwargs
):
):
# don't copy the course again.
# don't copy the course again.
if
module
.
location
!=
old_course_loc
:
if
module
.
location
!=
old_course_loc
:
...
@@ -95,7 +100,8 @@ class SplitMigrator(object):
...
@@ -95,7 +100,8 @@ class SplitMigrator(object):
fields
=
self
.
_get_fields_translate_references
(
fields
=
self
.
_get_fields_translate_references
(
module
,
course_version_locator
,
new_course
.
location
.
block_id
module
,
course_version_locator
,
new_course
.
location
.
block_id
),
),
continue_version
=
True
continue_version
=
True
,
**
kwargs
)
)
# after done w/ published items, add version for DRAFT pointing to the published structure
# after done w/ published items, add version for DRAFT pointing to the published structure
index_info
=
self
.
split_modulestore
.
get_course_index_info
(
course_version_locator
)
index_info
=
self
.
split_modulestore
.
get_course_index_info
(
course_version_locator
)
...
@@ -107,7 +113,7 @@ class SplitMigrator(object):
...
@@ -107,7 +113,7 @@ class SplitMigrator(object):
# children which meant some pointers were to non-existent locations in 'direct'
# children which meant some pointers were to non-existent locations in 'direct'
self
.
split_modulestore
.
internal_clean_children
(
course_version_locator
)
self
.
split_modulestore
.
internal_clean_children
(
course_version_locator
)
def
_add_draft_modules_to_course
(
self
,
published_course_usage_key
,
source_course_key
,
user_id
):
def
_add_draft_modules_to_course
(
self
,
published_course_usage_key
,
source_course_key
,
user_id
,
**
kwargs
):
"""
"""
update each draft. Create any which don't exist in published and attach to their parents.
update each draft. Create any which don't exist in published and attach to their parents.
"""
"""
...
@@ -117,11 +123,13 @@ class SplitMigrator(object):
...
@@ -117,11 +123,13 @@ class SplitMigrator(object):
# to prevent race conditions of grandchilden being added before their parents and thus having no parent to
# to prevent race conditions of grandchilden being added before their parents and thus having no parent to
# add to
# add to
awaiting_adoption
=
{}
awaiting_adoption
=
{}
for
module
in
self
.
source_modulestore
.
get_items
(
source_course_key
,
revision
=
ModuleStoreEnum
.
RevisionOption
.
draft_only
):
for
module
in
self
.
source_modulestore
.
get_items
(
source_course_key
,
revision
=
ModuleStoreEnum
.
RevisionOption
.
draft_only
,
**
kwargs
):
new_locator
=
new_draft_course_loc
.
make_usage_key
(
module
.
category
,
module
.
location
.
block_id
)
new_locator
=
new_draft_course_loc
.
make_usage_key
(
module
.
category
,
module
.
location
.
block_id
)
if
self
.
split_modulestore
.
has_item
(
new_locator
):
if
self
.
split_modulestore
.
has_item
(
new_locator
):
# was in 'direct' so draft is a new version
# was in 'direct' so draft is a new version
split_module
=
self
.
split_modulestore
.
get_item
(
new_locator
)
split_module
=
self
.
split_modulestore
.
get_item
(
new_locator
,
**
kwargs
)
# need to remove any no-longer-explicitly-set values and add/update any now set values.
# need to remove any no-longer-explicitly-set values and add/update any now set values.
for
name
,
field
in
split_module
.
fields
.
iteritems
():
for
name
,
field
in
split_module
.
fields
.
iteritems
():
if
field
.
is_set_on
(
split_module
)
and
not
module
.
fields
[
name
]
.
is_set_on
(
module
):
if
field
.
is_set_on
(
split_module
)
and
not
module
.
fields
[
name
]
.
is_set_on
(
module
):
...
@@ -131,7 +139,7 @@ class SplitMigrator(object):
...
@@ -131,7 +139,7 @@ class SplitMigrator(object):
)
.
iteritems
():
)
.
iteritems
():
field
.
write_to
(
split_module
,
value
)
field
.
write_to
(
split_module
,
value
)
_new_module
=
self
.
split_modulestore
.
update_item
(
split_module
,
user_id
)
_new_module
=
self
.
split_modulestore
.
update_item
(
split_module
,
user_id
,
**
kwargs
)
else
:
else
:
# only a draft version (aka, 'private').
# only a draft version (aka, 'private').
_new_module
=
self
.
split_modulestore
.
create_item
(
_new_module
=
self
.
split_modulestore
.
create_item
(
...
@@ -140,22 +148,23 @@ class SplitMigrator(object):
...
@@ -140,22 +148,23 @@ class SplitMigrator(object):
block_id
=
new_locator
.
block_id
,
block_id
=
new_locator
.
block_id
,
fields
=
self
.
_get_fields_translate_references
(
fields
=
self
.
_get_fields_translate_references
(
module
,
new_draft_course_loc
,
published_course_usage_key
.
block_id
module
,
new_draft_course_loc
,
published_course_usage_key
.
block_id
)
),
**
kwargs
)
)
awaiting_adoption
[
module
.
location
]
=
new_locator
awaiting_adoption
[
module
.
location
]
=
new_locator
for
draft_location
,
new_locator
in
awaiting_adoption
.
iteritems
():
for
draft_location
,
new_locator
in
awaiting_adoption
.
iteritems
():
parent_loc
=
self
.
source_modulestore
.
get_parent_location
(
parent_loc
=
self
.
source_modulestore
.
get_parent_location
(
draft_location
,
revision
=
ModuleStoreEnum
.
RevisionOption
.
draft_preferred
draft_location
,
revision
=
ModuleStoreEnum
.
RevisionOption
.
draft_preferred
,
**
kwargs
)
)
if
parent_loc
is
None
:
if
parent_loc
is
None
:
log
.
warn
(
u'No parent found in source course for
%
s'
,
draft_location
)
log
.
warn
(
u'No parent found in source course for
%
s'
,
draft_location
)
continue
continue
old_parent
=
self
.
source_modulestore
.
get_item
(
parent_loc
)
old_parent
=
self
.
source_modulestore
.
get_item
(
parent_loc
,
**
kwargs
)
split_parent_loc
=
new_draft_course_loc
.
make_usage_key
(
split_parent_loc
=
new_draft_course_loc
.
make_usage_key
(
parent_loc
.
category
,
parent_loc
.
category
,
parent_loc
.
block_id
if
parent_loc
.
category
!=
'course'
else
published_course_usage_key
.
block_id
parent_loc
.
block_id
if
parent_loc
.
category
!=
'course'
else
published_course_usage_key
.
block_id
)
)
new_parent
=
self
.
split_modulestore
.
get_item
(
split_parent_loc
)
new_parent
=
self
.
split_modulestore
.
get_item
(
split_parent_loc
,
**
kwargs
)
# this only occurs if the parent was also awaiting adoption: skip this one, go to next
# this only occurs if the parent was also awaiting adoption: skip this one, go to next
if
any
(
new_locator
==
child
.
version_agnostic
()
for
child
in
new_parent
.
children
):
if
any
(
new_locator
==
child
.
version_agnostic
()
for
child
in
new_parent
.
children
):
continue
continue
...
...
common/lib/xmodule/xmodule/modulestore/split_mongo/caching_descriptor_system.py
View file @
9efe5d92
...
@@ -53,7 +53,7 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
...
@@ -53,7 +53,7 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
self
.
default_class
=
default_class
self
.
default_class
=
default_class
self
.
local_modules
=
{}
self
.
local_modules
=
{}
def
_load_item
(
self
,
block_id
,
course_entry_override
=
None
):
def
_load_item
(
self
,
block_id
,
course_entry_override
=
None
,
**
kwargs
):
if
isinstance
(
block_id
,
BlockUsageLocator
):
if
isinstance
(
block_id
,
BlockUsageLocator
):
if
isinstance
(
block_id
.
block_id
,
LocalId
):
if
isinstance
(
block_id
.
block_id
,
LocalId
):
try
:
try
:
...
@@ -77,7 +77,7 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
...
@@ -77,7 +77,7 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
raise
ItemNotFoundError
(
block_id
)
raise
ItemNotFoundError
(
block_id
)
class_
=
self
.
load_block_type
(
json_data
.
get
(
'category'
))
class_
=
self
.
load_block_type
(
json_data
.
get
(
'category'
))
return
self
.
xblock_from_json
(
class_
,
block_id
,
json_data
,
course_entry_override
)
return
self
.
xblock_from_json
(
class_
,
block_id
,
json_data
,
course_entry_override
,
**
kwargs
)
# xblock's runtime does not always pass enough contextual information to figure out
# xblock's runtime does not always pass enough contextual information to figure out
# which named container (course x branch) or which parent is requesting an item. Because split allows
# which named container (course x branch) or which parent is requesting an item. Because split allows
...
@@ -90,7 +90,7 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
...
@@ -90,7 +90,7 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
# low; thus, the course_entry is most likely correct. If the thread is looking at > 1 named container
# low; thus, the course_entry is most likely correct. If the thread is looking at > 1 named container
# pointing to the same structure, the access is likely to be chunky enough that the last known container
# pointing to the same structure, the access is likely to be chunky enough that the last known container
# is the intended one when not given a course_entry_override; thus, the caching of the last branch/course id.
# is the intended one when not given a course_entry_override; thus, the caching of the last branch/course id.
def
xblock_from_json
(
self
,
class_
,
block_id
,
json_data
,
course_entry_override
=
None
):
def
xblock_from_json
(
self
,
class_
,
block_id
,
json_data
,
course_entry_override
=
None
,
**
kwargs
):
if
course_entry_override
is
None
:
if
course_entry_override
is
None
:
course_entry_override
=
self
.
course_entry
course_entry_override
=
self
.
course_entry
else
:
else
:
...
@@ -126,6 +126,7 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
...
@@ -126,6 +126,7 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
definition
,
definition
,
converted_fields
,
converted_fields
,
json_data
.
get
(
'_inherited_settings'
),
json_data
.
get
(
'_inherited_settings'
),
**
kwargs
)
)
field_data
=
KvsFieldData
(
kvs
)
field_data
=
KvsFieldData
(
kvs
)
...
@@ -151,6 +152,10 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
...
@@ -151,6 +152,10 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
edit_info
=
json_data
.
get
(
'edit_info'
,
{})
edit_info
=
json_data
.
get
(
'edit_info'
,
{})
module
.
edited_by
=
edit_info
.
get
(
'edited_by'
)
module
.
edited_by
=
edit_info
.
get
(
'edited_by'
)
module
.
edited_on
=
edit_info
.
get
(
'edited_on'
)
module
.
edited_on
=
edit_info
.
get
(
'edited_on'
)
module
.
subtree_edited_by
=
None
# TODO - addressed with LMS-11183
module
.
subtree_edited_on
=
None
# TODO - addressed with LMS-11183
module
.
published_by
=
None
# TODO - addressed with LMS-11184
module
.
published_date
=
None
# TODO - addressed with LMS-11184
module
.
previous_version
=
edit_info
.
get
(
'previous_version'
)
module
.
previous_version
=
edit_info
.
get
(
'previous_version'
)
module
.
update_version
=
edit_info
.
get
(
'update_version'
)
module
.
update_version
=
edit_info
.
get
(
'update_version'
)
module
.
source_version
=
edit_info
.
get
(
'source_version'
,
None
)
module
.
source_version
=
edit_info
.
get
(
'source_version'
,
None
)
...
...
common/lib/xmodule/xmodule/modulestore/split_mongo/split.py
View file @
9efe5d92
...
@@ -63,7 +63,7 @@ from xblock.fields import Scope, Reference, ReferenceList, ReferenceValueDict
...
@@ -63,7 +63,7 @@ from xblock.fields import Scope, Reference, ReferenceList, ReferenceValueDict
from
xmodule.errortracker
import
null_error_tracker
from
xmodule.errortracker
import
null_error_tracker
from
opaque_keys.edx.locator
import
(
from
opaque_keys.edx.locator
import
(
BlockUsageLocator
,
DefinitionLocator
,
CourseLocator
,
VersionTree
,
BlockUsageLocator
,
DefinitionLocator
,
CourseLocator
,
VersionTree
,
LocalId
,
Locator
LocalId
,
)
)
from
xmodule.modulestore.exceptions
import
InsufficientSpecificationError
,
VersionConflictError
,
DuplicateItemError
,
\
from
xmodule.modulestore.exceptions
import
InsufficientSpecificationError
,
VersionConflictError
,
DuplicateItemError
,
\
DuplicateCourseError
DuplicateCourseError
...
@@ -77,7 +77,6 @@ from .caching_descriptor_system import CachingDescriptorSystem
...
@@ -77,7 +77,6 @@ from .caching_descriptor_system import CachingDescriptorSystem
from
xmodule.modulestore.split_mongo.mongo_connection
import
MongoConnection
from
xmodule.modulestore.split_mongo.mongo_connection
import
MongoConnection
from
xmodule.error_module
import
ErrorDescriptor
from
xmodule.error_module
import
ErrorDescriptor
from
xmodule.modulestore.split_mongo
import
encode_key_for_mongo
,
decode_key_from_mongo
from
xmodule.modulestore.split_mongo
import
encode_key_for_mongo
,
decode_key_from_mongo
import
types
from
_collections
import
defaultdict
from
_collections
import
defaultdict
...
@@ -111,7 +110,6 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
...
@@ -111,7 +110,6 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
"""
"""
SCHEMA_VERSION
=
1
SCHEMA_VERSION
=
1
reference_type
=
Locator
# a list of field names to store in course index search_targets. Note, this will
# a list of field names to store in course index search_targets. Note, this will
# only record one value per key. If branches disagree, the last one set wins.
# only record one value per key. If branches disagree, the last one set wins.
# It won't recompute the value on operations such as update_course_index (e.g., to revert to a prev
# It won't recompute the value on operations such as update_course_index (e.g., to revert to a prev
...
@@ -214,7 +212,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
...
@@ -214,7 +212,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
system
.
module_data
.
update
(
new_module_data
)
system
.
module_data
.
update
(
new_module_data
)
return
system
.
module_data
return
system
.
module_data
def
_load_items
(
self
,
course_entry
,
block_ids
,
depth
=
0
,
lazy
=
True
):
def
_load_items
(
self
,
course_entry
,
block_ids
,
depth
=
0
,
lazy
=
True
,
**
kwargs
):
'''
'''
Load & cache the given blocks from the course. Prefetch down to the
Load & cache the given blocks from the course. Prefetch down to the
given depth. Load the definitions into each block if lazy is False;
given depth. Load the definitions into each block if lazy is False;
...
@@ -248,7 +246,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
...
@@ -248,7 +246,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
branch
=
course_entry
.
get
(
'branch'
),
branch
=
course_entry
.
get
(
'branch'
),
)
)
self
.
cache_items
(
system
,
block_ids
,
course_key
,
depth
,
lazy
)
self
.
cache_items
(
system
,
block_ids
,
course_key
,
depth
,
lazy
)
return
[
system
.
load_item
(
block_id
,
course_entry
)
for
block_id
in
block_ids
]
return
[
system
.
load_item
(
block_id
,
course_entry
,
**
kwargs
)
for
block_id
in
block_ids
]
def
_get_cache
(
self
,
course_version_guid
):
def
_get_cache
(
self
,
course_version_guid
):
"""
"""
...
@@ -333,7 +331,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
...
@@ -333,7 +331,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
}
}
return
envelope
return
envelope
def
get_courses
(
self
,
branch
,
qualifiers
=
None
):
def
get_courses
(
self
,
branch
,
qualifiers
=
None
,
**
kwargs
):
'''
'''
Returns a list of course descriptors matching any given qualifiers.
Returns a list of course descriptors matching any given qualifiers.
...
@@ -373,12 +371,21 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
...
@@ -373,12 +371,21 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
'structure'
:
entry
,
'structure'
:
entry
,
}
}
root
=
entry
[
'root'
]
root
=
entry
[
'root'
]
course_list
=
self
.
_load_items
(
envelope
,
[
root
],
0
,
lazy
=
True
)
course_list
=
self
.
_load_items
(
envelope
,
[
root
],
0
,
lazy
=
True
,
**
kwargs
)
if
not
isinstance
(
course_list
[
0
],
ErrorDescriptor
):
if
not
isinstance
(
course_list
[
0
],
ErrorDescriptor
):
result
.
append
(
course_list
[
0
])
result
.
append
(
course_list
[
0
])
return
result
return
result
def
get_course
(
self
,
course_id
,
depth
=
0
):
def
make_course_key
(
self
,
org
,
course
,
run
):
"""
Return a valid :class:`~opaque_keys.edx.keys.CourseKey` for this modulestore
that matches the supplied `org`, `course`, and `run`.
This key may represent a course that doesn't exist in this modulestore.
"""
return
CourseLocator
(
org
,
course
,
run
)
def
get_course
(
self
,
course_id
,
depth
=
0
,
**
kwargs
):
'''
'''
Gets the course descriptor for the course identified by the locator
Gets the course descriptor for the course identified by the locator
'''
'''
...
@@ -388,10 +395,10 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
...
@@ -388,10 +395,10 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
course_entry
=
self
.
_lookup_course
(
course_id
)
course_entry
=
self
.
_lookup_course
(
course_id
)
root
=
course_entry
[
'structure'
][
'root'
]
root
=
course_entry
[
'structure'
][
'root'
]
result
=
self
.
_load_items
(
course_entry
,
[
root
],
0
,
lazy
=
True
)
result
=
self
.
_load_items
(
course_entry
,
[
root
],
0
,
lazy
=
True
,
**
kwargs
)
return
result
[
0
]
return
result
[
0
]
def
has_course
(
self
,
course_id
,
ignore_case
=
False
):
def
has_course
(
self
,
course_id
,
ignore_case
=
False
,
**
kwargs
):
'''
'''
Does this course exist in this modulestore. This method does not verify that the branch &/or
Does this course exist in this modulestore. This method does not verify that the branch &/or
version in the course_id exists. Use get_course_index_info to check that.
version in the course_id exists. Use get_course_index_info to check that.
...
@@ -423,7 +430,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
...
@@ -423,7 +430,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
return
self
.
_get_block_from_structure
(
course_structure
,
usage_key
.
block_id
)
is
not
None
return
self
.
_get_block_from_structure
(
course_structure
,
usage_key
.
block_id
)
is
not
None
def
get_item
(
self
,
usage_key
,
depth
=
0
):
def
get_item
(
self
,
usage_key
,
depth
=
0
,
**
kwargs
):
"""
"""
depth (int): An argument that some module stores may use to prefetch
depth (int): An argument that some module stores may use to prefetch
descendants of the queried modules for more efficient results later
descendants of the queried modules for more efficient results later
...
@@ -437,14 +444,14 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
...
@@ -437,14 +444,14 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
raise
ItemNotFoundError
(
usage_key
)
raise
ItemNotFoundError
(
usage_key
)
course
=
self
.
_lookup_course
(
usage_key
)
course
=
self
.
_lookup_course
(
usage_key
)
items
=
self
.
_load_items
(
course
,
[
usage_key
.
block_id
],
depth
,
lazy
=
True
)
items
=
self
.
_load_items
(
course
,
[
usage_key
.
block_id
],
depth
,
lazy
=
True
,
**
kwargs
)
if
len
(
items
)
==
0
:
if
len
(
items
)
==
0
:
raise
ItemNotFoundError
(
usage_key
)
raise
ItemNotFoundError
(
usage_key
)
elif
len
(
items
)
>
1
:
elif
len
(
items
)
>
1
:
log
.
debug
(
"Found more than one item for '{}'"
.
format
(
usage_key
))
log
.
debug
(
"Found more than one item for '{}'"
.
format
(
usage_key
))
return
items
[
0
]
return
items
[
0
]
def
get_items
(
self
,
course_locator
,
settings
=
None
,
content
=
None
,
**
kwargs
):
def
get_items
(
self
,
course_locator
,
settings
=
None
,
content
=
None
,
qualifiers
=
None
,
**
kwargs
):
"""
"""
Returns:
Returns:
list of XModuleDescriptor instances for the matching items within the course with
list of XModuleDescriptor instances for the matching items within the course with
...
@@ -455,10 +462,10 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
...
@@ -455,10 +462,10 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
Args:
Args:
course_locator (CourseLocator): the course identifier
course_locator (CourseLocator): the course identifier
settings (dict): fields to look for which have settings scope. Follows same syntax
settings (dict): fields to look for which have settings scope. Follows same syntax
and rules as
kwarg
s below
and rules as
qualifier
s below
content (dict): fields to look for which have content scope. Follows same syntax and
content (dict): fields to look for which have content scope. Follows same syntax and
rules as
kwarg
s below.
rules as
qualifier
s below.
kwargs (key=value
): what to look for within the course.
qualifiers (dict
): what to look for within the course.
Common qualifiers are ``category`` or any field name. if the target field is a list,
Common qualifiers are ``category`` or any field name. if the target field is a list,
then it searches for the given value in the list not list equivalence.
then it searches for the given value in the list not list equivalence.
For substring matching pass a regex object.
For substring matching pass a regex object.
...
@@ -467,6 +474,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
...
@@ -467,6 +474,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
"""
"""
course
=
self
.
_lookup_course
(
course_locator
)
course
=
self
.
_lookup_course
(
course_locator
)
items
=
[]
items
=
[]
qualifiers
=
qualifiers
.
copy
()
if
qualifiers
else
{}
# copy the qualifiers (destructively manipulated here)
def
_block_matches_all
(
block_json
):
def
_block_matches_all
(
block_json
):
"""
"""
...
@@ -474,7 +482,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
...
@@ -474,7 +482,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
"""
"""
# do the checks which don't require loading any additional data
# do the checks which don't require loading any additional data
if
(
if
(
self
.
_block_matches
(
block_json
,
kwarg
s
)
and
self
.
_block_matches
(
block_json
,
qualifier
s
)
and
self
.
_block_matches
(
block_json
.
get
(
'fields'
,
{}),
settings
)
self
.
_block_matches
(
block_json
.
get
(
'fields'
,
{}),
settings
)
):
):
if
content
:
if
content
:
...
@@ -485,23 +493,23 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
...
@@ -485,23 +493,23 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
if
settings
is
None
:
if
settings
is
None
:
settings
=
{}
settings
=
{}
if
'name'
in
kwarg
s
:
if
'name'
in
qualifier
s
:
# odd case where we don't search just confirm
# odd case where we don't search just confirm
block_id
=
kwarg
s
.
pop
(
'name'
)
block_id
=
qualifier
s
.
pop
(
'name'
)
block
=
course
[
'structure'
][
'blocks'
]
.
get
(
block_id
)
block
=
course
[
'structure'
][
'blocks'
]
.
get
(
block_id
)
if
_block_matches_all
(
block
):
if
_block_matches_all
(
block
):
return
self
.
_load_items
(
course
,
[
block_id
],
lazy
=
True
)
return
self
.
_load_items
(
course
,
[
block_id
],
lazy
=
True
,
**
kwargs
)
else
:
else
:
return
[]
return
[]
# don't expect caller to know that children are in fields
# don't expect caller to know that children are in fields
if
'children'
in
kwarg
s
:
if
'children'
in
qualifier
s
:
settings
[
'children'
]
=
kwarg
s
.
pop
(
'children'
)
settings
[
'children'
]
=
qualifier
s
.
pop
(
'children'
)
for
block_id
,
value
in
course
[
'structure'
][
'blocks'
]
.
iteritems
():
for
block_id
,
value
in
course
[
'structure'
][
'blocks'
]
.
iteritems
():
if
_block_matches_all
(
value
):
if
_block_matches_all
(
value
):
items
.
append
(
block_id
)
items
.
append
(
block_id
)
if
len
(
items
)
>
0
:
if
len
(
items
)
>
0
:
return
self
.
_load_items
(
course
,
items
,
0
,
lazy
=
True
)
return
self
.
_load_items
(
course
,
items
,
0
,
lazy
=
True
,
**
kwargs
)
else
:
else
:
return
[]
return
[]
...
@@ -523,7 +531,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
...
@@ -523,7 +531,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
block_id
=
decode_key_from_mongo
(
parent_id
),
block_id
=
decode_key_from_mongo
(
parent_id
),
)
)
def
get_orphans
(
self
,
course_key
):
def
get_orphans
(
self
,
course_key
,
**
kwargs
):
"""
"""
Return an array of all of the orphans in the course.
Return an array of all of the orphans in the course.
"""
"""
...
@@ -820,6 +828,11 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
...
@@ -820,6 +828,11 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
the course id'd by version_guid but instead in one w/ a new version_guid. Ensure in this case that you get
the course id'd by version_guid but instead in one w/ a new version_guid. Ensure in this case that you get
the new version_guid from the locator in the returned object!
the new version_guid from the locator in the returned object!
"""
"""
# split handles all the fields in one dict not separated by scope
fields
=
fields
or
{}
fields
.
update
(
kwargs
.
pop
(
'metadata'
,
{})
or
{})
fields
.
update
(
kwargs
.
pop
(
'definition_data'
,
{})
or
{})
# find course_index entry if applicable and structures entry
# find course_index entry if applicable and structures entry
index_entry
=
self
.
_get_index_if_valid
(
course_key
,
force
,
continue_version
)
index_entry
=
self
.
_get_index_if_valid
(
course_key
,
force
,
continue_version
)
structure
=
self
.
_lookup_course
(
course_key
)[
'structure'
]
structure
=
self
.
_lookup_course
(
course_key
)[
'structure'
]
...
@@ -940,18 +953,20 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
...
@@ -940,18 +953,20 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
# don't need to update the index b/c create_item did it for this version
# don't need to update the index b/c create_item did it for this version
return
xblock
return
xblock
def
clone_course
(
self
,
source_course_id
,
dest_course_id
,
user_id
,
fields
=
None
):
def
clone_course
(
self
,
source_course_id
,
dest_course_id
,
user_id
,
fields
=
None
,
**
kwargs
):
"""
"""
See :meth: `.ModuleStoreWrite.clone_course` for documentation.
See :meth: `.ModuleStoreWrite.clone_course` for documentation.
In split, other than copying the assets, this is cheap as it merely creates a new version of the
In split, other than copying the assets, this is cheap as it merely creates a new version of the
existing course.
existing course.
"""
"""
super
(
SplitMongoModuleStore
,
self
)
.
clone_course
(
source_course_id
,
dest_course_id
,
user_id
,
fields
)
super
(
SplitMongoModuleStore
,
self
)
.
clone_course
(
source_course_id
,
dest_course_id
,
user_id
,
fields
,
**
kwargs
)
source_index
=
self
.
get_course_index_info
(
source_course_id
)
source_index
=
self
.
get_course_index_info
(
source_course_id
)
if
source_index
is
None
:
raise
ItemNotFoundError
(
"Cannot find a course at {0}. Aborting"
.
format
(
source_course_id
))
return
self
.
create_course
(
return
self
.
create_course
(
dest_course_id
.
org
,
dest_course_id
.
course
,
dest_course_id
.
run
,
user_id
,
fields
=
fields
,
dest_course_id
.
org
,
dest_course_id
.
course
,
dest_course_id
.
run
,
user_id
,
fields
=
fields
,
versions_dict
=
source_index
[
'versions'
],
search_targets
=
source_index
[
'search_targets'
]
versions_dict
=
source_index
[
'versions'
],
search_targets
=
source_index
[
'search_targets'
]
,
**
kwargs
)
)
def
create_course
(
def
create_course
(
...
@@ -1087,10 +1102,10 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
...
@@ -1087,10 +1102,10 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
self
.
_update_search_targets
(
index_entry
,
fields
)
self
.
_update_search_targets
(
index_entry
,
fields
)
self
.
db_connection
.
insert_course_index
(
index_entry
)
self
.
db_connection
.
insert_course_index
(
index_entry
)
# expensive hack to persist default field values set in __init__ method (e.g., wiki_slug)
# expensive hack to persist default field values set in __init__ method (e.g., wiki_slug)
course
=
self
.
get_course
(
locator
)
course
=
self
.
get_course
(
locator
,
**
kwargs
)
return
self
.
update_item
(
course
,
user_id
)
return
self
.
update_item
(
course
,
user_id
,
**
kwargs
)
def
update_item
(
self
,
descriptor
,
user_id
,
allow_not_found
=
False
,
force
=
False
):
def
update_item
(
self
,
descriptor
,
user_id
,
allow_not_found
=
False
,
force
=
False
,
**
kwargs
):
"""
"""
Save the descriptor's fields. it doesn't descend the course dag to save the children.
Save the descriptor's fields. it doesn't descend the course dag to save the children.
Return the new descriptor (updated location).
Return the new descriptor (updated location).
...
@@ -1161,12 +1176,12 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
...
@@ -1161,12 +1176,12 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
# fetch and return the new item--fetching is unnecessary but a good qc step
# fetch and return the new item--fetching is unnecessary but a good qc step
new_locator
=
descriptor
.
location
.
map_into_course
(
course_key
)
new_locator
=
descriptor
.
location
.
map_into_course
(
course_key
)
return
self
.
get_item
(
new_locator
)
return
self
.
get_item
(
new_locator
,
**
kwargs
)
else
:
else
:
# nothing changed, just return the one sent in
# nothing changed, just return the one sent in
return
descriptor
return
descriptor
def
create_xblock
(
self
,
runtime
,
category
,
fields
=
None
,
block_id
=
None
,
definition_id
=
None
,
parent_xblock
=
None
):
def
create_xblock
(
self
,
runtime
,
category
,
fields
=
None
,
block_id
=
None
,
definition_id
=
None
,
parent_xblock
=
None
,
**
kwargs
):
"""
"""
This method instantiates the correct subclass of XModuleDescriptor based
This method instantiates the correct subclass of XModuleDescriptor based
on the contents of json_data. It does not persist it and can create one which
on the contents of json_data. It does not persist it and can create one which
...
@@ -1193,7 +1208,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
...
@@ -1193,7 +1208,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
if
field_name
in
fields
:
if
field_name
in
fields
:
json_data
[
'_inherited_settings'
][
field_name
]
=
fields
[
field_name
]
json_data
[
'_inherited_settings'
][
field_name
]
=
fields
[
field_name
]
new_block
=
runtime
.
xblock_from_json
(
xblock_class
,
block_id
,
json_data
)
new_block
=
runtime
.
xblock_from_json
(
xblock_class
,
block_id
,
json_data
,
**
kwargs
)
if
parent_xblock
is
not
None
:
if
parent_xblock
is
not
None
:
parent_xblock
.
children
.
append
(
new_block
.
scope_ids
.
usage_id
)
parent_xblock
.
children
.
append
(
new_block
.
scope_ids
.
usage_id
)
# decache pending children field settings
# decache pending children field settings
...
@@ -1844,6 +1859,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
...
@@ -1844,6 +1859,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
destination_block
[
'edit_info'
][
'previous_version'
]
=
previous_version
destination_block
[
'edit_info'
][
'previous_version'
]
=
previous_version
destination_block
[
'edit_info'
][
'update_version'
]
=
destination_version
destination_block
[
'edit_info'
][
'update_version'
]
=
destination_version
destination_block
[
'edit_info'
][
'edited_by'
]
=
user_id
destination_block
[
'edit_info'
][
'edited_by'
]
=
user_id
destination_block
[
'edit_info'
][
'edited_on'
]
=
datetime
.
datetime
.
now
(
UTC
)
else
:
else
:
destination_block
=
self
.
_new_block
(
destination_block
=
self
.
_new_block
(
user_id
,
new_block
[
'category'
],
user_id
,
new_block
[
'category'
],
...
@@ -1939,7 +1955,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
...
@@ -1939,7 +1955,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
for
entry
in
entries
for
entry
in
entries
]
]
def
get_courses_for_wiki
(
self
,
wiki_slug
):
def
get_courses_for_wiki
(
self
,
wiki_slug
,
**
kwargs
):
"""
"""
Return the list of courses which use this wiki_slug
Return the list of courses which use this wiki_slug
:param wiki_slug: the course wiki root slug
:param wiki_slug: the course wiki root slug
...
...
common/lib/xmodule/xmodule/modulestore/split_mongo/split_draft.py
View file @
9efe5d92
...
@@ -48,16 +48,22 @@ class DraftVersioningModuleStore(ModuleStoreDraftAndPublished, SplitMongoModuleS
...
@@ -48,16 +48,22 @@ class DraftVersioningModuleStore(ModuleStoreDraftAndPublished, SplitMongoModuleS
item
=
super
(
DraftVersioningModuleStore
,
self
)
.
create_course
(
item
=
super
(
DraftVersioningModuleStore
,
self
)
.
create_course
(
org
,
course
,
run
,
user_id
,
master_branch
=
master_branch
,
**
kwargs
org
,
course
,
run
,
user_id
,
master_branch
=
master_branch
,
**
kwargs
)
)
self
.
_auto_publish_no_children
(
item
.
location
,
item
.
location
.
category
,
user_id
)
self
.
_auto_publish_no_children
(
item
.
location
,
item
.
location
.
category
,
user_id
,
**
kwargs
)
return
item
return
item
def
get_courses
(
self
):
def
get_courses
(
self
,
**
kwargs
):
"""
"""
Returns all the courses on the Draft
branch (which is a superset of the courses on the Published branch)
.
Returns all the courses on the Draft
or Published branch depending on the branch setting
.
"""
"""
return
super
(
DraftVersioningModuleStore
,
self
)
.
get_courses
(
ModuleStoreEnum
.
BranchName
.
draft
)
branch_setting
=
self
.
get_branch_setting
()
if
branch_setting
==
ModuleStoreEnum
.
Branch
.
draft_preferred
:
return
super
(
DraftVersioningModuleStore
,
self
)
.
get_courses
(
ModuleStoreEnum
.
BranchName
.
draft
,
**
kwargs
)
elif
branch_setting
==
ModuleStoreEnum
.
Branch
.
published_only
:
return
super
(
DraftVersioningModuleStore
,
self
)
.
get_courses
(
ModuleStoreEnum
.
BranchName
.
published
,
**
kwargs
)
else
:
raise
InsufficientSpecificationError
()
def
_auto_publish_no_children
(
self
,
location
,
category
,
user_id
):
def
_auto_publish_no_children
(
self
,
location
,
category
,
user_id
,
**
kwargs
):
"""
"""
Publishes item if the category is DIRECT_ONLY. This assumes another method has checked that
Publishes item if the category is DIRECT_ONLY. This assumes another method has checked that
location points to the head of the branch and ignores the version. If you call this in any
location points to the head of the branch and ignores the version. If you call this in any
...
@@ -66,16 +72,17 @@ class DraftVersioningModuleStore(ModuleStoreDraftAndPublished, SplitMongoModuleS
...
@@ -66,16 +72,17 @@ class DraftVersioningModuleStore(ModuleStoreDraftAndPublished, SplitMongoModuleS
"""
"""
if
location
.
branch
==
ModuleStoreEnum
.
BranchName
.
draft
and
category
in
DIRECT_ONLY_CATEGORIES
:
if
location
.
branch
==
ModuleStoreEnum
.
BranchName
.
draft
and
category
in
DIRECT_ONLY_CATEGORIES
:
# version_agnostic b/c of above assumption in docstring
# version_agnostic b/c of above assumption in docstring
self
.
publish
(
location
.
version_agnostic
(),
user_id
,
blacklist
=
EXCLUDE_ALL
)
self
.
publish
(
location
.
version_agnostic
(),
user_id
,
blacklist
=
EXCLUDE_ALL
,
**
kwargs
)
def
update_item
(
self
,
descriptor
,
user_id
,
allow_not_found
=
False
,
force
=
False
):
def
update_item
(
self
,
descriptor
,
user_id
,
allow_not_found
=
False
,
force
=
False
,
**
kwargs
):
item
=
super
(
DraftVersioningModuleStore
,
self
)
.
update_item
(
item
=
super
(
DraftVersioningModuleStore
,
self
)
.
update_item
(
descriptor
,
descriptor
,
user_id
,
user_id
,
allow_not_found
=
allow_not_found
,
allow_not_found
=
allow_not_found
,
force
=
force
force
=
force
,
**
kwargs
)
)
self
.
_auto_publish_no_children
(
item
.
location
,
item
.
location
.
category
,
user_id
)
self
.
_auto_publish_no_children
(
item
.
location
,
item
.
location
.
category
,
user_id
,
**
kwargs
)
return
item
return
item
def
create_item
(
def
create_item
(
...
@@ -88,7 +95,7 @@ class DraftVersioningModuleStore(ModuleStoreDraftAndPublished, SplitMongoModuleS
...
@@ -88,7 +95,7 @@ class DraftVersioningModuleStore(ModuleStoreDraftAndPublished, SplitMongoModuleS
definition_locator
=
definition_locator
,
fields
=
fields
,
definition_locator
=
definition_locator
,
fields
=
fields
,
force
=
force
,
continue_version
=
continue_version
,
**
kwargs
force
=
force
,
continue_version
=
continue_version
,
**
kwargs
)
)
self
.
_auto_publish_no_children
(
item
.
location
,
item
.
location
.
category
,
user_id
)
self
.
_auto_publish_no_children
(
item
.
location
,
item
.
location
.
category
,
user_id
,
**
kwargs
)
return
item
return
item
def
create_child
(
def
create_child
(
...
@@ -99,7 +106,7 @@ class DraftVersioningModuleStore(ModuleStoreDraftAndPublished, SplitMongoModuleS
...
@@ -99,7 +106,7 @@ class DraftVersioningModuleStore(ModuleStoreDraftAndPublished, SplitMongoModuleS
user_id
,
parent_usage_key
,
block_type
,
block_id
=
block_id
,
user_id
,
parent_usage_key
,
block_type
,
block_id
=
block_id
,
fields
=
fields
,
continue_version
=
continue_version
,
**
kwargs
fields
=
fields
,
continue_version
=
continue_version
,
**
kwargs
)
)
self
.
_auto_publish_no_children
(
parent_usage_key
,
item
.
location
.
category
,
user_id
)
self
.
_auto_publish_no_children
(
parent_usage_key
,
item
.
location
.
category
,
user_id
,
**
kwargs
)
return
item
return
item
def
delete_item
(
self
,
location
,
user_id
,
revision
=
None
,
**
kwargs
):
def
delete_item
(
self
,
location
,
user_id
,
revision
=
None
,
**
kwargs
):
...
@@ -134,8 +141,8 @@ class DraftVersioningModuleStore(ModuleStoreDraftAndPublished, SplitMongoModuleS
...
@@ -134,8 +141,8 @@ class DraftVersioningModuleStore(ModuleStoreDraftAndPublished, SplitMongoModuleS
for
branch
in
branches_to_delete
:
for
branch
in
branches_to_delete
:
branched_location
=
location
.
for_branch
(
branch
)
branched_location
=
location
.
for_branch
(
branch
)
parent_loc
=
self
.
get_parent_location
(
branched_location
)
parent_loc
=
self
.
get_parent_location
(
branched_location
)
SplitMongoModuleStore
.
delete_item
(
self
,
branched_location
,
user_id
,
**
kwargs
)
SplitMongoModuleStore
.
delete_item
(
self
,
branched_location
,
user_id
)
self
.
_auto_publish_no_children
(
parent_loc
,
parent_loc
.
category
,
user_id
)
self
.
_auto_publish_no_children
(
parent_loc
,
parent_loc
.
category
,
user_id
,
**
kwargs
)
def
_map_revision_to_branch
(
self
,
key
,
revision
=
None
):
def
_map_revision_to_branch
(
self
,
key
,
revision
=
None
):
"""
"""
...
@@ -157,25 +164,20 @@ class DraftVersioningModuleStore(ModuleStoreDraftAndPublished, SplitMongoModuleS
...
@@ -157,25 +164,20 @@ class DraftVersioningModuleStore(ModuleStoreDraftAndPublished, SplitMongoModuleS
usage_key
=
self
.
_map_revision_to_branch
(
usage_key
,
revision
=
revision
)
usage_key
=
self
.
_map_revision_to_branch
(
usage_key
,
revision
=
revision
)
return
super
(
DraftVersioningModuleStore
,
self
)
.
has_item
(
usage_key
)
return
super
(
DraftVersioningModuleStore
,
self
)
.
has_item
(
usage_key
)
def
get_item
(
self
,
usage_key
,
depth
=
0
,
revision
=
None
):
def
get_item
(
self
,
usage_key
,
depth
=
0
,
revision
=
None
,
**
kwargs
):
"""
"""
Returns the item identified by usage_key and revision.
Returns the item identified by usage_key and revision.
"""
"""
usage_key
=
self
.
_map_revision_to_branch
(
usage_key
,
revision
=
revision
)
usage_key
=
self
.
_map_revision_to_branch
(
usage_key
,
revision
=
revision
)
return
super
(
DraftVersioningModuleStore
,
self
)
.
get_item
(
usage_key
,
depth
=
depth
)
return
super
(
DraftVersioningModuleStore
,
self
)
.
get_item
(
usage_key
,
depth
=
depth
,
**
kwargs
)
def
get_items
(
self
,
course_locator
,
settings
=
None
,
content
=
None
,
revision
=
None
,
**
kwargs
):
def
get_items
(
self
,
course_locator
,
revision
=
None
,
**
kwargs
):
"""
"""
Returns a list of XModuleDescriptor instances for the matching items within the course with
Returns a list of XModuleDescriptor instances for the matching items within the course with
the given course_locator.
the given course_locator.
"""
"""
course_locator
=
self
.
_map_revision_to_branch
(
course_locator
,
revision
=
revision
)
course_locator
=
self
.
_map_revision_to_branch
(
course_locator
,
revision
=
revision
)
return
super
(
DraftVersioningModuleStore
,
self
)
.
get_items
(
return
super
(
DraftVersioningModuleStore
,
self
)
.
get_items
(
course_locator
,
**
kwargs
)
course_locator
,
settings
=
settings
,
content
=
content
,
**
kwargs
)
def
get_parent_location
(
self
,
location
,
revision
=
None
,
**
kwargs
):
def
get_parent_location
(
self
,
location
,
revision
=
None
,
**
kwargs
):
'''
'''
...
@@ -200,14 +202,18 @@ class DraftVersioningModuleStore(ModuleStoreDraftAndPublished, SplitMongoModuleS
...
@@ -200,14 +202,18 @@ class DraftVersioningModuleStore(ModuleStoreDraftAndPublished, SplitMongoModuleS
:param xblock: the block to check
:param xblock: the block to check
:return: True if the draft and published versions differ
:return: True if the draft and published versions differ
"""
"""
# TODO for better performance: lookup the courses and get the block entry, don't create the instances
def
get_block
(
branch_name
):
draft
=
self
.
get_item
(
xblock
.
location
.
for_branch
(
ModuleStoreEnum
.
BranchName
.
draft
))
course_structure
=
self
.
_lookup_course
(
xblock
.
location
.
for_branch
(
branch_name
))[
'structure'
]
try
:
return
self
.
_get_block_from_structure
(
course_structure
,
xblock
.
location
.
block_id
)
published
=
self
.
get_item
(
xblock
.
location
.
for_branch
(
ModuleStoreEnum
.
BranchName
.
published
))
except
ItemNotFoundError
:
draft_block
=
get_block
(
ModuleStoreEnum
.
BranchName
.
draft
)
published_block
=
get_block
(
ModuleStoreEnum
.
BranchName
.
published
)
if
not
published_block
:
return
True
return
True
return
draft
.
update_version
!=
published
.
source_version
# check if the draft has changed since the published was created
return
self
.
_get_version
(
draft_block
)
!=
self
.
_get_version
(
published_block
)
def
publish
(
self
,
location
,
user_id
,
blacklist
=
None
,
**
kwargs
):
def
publish
(
self
,
location
,
user_id
,
blacklist
=
None
,
**
kwargs
):
"""
"""
...
@@ -224,15 +230,15 @@ class DraftVersioningModuleStore(ModuleStoreDraftAndPublished, SplitMongoModuleS
...
@@ -224,15 +230,15 @@ class DraftVersioningModuleStore(ModuleStoreDraftAndPublished, SplitMongoModuleS
[
location
],
[
location
],
blacklist
=
blacklist
blacklist
=
blacklist
)
)
return
self
.
get_item
(
location
.
for_branch
(
ModuleStoreEnum
.
BranchName
.
published
))
return
self
.
get_item
(
location
.
for_branch
(
ModuleStoreEnum
.
BranchName
.
published
)
,
**
kwargs
)
def
unpublish
(
self
,
location
,
user_id
):
def
unpublish
(
self
,
location
,
user_id
,
**
kwargs
):
"""
"""
Deletes the published version of the item.
Deletes the published version of the item.
Returns the newly unpublished item.
Returns the newly unpublished item.
"""
"""
self
.
delete_item
(
location
,
user_id
,
revision
=
ModuleStoreEnum
.
RevisionOption
.
published_only
)
self
.
delete_item
(
location
,
user_id
,
revision
=
ModuleStoreEnum
.
RevisionOption
.
published_only
)
return
self
.
get_item
(
location
.
for_branch
(
ModuleStoreEnum
.
BranchName
.
draft
))
return
self
.
get_item
(
location
.
for_branch
(
ModuleStoreEnum
.
BranchName
.
draft
)
,
**
kwargs
)
def
revert_to_published
(
self
,
location
,
user_id
):
def
revert_to_published
(
self
,
location
,
user_id
):
"""
"""
...
@@ -255,24 +261,13 @@ class DraftVersioningModuleStore(ModuleStoreDraftAndPublished, SplitMongoModuleS
...
@@ -255,24 +261,13 @@ class DraftVersioningModuleStore(ModuleStoreDraftAndPublished, SplitMongoModuleS
PublishState.public - published exists and is the same as draft
PublishState.public - published exists and is the same as draft
PublishState.private - no published version exists
PublishState.private - no published version exists
"""
"""
def
get_head
(
branch
):
draft_head
=
self
.
_get_head
(
xblock
,
ModuleStoreEnum
.
BranchName
.
draft
)
course_structure
=
self
.
_lookup_course
(
xblock
.
location
.
course_key
.
for_branch
(
branch
))[
'structure'
]
published_head
=
self
.
_get_head
(
xblock
,
ModuleStoreEnum
.
BranchName
.
published
)
return
self
.
_get_block_from_structure
(
course_structure
,
xblock
.
location
.
block_id
)
def
get_version
(
block
):
"""
Return the version of the given database representation of a block.
"""
#TODO: make this method a more generic helper
return
block
[
'edit_info'
]
.
get
(
'source_version'
,
block
[
'edit_info'
][
'update_version'
])
draft_head
=
get_head
(
ModuleStoreEnum
.
BranchName
.
draft
)
published_head
=
get_head
(
ModuleStoreEnum
.
BranchName
.
published
)
if
not
published_head
:
if
not
published_head
:
# published version does not exist
# published version does not exist
return
PublishState
.
private
return
PublishState
.
private
elif
get_version
(
draft_head
)
==
get_version
(
published_head
):
elif
self
.
_get_version
(
draft_head
)
==
self
.
_
get_version
(
published_head
):
# published and draft versions are equal
# published and draft versions are equal
return
PublishState
.
public
return
PublishState
.
public
else
:
else
:
...
@@ -287,3 +282,13 @@ class DraftVersioningModuleStore(ModuleStoreDraftAndPublished, SplitMongoModuleS
...
@@ -287,3 +282,13 @@ class DraftVersioningModuleStore(ModuleStoreDraftAndPublished, SplitMongoModuleS
"""
"""
# This is a no-op in Split since a draft version of the data always remains
# This is a no-op in Split since a draft version of the data always remains
pass
pass
def
_get_head
(
self
,
xblock
,
branch
):
course_structure
=
self
.
_lookup_course
(
xblock
.
location
.
course_key
.
for_branch
(
branch
))[
'structure'
]
return
self
.
_get_block_from_structure
(
course_structure
,
xblock
.
location
.
block_id
)
def
_get_version
(
self
,
block
):
"""
Return the version of the given database representation of a block.
"""
return
block
[
'edit_info'
]
.
get
(
'source_version'
,
block
[
'edit_info'
][
'update_version'
])
common/lib/xmodule/xmodule/modulestore/split_mongo/split_mongo_kvs.py
View file @
9efe5d92
...
@@ -15,26 +15,27 @@ class SplitMongoKVS(InheritanceKeyValueStore):
...
@@ -15,26 +15,27 @@ class SplitMongoKVS(InheritanceKeyValueStore):
known to the MongoModuleStore (data, children, and metadata)
known to the MongoModuleStore (data, children, and metadata)
"""
"""
def
__init__
(
self
,
definition
,
fields
,
inherited_settin
gs
):
def
__init__
(
self
,
definition
,
initial_values
,
inherited_settings
,
**
kwar
gs
):
"""
"""
:param definition: either a lazyloader or definition id for the definition
:param definition: either a lazyloader or definition id for the definition
:param
fields: a dictionary of the locally set field
s
:param
initial_values: a dictionary of the locally set value
s
:param inherited_settings: the json value of each inheritable field from above this.
:param inherited_settings: the json value of each inheritable field from above this.
Note, local fields may override and disagree w/ this b/c this says what the value
Note, local fields may override and disagree w/ this b/c this says what the value
should be if the field is undefined.
should be if the field is undefined.
"""
"""
# deepcopy so that manipulations of fields does not pollute the source
# deepcopy so that manipulations of fields does not pollute the source
super
(
SplitMongoKVS
,
self
)
.
__init__
(
copy
.
deepcopy
(
field
s
),
inherited_settings
)
super
(
SplitMongoKVS
,
self
)
.
__init__
(
copy
.
deepcopy
(
initial_value
s
),
inherited_settings
)
self
.
_definition
=
definition
# either a DefinitionLazyLoader or the db id of the definition.
self
.
_definition
=
definition
# either a DefinitionLazyLoader or the db id of the definition.
# if the db id, then the definition is presumed to be loaded into _fields
# if the db id, then the definition is presumed to be loaded into _fields
# a decorator function for field values (to be called when a field is accessed)
self
.
field_decorator
=
kwargs
.
get
(
'field_decorator'
,
lambda
x
:
x
)
def
get
(
self
,
key
):
# simplest case, field is directly set
if
key
.
field_name
in
self
.
_fields
:
return
self
.
_fields
[
key
.
field_name
]
def
get
(
self
,
key
):
# load the field, if needed
if
key
.
field_name
not
in
self
.
_fields
:
# parent undefined in editing runtime (I think)
# parent undefined in editing runtime (I think)
if
key
.
scope
==
Scope
.
parent
:
if
key
.
scope
==
Scope
.
parent
:
# see STUD-624. Right now copies MongoKeyValueStore.get's behavior of returning None
# see STUD-624. Right now copies MongoKeyValueStore.get's behavior of returning None
...
@@ -48,13 +49,19 @@ class SplitMongoKVS(InheritanceKeyValueStore):
...
@@ -48,13 +49,19 @@ class SplitMongoKVS(InheritanceKeyValueStore):
elif
key
.
scope
==
Scope
.
content
:
elif
key
.
scope
==
Scope
.
content
:
if
isinstance
(
self
.
_definition
,
DefinitionLazyLoader
):
if
isinstance
(
self
.
_definition
,
DefinitionLazyLoader
):
self
.
_load_definition
()
self
.
_load_definition
()
if
key
.
field_name
in
self
.
_fields
:
else
:
return
self
.
_fields
[
key
.
field_name
]
raise
KeyError
()
raise
KeyError
()
else
:
else
:
raise
InvalidScopeError
(
key
)
raise
InvalidScopeError
(
key
)
if
key
.
field_name
in
self
.
_fields
:
field_value
=
self
.
_fields
[
key
.
field_name
]
# return the "decorated" field value
return
self
.
field_decorator
(
field_value
)
return
None
def
set
(
self
,
key
,
value
):
def
set
(
self
,
key
,
value
):
# handle any special cases
# handle any special cases
if
key
.
scope
not
in
[
Scope
.
children
,
Scope
.
settings
,
Scope
.
content
]:
if
key
.
scope
not
in
[
Scope
.
children
,
Scope
.
settings
,
Scope
.
content
]:
...
...
common/lib/xmodule/xmodule/modulestore/tests/test_contentstore.py
View file @
9efe5d92
...
@@ -9,7 +9,8 @@ from tempfile import mkdtemp
...
@@ -9,7 +9,8 @@ from tempfile import mkdtemp
import
path
import
path
import
shutil
import
shutil
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
,
AssetLocation
from
opaque_keys.edx.locator
import
CourseLocator
,
AssetLocator
from
opaque_keys.edx.keys
import
AssetKey
from
xmodule.tests
import
DATA_DIR
from
xmodule.tests
import
DATA_DIR
from
xmodule.contentstore.mongo
import
MongoContentStore
from
xmodule.contentstore.mongo
import
MongoContentStore
from
xmodule.contentstore.content
import
StaticContent
from
xmodule.contentstore.content
import
StaticContent
...
@@ -41,13 +42,13 @@ class TestContentstore(unittest.TestCase):
...
@@ -41,13 +42,13 @@ class TestContentstore(unittest.TestCase):
Restores deprecated values
Restores deprecated values
"""
"""
if
cls
.
asset_deprecated
is
not
None
:
if
cls
.
asset_deprecated
is
not
None
:
setattr
(
AssetLocat
ion
,
'deprecated'
,
cls
.
asset_deprecated
)
setattr
(
AssetLocat
or
,
'deprecated'
,
cls
.
asset_deprecated
)
else
:
else
:
delattr
(
AssetLocat
ion
,
'deprecated'
)
delattr
(
AssetLocat
or
,
'deprecated'
)
if
cls
.
ssck_deprecated
is
not
None
:
if
cls
.
ssck_deprecated
is
not
None
:
setattr
(
SlashSeparatedCourseKey
,
'deprecated'
,
cls
.
ssck_deprecated
)
setattr
(
CourseLocator
,
'deprecated'
,
cls
.
ssck_deprecated
)
else
:
else
:
delattr
(
SlashSeparatedCourseKey
,
'deprecated'
)
delattr
(
CourseLocator
,
'deprecated'
)
return
super
(
TestContentstore
,
cls
)
.
tearDownClass
()
return
super
(
TestContentstore
,
cls
)
.
tearDownClass
()
def
set_up_assets
(
self
,
deprecated
):
def
set_up_assets
(
self
,
deprecated
):
...
@@ -59,11 +60,11 @@ class TestContentstore(unittest.TestCase):
...
@@ -59,11 +60,11 @@ class TestContentstore(unittest.TestCase):
self
.
contentstore
=
MongoContentStore
(
HOST
,
DB
,
port
=
PORT
)
self
.
contentstore
=
MongoContentStore
(
HOST
,
DB
,
port
=
PORT
)
self
.
addCleanup
(
self
.
contentstore
.
_drop_database
)
# pylint: disable=protected-access
self
.
addCleanup
(
self
.
contentstore
.
_drop_database
)
# pylint: disable=protected-access
setattr
(
AssetLocat
ion
,
'deprecated'
,
deprecated
)
setattr
(
AssetLocat
or
,
'deprecated'
,
deprecated
)
setattr
(
SlashSeparatedCourseKey
,
'deprecated'
,
deprecated
)
setattr
(
CourseLocator
,
'deprecated'
,
deprecated
)
self
.
course1_key
=
SlashSeparatedCourseKey
(
'test'
,
'asset_test'
,
'2014_07'
)
self
.
course1_key
=
CourseLocator
(
'test'
,
'asset_test'
,
'2014_07'
)
self
.
course2_key
=
SlashSeparatedCourseKey
(
'test'
,
'asset_test2'
,
'2014_07'
)
self
.
course2_key
=
CourseLocator
(
'test'
,
'asset_test2'
,
'2014_07'
)
self
.
course1_files
=
[
'contains.sh'
,
'picture1.jpg'
,
'picture2.jpg'
]
self
.
course1_files
=
[
'contains.sh'
,
'picture1.jpg'
,
'picture2.jpg'
]
self
.
course2_files
=
[
'picture1.jpg'
,
'picture3.jpg'
,
'door_2.ogg'
]
self
.
course2_files
=
[
'picture1.jpg'
,
'picture3.jpg'
,
'door_2.ogg'
]
...
@@ -154,13 +155,13 @@ class TestContentstore(unittest.TestCase):
...
@@ -154,13 +155,13 @@ class TestContentstore(unittest.TestCase):
course1_assets
,
count
=
self
.
contentstore
.
get_all_content_for_course
(
self
.
course1_key
)
course1_assets
,
count
=
self
.
contentstore
.
get_all_content_for_course
(
self
.
course1_key
)
self
.
assertEqual
(
count
,
len
(
self
.
course1_files
),
course1_assets
)
self
.
assertEqual
(
count
,
len
(
self
.
course1_files
),
course1_assets
)
for
asset
in
course1_assets
:
for
asset
in
course1_assets
:
parsed
=
Asset
Location
.
from_deprecated
_string
(
asset
[
'filename'
])
parsed
=
Asset
Key
.
from
_string
(
asset
[
'filename'
])
self
.
assertIn
(
parsed
.
name
,
self
.
course1_files
)
self
.
assertIn
(
parsed
.
name
,
self
.
course1_files
)
course1_assets
,
__
=
self
.
contentstore
.
get_all_content_for_course
(
self
.
course1_key
,
1
,
1
)
course1_assets
,
__
=
self
.
contentstore
.
get_all_content_for_course
(
self
.
course1_key
,
1
,
1
)
self
.
assertEqual
(
len
(
course1_assets
),
1
,
course1_assets
)
self
.
assertEqual
(
len
(
course1_assets
),
1
,
course1_assets
)
fake_course
=
SlashSeparatedCourseKey
(
'test'
,
'fake'
,
'non'
)
fake_course
=
CourseLocator
(
'test'
,
'fake'
,
'non'
)
course_assets
,
count
=
self
.
contentstore
.
get_all_content_for_course
(
fake_course
)
course_assets
,
count
=
self
.
contentstore
.
get_all_content_for_course
(
fake_course
)
self
.
assertEqual
(
count
,
0
)
self
.
assertEqual
(
count
,
0
)
self
.
assertEqual
(
course_assets
,
[])
self
.
assertEqual
(
course_assets
,
[])
...
@@ -183,7 +184,7 @@ class TestContentstore(unittest.TestCase):
...
@@ -183,7 +184,7 @@ class TestContentstore(unittest.TestCase):
copy_all_course_assets
copy_all_course_assets
"""
"""
self
.
set_up_assets
(
deprecated
)
self
.
set_up_assets
(
deprecated
)
dest_course
=
SlashSeparatedCourseKey
(
'test'
,
'destination'
,
'copy'
)
dest_course
=
CourseLocator
(
'test'
,
'destination'
,
'copy'
)
self
.
contentstore
.
copy_all_course_assets
(
self
.
course1_key
,
dest_course
)
self
.
contentstore
.
copy_all_course_assets
(
self
.
course1_key
,
dest_course
)
for
filename
in
self
.
course1_files
:
for
filename
in
self
.
course1_files
:
asset_key
=
self
.
course1_key
.
make_asset_key
(
'asset'
,
filename
)
asset_key
=
self
.
course1_key
.
make_asset_key
(
'asset'
,
filename
)
...
...
common/lib/xmodule/xmodule/modulestore/tests/test_mixed_modulestore.py
View file @
9efe5d92
import
pymongo
import
pymongo
from
uuid
import
uuid4
from
uuid
import
uuid4
import
ddt
import
ddt
import
itertools
from
importlib
import
import_module
from
importlib
import
import_module
from
collections
import
namedtuple
from
collections
import
namedtuple
import
unittest
import
unittest
...
@@ -8,7 +9,6 @@ import datetime
...
@@ -8,7 +9,6 @@ import datetime
from
pytz
import
UTC
from
pytz
import
UTC
from
xmodule.tests
import
DATA_DIR
from
xmodule.tests
import
DATA_DIR
from
opaque_keys.edx.locations
import
Location
from
xmodule.modulestore
import
ModuleStoreEnum
,
PublishState
from
xmodule.modulestore
import
ModuleStoreEnum
,
PublishState
from
xmodule.modulestore.exceptions
import
ItemNotFoundError
from
xmodule.modulestore.exceptions
import
ItemNotFoundError
from
xmodule.exceptions
import
InvalidVersionError
from
xmodule.exceptions
import
InvalidVersionError
...
@@ -21,6 +21,7 @@ from opaque_keys.edx.locator import BlockUsageLocator, CourseLocator
...
@@ -21,6 +21,7 @@ from opaque_keys.edx.locator import BlockUsageLocator, CourseLocator
from
django.conf
import
settings
from
django.conf
import
settings
from
xmodule.modulestore.tests.factories
import
check_mongo_calls
from
xmodule.modulestore.tests.factories
import
check_mongo_calls
from
xmodule.modulestore.search
import
path_to_location
from
xmodule.modulestore.search
import
path_to_location
from
xmodule.modulestore.exceptions
import
DuplicateCourseError
if
not
settings
.
configured
:
if
not
settings
.
configured
:
settings
.
configure
()
settings
.
configure
()
from
xmodule.modulestore.mixed
import
MixedModuleStore
from
xmodule.modulestore.mixed
import
MixedModuleStore
...
@@ -90,7 +91,7 @@ class TestMixedModuleStore(unittest.TestCase):
...
@@ -90,7 +91,7 @@ class TestMixedModuleStore(unittest.TestCase):
"""
"""
AssertEqual replacement for CourseLocator
AssertEqual replacement for CourseLocator
"""
"""
if
loc1
.
version_agnostic
()
!=
loc2
.
version_agnostic
(
):
if
loc1
.
for_branch
(
None
)
!=
loc2
.
for_branch
(
None
):
self
.
fail
(
self
.
_formatMessage
(
msg
,
u"{} != {}"
.
format
(
unicode
(
loc1
),
unicode
(
loc2
))))
self
.
fail
(
self
.
_formatMessage
(
msg
,
u"{} != {}"
.
format
(
unicode
(
loc1
),
unicode
(
loc2
))))
def
setUp
(
self
):
def
setUp
(
self
):
...
@@ -124,13 +125,13 @@ class TestMixedModuleStore(unittest.TestCase):
...
@@ -124,13 +125,13 @@ class TestMixedModuleStore(unittest.TestCase):
# create course
# create course
self
.
course
=
self
.
store
.
create_course
(
course_key
.
org
,
course_key
.
course
,
course_key
.
run
,
self
.
user_id
)
self
.
course
=
self
.
store
.
create_course
(
course_key
.
org
,
course_key
.
course
,
course_key
.
run
,
self
.
user_id
)
if
isinstance
(
self
.
course
.
id
,
CourseLocator
):
if
isinstance
(
self
.
course
.
id
,
CourseLocator
):
self
.
course_locations
[
self
.
MONGO_COURSEID
]
=
self
.
course
.
location
.
version_agnostic
()
self
.
course_locations
[
self
.
MONGO_COURSEID
]
=
self
.
course
.
location
else
:
else
:
self
.
assertEqual
(
self
.
course
.
id
,
course_key
)
self
.
assertEqual
(
self
.
course
.
id
,
course_key
)
# create chapter
# create chapter
chapter
=
self
.
store
.
create_child
(
self
.
user_id
,
self
.
course
.
location
,
'chapter'
,
block_id
=
'Overview'
)
chapter
=
self
.
store
.
create_child
(
self
.
user_id
,
self
.
course
.
location
,
'chapter'
,
block_id
=
'Overview'
)
self
.
writable_chapter_location
=
chapter
.
location
.
version_agnostic
()
self
.
writable_chapter_location
=
chapter
.
location
def
_create_block_hierarchy
(
self
):
def
_create_block_hierarchy
(
self
):
"""
"""
...
@@ -175,13 +176,13 @@ class TestMixedModuleStore(unittest.TestCase):
...
@@ -175,13 +176,13 @@ class TestMixedModuleStore(unittest.TestCase):
def
create_sub_tree
(
parent
,
block_info
):
def
create_sub_tree
(
parent
,
block_info
):
block
=
self
.
store
.
create_child
(
block
=
self
.
store
.
create_child
(
self
.
user_id
,
parent
.
location
.
version_agnostic
()
,
self
.
user_id
,
parent
.
location
,
block_info
.
category
,
block_id
=
block_info
.
display_name
,
block_info
.
category
,
block_id
=
block_info
.
display_name
,
fields
=
{
'display_name'
:
block_info
.
display_name
},
fields
=
{
'display_name'
:
block_info
.
display_name
},
)
)
for
tree
in
block_info
.
sub_tree
:
for
tree
in
block_info
.
sub_tree
:
create_sub_tree
(
block
,
tree
)
create_sub_tree
(
block
,
tree
)
setattr
(
self
,
block_info
.
field_name
,
block
.
location
.
version_agnostic
()
)
setattr
(
self
,
block_info
.
field_name
,
block
.
location
)
for
tree
in
trees
:
for
tree
in
trees
:
create_sub_tree
(
self
.
course
,
tree
)
create_sub_tree
(
self
.
course
,
tree
)
...
@@ -192,6 +193,10 @@ class TestMixedModuleStore(unittest.TestCase):
...
@@ -192,6 +193,10 @@ class TestMixedModuleStore(unittest.TestCase):
"""
"""
return
self
.
course_locations
[
string
]
.
course_key
return
self
.
course_locations
[
string
]
.
course_key
def
_initialize_mixed
(
self
):
self
.
store
=
MixedModuleStore
(
None
,
create_modulestore_instance
=
create_modulestore_instance
,
**
self
.
options
)
self
.
addCleanup
(
self
.
store
.
close_all_connections
)
def
initdb
(
self
,
default
):
def
initdb
(
self
,
default
):
"""
"""
Initialize the database and create one test course in it
Initialize the database and create one test course in it
...
@@ -203,8 +208,7 @@ class TestMixedModuleStore(unittest.TestCase):
...
@@ -203,8 +208,7 @@ class TestMixedModuleStore(unittest.TestCase):
if
index
>
0
:
if
index
>
0
:
store_configs
[
index
],
store_configs
[
0
]
=
store_configs
[
0
],
store_configs
[
index
]
store_configs
[
index
],
store_configs
[
0
]
=
store_configs
[
0
],
store_configs
[
index
]
break
break
self
.
store
=
MixedModuleStore
(
None
,
create_modulestore_instance
=
create_modulestore_instance
,
**
self
.
options
)
self
.
_initialize_mixed
()
self
.
addCleanup
(
self
.
store
.
close_all_connections
)
# convert to CourseKeys
# convert to CourseKeys
self
.
course_locations
=
{
self
.
course_locations
=
{
...
@@ -216,12 +220,9 @@ class TestMixedModuleStore(unittest.TestCase):
...
@@ -216,12 +220,9 @@ class TestMixedModuleStore(unittest.TestCase):
course_id
:
course_key
.
make_usage_key
(
'course'
,
course_key
.
run
)
course_id
:
course_key
.
make_usage_key
(
'course'
,
course_key
.
run
)
for
course_id
,
course_key
in
self
.
course_locations
.
iteritems
()
# pylint: disable=maybe-no-member
for
course_id
,
course_key
in
self
.
course_locations
.
iteritems
()
# pylint: disable=maybe-no-member
}
}
if
default
==
'split'
:
self
.
fake_location
=
CourseLocator
(
self
.
fake_location
=
self
.
course_locations
[
self
.
MONGO_COURSEID
]
.
course_key
.
make_usage_key
(
'vertical'
,
'fake'
)
'foo'
,
'bar'
,
'slowly'
,
branch
=
ModuleStoreEnum
.
BranchName
.
draft
)
.
make_usage_key
(
'vertical'
,
'baz'
)
else
:
self
.
fake_location
=
Location
(
'foo'
,
'bar'
,
'slowly'
,
'vertical'
,
'baz'
)
self
.
xml_chapter_location
=
self
.
course_locations
[
self
.
XML_COURSEID1
]
.
replace
(
self
.
xml_chapter_location
=
self
.
course_locations
[
self
.
XML_COURSEID1
]
.
replace
(
category
=
'chapter'
,
name
=
'Overview'
category
=
'chapter'
,
name
=
'Overview'
)
)
...
@@ -248,6 +249,23 @@ class TestMixedModuleStore(unittest.TestCase):
...
@@ -248,6 +249,23 @@ class TestMixedModuleStore(unittest.TestCase):
SlashSeparatedCourseKey
(
'foo'
,
'bar'
,
'2012_Fall'
)),
mongo_ms_type
SlashSeparatedCourseKey
(
'foo'
,
'bar'
,
'2012_Fall'
)),
mongo_ms_type
)
)
@ddt.data
(
*
itertools
.
product
(
(
ModuleStoreEnum
.
Type
.
mongo
,
ModuleStoreEnum
.
Type
.
split
),
(
True
,
False
)
))
@ddt.unpack
def
test_duplicate_course_error
(
self
,
default_ms
,
reset_mixed_mappings
):
"""
Make sure we get back the store type we expect for given mappings
"""
self
.
_initialize_mixed
()
with
self
.
store
.
default_store
(
default_ms
):
self
.
store
.
create_course
(
'org_x'
,
'course_y'
,
'run_z'
,
self
.
user_id
)
if
reset_mixed_mappings
:
self
.
store
.
mappings
=
{}
with
self
.
assertRaises
(
DuplicateCourseError
):
self
.
store
.
create_course
(
'org_x'
,
'course_y'
,
'run_z'
,
self
.
user_id
)
# split has one lookup for the course and then one for the course items
# split has one lookup for the course and then one for the course items
@ddt.data
((
'draft'
,
1
,
0
),
(
'split'
,
2
,
0
))
@ddt.data
((
'draft'
,
1
,
0
),
(
'split'
,
2
,
0
))
@ddt.unpack
@ddt.unpack
...
@@ -308,7 +326,7 @@ class TestMixedModuleStore(unittest.TestCase):
...
@@ -308,7 +326,7 @@ class TestMixedModuleStore(unittest.TestCase):
course_locn
=
self
.
course_locations
[
self
.
XML_COURSEID1
]
course_locn
=
self
.
course_locations
[
self
.
XML_COURSEID1
]
# NOTE: use get_course if you just want the course. get_items is expensive
# NOTE: use get_course if you just want the course. get_items is expensive
modules
=
self
.
store
.
get_items
(
course_locn
.
course_key
,
category
=
'course'
)
modules
=
self
.
store
.
get_items
(
course_locn
.
course_key
,
qualifiers
=
{
'category'
:
'course'
}
)
self
.
assertEqual
(
len
(
modules
),
1
)
self
.
assertEqual
(
len
(
modules
),
1
)
self
.
assertEqual
(
modules
[
0
]
.
location
,
course_locn
)
self
.
assertEqual
(
modules
[
0
]
.
location
,
course_locn
)
...
@@ -316,7 +334,7 @@ class TestMixedModuleStore(unittest.TestCase):
...
@@ -316,7 +334,7 @@ class TestMixedModuleStore(unittest.TestCase):
course_locn
=
self
.
course_locations
[
self
.
MONGO_COURSEID
]
course_locn
=
self
.
course_locations
[
self
.
MONGO_COURSEID
]
with
check_mongo_calls
(
mongo_store
,
max_find
,
max_send
):
with
check_mongo_calls
(
mongo_store
,
max_find
,
max_send
):
# NOTE: use get_course if you just want the course. get_items is expensive
# NOTE: use get_course if you just want the course. get_items is expensive
modules
=
self
.
store
.
get_items
(
course_locn
.
course_key
,
category
=
'problem'
)
modules
=
self
.
store
.
get_items
(
course_locn
.
course_key
,
qualifiers
=
{
'category'
:
'problem'
}
)
self
.
assertEqual
(
len
(
modules
),
6
)
self
.
assertEqual
(
len
(
modules
),
6
)
# verify that an error is raised when the revision is not valid
# verify that an error is raised when the revision is not valid
...
@@ -368,7 +386,7 @@ class TestMixedModuleStore(unittest.TestCase):
...
@@ -368,7 +386,7 @@ class TestMixedModuleStore(unittest.TestCase):
# Create dummy direct only xblocks
# Create dummy direct only xblocks
chapter
=
self
.
store
.
create_item
(
chapter
=
self
.
store
.
create_item
(
self
.
user_id
,
self
.
user_id
,
test_course
.
id
.
version_agnostic
()
,
test_course
.
id
,
'chapter'
,
'chapter'
,
block_id
=
'vertical_container'
block_id
=
'vertical_container'
)
)
...
@@ -389,7 +407,7 @@ class TestMixedModuleStore(unittest.TestCase):
...
@@ -389,7 +407,7 @@ class TestMixedModuleStore(unittest.TestCase):
# Create a dummy component to test against
# Create a dummy component to test against
xblock
=
self
.
store
.
create_item
(
xblock
=
self
.
store
.
create_item
(
self
.
user_id
,
self
.
user_id
,
test_course
.
id
.
version_agnostic
()
,
test_course
.
id
,
'vertical'
,
'vertical'
,
block_id
=
'test_vertical'
block_id
=
'test_vertical'
)
)
...
@@ -504,7 +522,7 @@ class TestMixedModuleStore(unittest.TestCase):
...
@@ -504,7 +522,7 @@ class TestMixedModuleStore(unittest.TestCase):
revision
=
ModuleStoreEnum
.
RevisionOption
.
draft_preferred
revision
=
ModuleStoreEnum
.
RevisionOption
.
draft_preferred
)
)
self
.
store
.
publish
(
private_vert
.
location
.
version_agnostic
()
,
self
.
user_id
)
self
.
store
.
publish
(
private_vert
.
location
,
self
.
user_id
)
private_leaf
.
display_name
=
'change me'
private_leaf
.
display_name
=
'change me'
private_leaf
=
self
.
store
.
update_item
(
private_leaf
,
self
.
user_id
)
private_leaf
=
self
.
store
.
update_item
(
private_leaf
,
self
.
user_id
)
mongo_store
=
self
.
store
.
_get_modulestore_for_courseid
(
self
.
_course_key_from_string
(
self
.
MONGO_COURSEID
))
mongo_store
=
self
.
store
.
_get_modulestore_for_courseid
(
self
.
_course_key_from_string
(
self
.
MONGO_COURSEID
))
...
@@ -512,7 +530,7 @@ class TestMixedModuleStore(unittest.TestCase):
...
@@ -512,7 +530,7 @@ class TestMixedModuleStore(unittest.TestCase):
with
check_mongo_calls
(
mongo_store
,
max_find
,
max_send
):
with
check_mongo_calls
(
mongo_store
,
max_find
,
max_send
):
self
.
store
.
delete_item
(
private_leaf
.
location
,
self
.
user_id
)
self
.
store
.
delete_item
(
private_leaf
.
location
,
self
.
user_id
)
@ddt.data
((
'draft'
,
3
,
0
),
(
'split'
,
6
,
0
))
@ddt.data
((
'draft'
,
2
,
0
),
(
'split'
,
3
,
0
))
@ddt.unpack
@ddt.unpack
def
test_get_courses
(
self
,
default_ms
,
max_find
,
max_send
):
def
test_get_courses
(
self
,
default_ms
,
max_find
,
max_send
):
self
.
initdb
(
default_ms
)
self
.
initdb
(
default_ms
)
...
@@ -520,16 +538,19 @@ class TestMixedModuleStore(unittest.TestCase):
...
@@ -520,16 +538,19 @@ class TestMixedModuleStore(unittest.TestCase):
mongo_store
=
self
.
store
.
_get_modulestore_for_courseid
(
self
.
_course_key_from_string
(
self
.
MONGO_COURSEID
))
mongo_store
=
self
.
store
.
_get_modulestore_for_courseid
(
self
.
_course_key_from_string
(
self
.
MONGO_COURSEID
))
with
check_mongo_calls
(
mongo_store
,
max_find
,
max_send
):
with
check_mongo_calls
(
mongo_store
,
max_find
,
max_send
):
courses
=
self
.
store
.
get_courses
()
courses
=
self
.
store
.
get_courses
()
course_ids
=
[
course_ids
=
[
course
.
location
for
course
in
courses
]
course
.
location
.
version_agnostic
()
if
hasattr
(
course
.
location
,
'version_agnostic'
)
else
course
.
location
for
course
in
courses
]
self
.
assertEqual
(
len
(
courses
),
3
,
"Not 3 courses: {}"
.
format
(
course_ids
))
self
.
assertEqual
(
len
(
courses
),
3
,
"Not 3 courses: {}"
.
format
(
course_ids
))
self
.
assertIn
(
self
.
course_locations
[
self
.
MONGO_COURSEID
],
course_ids
)
self
.
assertIn
(
self
.
course_locations
[
self
.
MONGO_COURSEID
],
course_ids
)
self
.
assertIn
(
self
.
course_locations
[
self
.
XML_COURSEID1
],
course_ids
)
self
.
assertIn
(
self
.
course_locations
[
self
.
XML_COURSEID1
],
course_ids
)
self
.
assertIn
(
self
.
course_locations
[
self
.
XML_COURSEID2
],
course_ids
)
self
.
assertIn
(
self
.
course_locations
[
self
.
XML_COURSEID2
],
course_ids
)
with
self
.
store
.
branch_setting
(
ModuleStoreEnum
.
Branch
.
draft_preferred
):
draft_courses
=
self
.
store
.
get_courses
(
remove_branch
=
True
)
with
self
.
store
.
branch_setting
(
ModuleStoreEnum
.
Branch
.
published_only
):
published_courses
=
self
.
store
.
get_courses
(
remove_branch
=
True
)
self
.
assertEquals
([
c
.
id
for
c
in
draft_courses
],
[
c
.
id
for
c
in
published_courses
])
def
test_xml_get_courses
(
self
):
def
test_xml_get_courses
(
self
):
"""
"""
Test that the xml modulestore only loaded the courses from the maps.
Test that the xml modulestore only loaded the courses from the maps.
...
@@ -604,7 +625,7 @@ class TestMixedModuleStore(unittest.TestCase):
...
@@ -604,7 +625,7 @@ class TestMixedModuleStore(unittest.TestCase):
self
.
_create_block_hierarchy
()
self
.
_create_block_hierarchy
()
# publish the course
# publish the course
self
.
course
=
self
.
store
.
publish
(
self
.
course
.
location
.
version_agnostic
()
,
self
.
user_id
)
self
.
course
=
self
.
store
.
publish
(
self
.
course
.
location
,
self
.
user_id
)
# make drafts of verticals
# make drafts of verticals
self
.
store
.
convert_to_draft
(
self
.
vertical_x1a
,
self
.
user_id
)
self
.
store
.
convert_to_draft
(
self
.
vertical_x1a
,
self
.
user_id
)
...
@@ -630,7 +651,7 @@ class TestMixedModuleStore(unittest.TestCase):
...
@@ -630,7 +651,7 @@ class TestMixedModuleStore(unittest.TestCase):
])
])
# publish the course again
# publish the course again
self
.
store
.
publish
(
self
.
course
.
location
.
version_agnostic
()
,
self
.
user_id
)
self
.
store
.
publish
(
self
.
course
.
location
,
self
.
user_id
)
self
.
verify_get_parent_locations_results
([
self
.
verify_get_parent_locations_results
([
(
child_to_move_location
,
new_parent_location
,
None
),
(
child_to_move_location
,
new_parent_location
,
None
),
(
child_to_move_location
,
new_parent_location
,
ModuleStoreEnum
.
RevisionOption
.
draft_preferred
),
(
child_to_move_location
,
new_parent_location
,
ModuleStoreEnum
.
RevisionOption
.
draft_preferred
),
...
@@ -870,7 +891,7 @@ class TestMixedModuleStore(unittest.TestCase):
...
@@ -870,7 +891,7 @@ class TestMixedModuleStore(unittest.TestCase):
self
.
initdb
(
default_ms
)
self
.
initdb
(
default_ms
)
block
=
self
.
store
.
create_item
(
block
=
self
.
store
.
create_item
(
self
.
user_id
,
self
.
user_id
,
self
.
course
.
location
.
version_agnostic
()
.
course_key
,
self
.
course
.
location
.
course_key
,
'problem'
'problem'
)
)
self
.
assertEqual
(
self
.
user_id
,
block
.
edited_by
)
self
.
assertEqual
(
self
.
user_id
,
block
.
edited_by
)
...
@@ -881,7 +902,7 @@ class TestMixedModuleStore(unittest.TestCase):
...
@@ -881,7 +902,7 @@ class TestMixedModuleStore(unittest.TestCase):
self
.
initdb
(
default_ms
)
self
.
initdb
(
default_ms
)
block
=
self
.
store
.
create_item
(
block
=
self
.
store
.
create_item
(
self
.
user_id
,
self
.
user_id
,
self
.
course
.
location
.
version_agnostic
()
.
course_key
,
self
.
course
.
location
.
course_key
,
'problem'
'problem'
)
)
self
.
assertEqual
(
self
.
user_id
,
block
.
subtree_edited_by
)
self
.
assertEqual
(
self
.
user_id
,
block
.
subtree_edited_by
)
...
@@ -926,7 +947,7 @@ class TestMixedModuleStore(unittest.TestCase):
...
@@ -926,7 +947,7 @@ class TestMixedModuleStore(unittest.TestCase):
self
.
_create_block_hierarchy
()
self
.
_create_block_hierarchy
()
# publish
# publish
self
.
store
.
publish
(
self
.
course
.
location
.
version_agnostic
()
,
self
.
user_id
)
self
.
store
.
publish
(
self
.
course
.
location
,
self
.
user_id
)
published_xblock
=
self
.
store
.
get_item
(
published_xblock
=
self
.
store
.
get_item
(
self
.
vertical_x1a
,
self
.
vertical_x1a
,
revision
=
ModuleStoreEnum
.
RevisionOption
.
published_only
revision
=
ModuleStoreEnum
.
RevisionOption
.
published_only
...
@@ -962,7 +983,7 @@ class TestMixedModuleStore(unittest.TestCase):
...
@@ -962,7 +983,7 @@ class TestMixedModuleStore(unittest.TestCase):
# start off as Private
# start off as Private
item
=
self
.
store
.
create_child
(
self
.
user_id
,
self
.
writable_chapter_location
,
'problem'
,
'test_compute_publish_state'
)
item
=
self
.
store
.
create_child
(
self
.
user_id
,
self
.
writable_chapter_location
,
'problem'
,
'test_compute_publish_state'
)
item_location
=
item
.
location
.
version_agnostic
()
item_location
=
item
.
location
mongo_store
=
self
.
store
.
_get_modulestore_for_courseid
(
self
.
_course_key_from_string
(
self
.
MONGO_COURSEID
))
mongo_store
=
self
.
store
.
_get_modulestore_for_courseid
(
self
.
_course_key_from_string
(
self
.
MONGO_COURSEID
))
with
check_mongo_calls
(
mongo_store
,
max_find
,
max_send
):
with
check_mongo_calls
(
mongo_store
,
max_find
,
max_send
):
self
.
assertEquals
(
self
.
store
.
compute_publish_state
(
item
),
PublishState
.
private
)
self
.
assertEquals
(
self
.
store
.
compute_publish_state
(
item
),
PublishState
.
private
)
...
@@ -1012,13 +1033,13 @@ class TestMixedModuleStore(unittest.TestCase):
...
@@ -1012,13 +1033,13 @@ class TestMixedModuleStore(unittest.TestCase):
test_course
=
self
.
store
.
create_course
(
'testx'
,
'GreekHero'
,
'test_run'
,
self
.
user_id
)
test_course
=
self
.
store
.
create_course
(
'testx'
,
'GreekHero'
,
'test_run'
,
self
.
user_id
)
self
.
assertEqual
(
self
.
store
.
compute_publish_state
(
test_course
),
PublishState
.
public
)
self
.
assertEqual
(
self
.
store
.
compute_publish_state
(
test_course
),
PublishState
.
public
)
test_course_key
=
test_course
.
id
.
version_agnostic
()
test_course_key
=
test_course
.
id
# test create_item of direct-only category to make sure we are autopublishing
# test create_item of direct-only category to make sure we are autopublishing
chapter
=
self
.
store
.
create_item
(
self
.
user_id
,
test_course_key
,
'chapter'
,
'Overview'
)
chapter
=
self
.
store
.
create_item
(
self
.
user_id
,
test_course_key
,
'chapter'
,
'Overview'
)
self
.
assertEqual
(
self
.
store
.
compute_publish_state
(
chapter
),
PublishState
.
public
)
self
.
assertEqual
(
self
.
store
.
compute_publish_state
(
chapter
),
PublishState
.
public
)
chapter_location
=
chapter
.
location
.
version_agnostic
()
chapter_location
=
chapter
.
location
# test create_child of direct-only category to make sure we are autopublishing
# test create_child of direct-only category to make sure we are autopublishing
sequential
=
self
.
store
.
create_child
(
self
.
user_id
,
chapter_location
,
'sequential'
,
'Sequence'
)
sequential
=
self
.
store
.
create_child
(
self
.
user_id
,
chapter_location
,
'sequential'
,
'Sequence'
)
...
@@ -1189,6 +1210,59 @@ class TestMixedModuleStore(unittest.TestCase):
...
@@ -1189,6 +1210,59 @@ class TestMixedModuleStore(unittest.TestCase):
# there should be no published problems with the old name
# there should be no published problems with the old name
assertNumProblems
(
problem_original_name
,
0
)
assertNumProblems
(
problem_original_name
,
0
)
def
verify_default_store
(
self
,
store_type
):
# verify default_store property
self
.
assertEquals
(
self
.
store
.
default_modulestore
.
get_modulestore_type
(),
store_type
)
# verify internal helper method
store
=
self
.
store
.
_get_modulestore_for_courseid
()
self
.
assertEquals
(
store
.
get_modulestore_type
(),
store_type
)
# verify store used for creating a course
try
:
course
=
self
.
store
.
create_course
(
"org"
,
"course{}"
.
format
(
uuid4
()
.
hex
[:
3
]),
"run"
,
self
.
user_id
)
self
.
assertEquals
(
course
.
system
.
modulestore
.
get_modulestore_type
(),
store_type
)
except
NotImplementedError
:
self
.
assertEquals
(
store_type
,
ModuleStoreEnum
.
Type
.
xml
)
@ddt.data
(
ModuleStoreEnum
.
Type
.
mongo
,
ModuleStoreEnum
.
Type
.
split
,
ModuleStoreEnum
.
Type
.
xml
)
def
test_default_store
(
self
,
default_ms
):
"""
Test the default store context manager
"""
# initialize the mixed modulestore
self
.
_initialize_mixed
()
with
self
.
store
.
default_store
(
default_ms
):
self
.
verify_default_store
(
default_ms
)
def
test_default_store_nested
(
self
):
"""
Test the default store context manager, nested within one another
"""
# initialize the mixed modulestore
self
.
_initialize_mixed
()
with
self
.
store
.
default_store
(
ModuleStoreEnum
.
Type
.
mongo
):
self
.
verify_default_store
(
ModuleStoreEnum
.
Type
.
mongo
)
with
self
.
store
.
default_store
(
ModuleStoreEnum
.
Type
.
split
):
self
.
verify_default_store
(
ModuleStoreEnum
.
Type
.
split
)
with
self
.
store
.
default_store
(
ModuleStoreEnum
.
Type
.
xml
):
self
.
verify_default_store
(
ModuleStoreEnum
.
Type
.
xml
)
self
.
verify_default_store
(
ModuleStoreEnum
.
Type
.
split
)
self
.
verify_default_store
(
ModuleStoreEnum
.
Type
.
mongo
)
def
test_default_store_fake
(
self
):
"""
Test the default store context manager, asking for a fake store
"""
# initialize the mixed modulestore
self
.
_initialize_mixed
()
fake_store
=
"fake"
with
self
.
assertRaisesRegexp
(
Exception
,
"Cannot find store of type {}"
.
format
(
fake_store
)):
with
self
.
store
.
default_store
(
fake_store
):
pass
# pragma: no cover
#=============================================================================================================
#=============================================================================================================
# General utils for not using django settings
# General utils for not using django settings
...
...
common/lib/xmodule/xmodule/modulestore/tests/test_modulestore_settings.py
View file @
9efe5d92
...
@@ -45,7 +45,6 @@ class ModuleStoreSettingsMigration(TestCase):
...
@@ -45,7 +45,6 @@ class ModuleStoreSettingsMigration(TestCase):
"ENGINE"
:
"xmodule.modulestore.mixed.MixedModuleStore"
,
"ENGINE"
:
"xmodule.modulestore.mixed.MixedModuleStore"
,
"OPTIONS"
:
{
"OPTIONS"
:
{
"mappings"
:
{},
"mappings"
:
{},
"reference_type"
:
"Location"
,
"stores"
:
{
"stores"
:
{
"an_old_mongo_store"
:
{
"an_old_mongo_store"
:
{
"DOC_STORE_CONFIG"
:
{},
"DOC_STORE_CONFIG"
:
{},
...
@@ -77,15 +76,47 @@ class ModuleStoreSettingsMigration(TestCase):
...
@@ -77,15 +76,47 @@ class ModuleStoreSettingsMigration(TestCase):
}
}
}
}
ALREADY_UPDATED_MIXED_CONFIG
=
{
'default'
:
{
'ENGINE'
:
'xmodule.modulestore.mixed.MixedModuleStore'
,
'OPTIONS'
:
{
'mappings'
:
{},
'stores'
:
[
{
'NAME'
:
'split'
,
'ENGINE'
:
'xmodule.modulestore.split_mongo.split_draft.DraftVersioningModuleStore'
,
'DOC_STORE_CONFIG'
:
{},
'OPTIONS'
:
{
'default_class'
:
'xmodule.hidden_module.HiddenDescriptor'
,
'fs_root'
:
"fs_root"
,
'render_template'
:
'edxmako.shortcuts.render_to_string'
,
}
},
{
'NAME'
:
'draft'
,
'ENGINE'
:
'xmodule.modulestore.mongo.draft.DraftModuleStore'
,
'DOC_STORE_CONFIG'
:
{},
'OPTIONS'
:
{
'default_class'
:
'xmodule.hidden_module.HiddenDescriptor'
,
'fs_root'
:
"fs_root"
,
'render_template'
:
'edxmako.shortcuts.render_to_string'
,
}
},
]
}
}
}
def
_get_mixed_stores
(
self
,
mixed_setting
):
def
_get_mixed_stores
(
self
,
mixed_setting
):
"""
"""
Helper for accessing stores in a configuration setting for the Mixed modulestore
Helper for accessing stores in a configuration setting for the Mixed modulestore
.
"""
"""
return
mixed_setting
[
"default"
][
"OPTIONS"
][
"stores"
]
return
mixed_setting
[
"default"
][
"OPTIONS"
][
"stores"
]
def
assertStoreValuesEqual
(
self
,
store_setting1
,
store_setting2
):
def
assertStoreValuesEqual
(
self
,
store_setting1
,
store_setting2
):
"""
"""
Tests whether the fields in the given store_settings are equal
Tests whether the fields in the given store_settings are equal
.
"""
"""
store_fields
=
[
"OPTIONS"
,
"DOC_STORE_CONFIG"
]
store_fields
=
[
"OPTIONS"
,
"DOC_STORE_CONFIG"
]
for
field
in
store_fields
:
for
field
in
store_fields
:
...
@@ -108,26 +139,56 @@ class ModuleStoreSettingsMigration(TestCase):
...
@@ -108,26 +139,56 @@ class ModuleStoreSettingsMigration(TestCase):
return
new_mixed_setting
,
new_stores
[
0
]
return
new_mixed_setting
,
new_stores
[
0
]
def
is_split_configured
(
self
,
mixed_setting
):
"""
Tests whether the split module store is configured in the given setting.
"""
stores
=
self
.
_get_mixed_stores
(
mixed_setting
)
split_settings
=
[
store
for
store
in
stores
if
store
[
'ENGINE'
]
.
endswith
(
'.DraftVersioningModuleStore'
)]
if
len
(
split_settings
):
# there should only be one setting for split
self
.
assertEquals
(
len
(
split_settings
),
1
)
# verify name
self
.
assertEquals
(
split_settings
[
0
][
'NAME'
],
'split'
)
# verify split config settings equal those of mongo
self
.
assertStoreValuesEqual
(
split_settings
[
0
],
next
((
store
for
store
in
stores
if
'DraftModuleStore'
in
store
[
'ENGINE'
]),
None
)
)
return
len
(
split_settings
)
>
0
def
test_convert_into_mixed
(
self
):
def
test_convert_into_mixed
(
self
):
old_setting
=
self
.
OLD_CONFIG
old_setting
=
self
.
OLD_CONFIG
_
,
new_default_store_setting
=
self
.
assertMigrated
(
old_setting
)
new_mixed_setting
,
new_default_store_setting
=
self
.
assertMigrated
(
old_setting
)
self
.
assertStoreValuesEqual
(
new_default_store_setting
,
old_setting
[
"default"
])
self
.
assertStoreValuesEqual
(
new_default_store_setting
,
old_setting
[
"default"
])
self
.
assertEqual
(
new_default_store_setting
[
"ENGINE"
],
old_setting
[
"default"
][
"ENGINE"
])
self
.
assertEqual
(
new_default_store_setting
[
"ENGINE"
],
old_setting
[
"default"
][
"ENGINE"
])
self
.
assertFalse
(
self
.
is_split_configured
(
new_mixed_setting
))
def
test_convert_from_old_mongo_to_draft_store
(
self
):
def
test_convert_from_old_mongo_to_draft_store
(
self
):
old_setting
=
self
.
OLD_CONFIG_WITH_DIRECT_MONGO
old_setting
=
self
.
OLD_CONFIG_WITH_DIRECT_MONGO
_
,
new_default_store_setting
=
self
.
assertMigrated
(
old_setting
)
new_mixed_setting
,
new_default_store_setting
=
self
.
assertMigrated
(
old_setting
)
self
.
assertStoreValuesEqual
(
new_default_store_setting
,
old_setting
[
"default"
])
self
.
assertStoreValuesEqual
(
new_default_store_setting
,
old_setting
[
"default"
])
self
.
assertEqual
(
new_default_store_setting
[
"ENGINE"
],
"xmodule.modulestore.mongo.draft.DraftModuleStore"
)
self
.
assertEqual
(
new_default_store_setting
[
"ENGINE"
],
"xmodule.modulestore.mongo.draft.DraftModuleStore"
)
self
.
assertTrue
(
self
.
is_split_configured
(
new_mixed_setting
))
def
test_convert_from_dict_to_list
(
self
):
def
test_convert_from_dict_to_list
(
self
):
old_mixed_setting
=
self
.
OLD_MIXED_CONFIG_WITH_DICT
old_mixed_setting
=
self
.
OLD_MIXED_CONFIG_WITH_DICT
new_mixed_setting
,
new_default_store_setting
=
self
.
assertMigrated
(
old_mixed_setting
)
new_mixed_setting
,
new_default_store_setting
=
self
.
assertMigrated
(
old_mixed_setting
)
self
.
assertEqual
(
new_default_store_setting
[
"ENGINE"
],
"the_default_store"
)
self
.
assertEqual
(
new_default_store_setting
[
"ENGINE"
],
"the_default_store"
)
self
.
assertTrue
(
self
.
is_split_configured
(
new_mixed_setting
))
# compare each store configured in mixed
# exclude split when comparing old and new, since split was added as part of the migration
new_stores
=
[
store
for
store
in
self
.
_get_mixed_stores
(
new_mixed_setting
)
if
store
[
'NAME'
]
!=
'split'
]
old_stores
=
self
.
_get_mixed_stores
(
self
.
OLD_MIXED_CONFIG_WITH_DICT
)
old_stores
=
self
.
_get_mixed_stores
(
self
.
OLD_MIXED_CONFIG_WITH_DICT
)
new_stores
=
self
.
_get_mixed_stores
(
new_mixed_setting
)
# compare each store configured in mixed
self
.
assertEqual
(
len
(
new_stores
),
len
(
old_stores
))
self
.
assertEqual
(
len
(
new_stores
),
len
(
old_stores
))
for
new_store_setting
in
self
.
_get_mixed_stores
(
new_mixed_setting
):
for
new_store
in
new_stores
:
self
.
assertStoreValuesEqual
(
new_store_setting
,
old_stores
[
new_store_setting
[
'NAME'
]])
self
.
assertStoreValuesEqual
(
new_store
,
old_stores
[
new_store
[
'NAME'
]])
def
test_no_conversion
(
self
):
# make sure there is no migration done on an already updated config
old_mixed_setting
=
self
.
ALREADY_UPDATED_MIXED_CONFIG
new_mixed_setting
,
new_default_store_setting
=
self
.
assertMigrated
(
old_mixed_setting
)
self
.
assertTrue
(
self
.
is_split_configured
(
new_mixed_setting
))
self
.
assertEquals
(
old_mixed_setting
,
new_mixed_setting
)
common/lib/xmodule/xmodule/modulestore/tests/test_split_migrator.py
View file @
9efe5d92
...
@@ -151,7 +151,7 @@ class TestMigration(SplitWMongoCourseBoostrapper):
...
@@ -151,7 +151,7 @@ class TestMigration(SplitWMongoCourseBoostrapper):
# grab the detached items to compare they should be in both published and draft
# grab the detached items to compare they should be in both published and draft
for
category
in
[
'conditional'
,
'about'
,
'course_info'
,
'static_tab'
]:
for
category
in
[
'conditional'
,
'about'
,
'course_info'
,
'static_tab'
]:
for
conditional
in
presplit
.
get_items
(
self
.
old_course_key
,
category
=
category
):
for
conditional
in
presplit
.
get_items
(
self
.
old_course_key
,
qualifiers
=
{
'category'
:
category
}
):
locator
=
new_course_key
.
make_usage_key
(
category
,
conditional
.
location
.
block_id
)
locator
=
new_course_key
.
make_usage_key
(
category
,
conditional
.
location
.
block_id
)
self
.
compare_dags
(
presplit
,
conditional
,
self
.
split_mongo
.
get_item
(
locator
),
published
)
self
.
compare_dags
(
presplit
,
conditional
,
self
.
split_mongo
.
get_item
(
locator
),
published
)
...
...
common/lib/xmodule/xmodule/modulestore/tests/test_split_modulestore.py
View file @
9efe5d92
...
@@ -895,18 +895,18 @@ class SplitModuleItemTests(SplitModuleTest):
...
@@ -895,18 +895,18 @@ class SplitModuleItemTests(SplitModuleTest):
self
.
assertEqual
(
len
(
matches
),
6
)
self
.
assertEqual
(
len
(
matches
),
6
)
matches
=
modulestore
()
.
get_items
(
locator
)
matches
=
modulestore
()
.
get_items
(
locator
)
self
.
assertEqual
(
len
(
matches
),
6
)
self
.
assertEqual
(
len
(
matches
),
6
)
matches
=
modulestore
()
.
get_items
(
locator
,
category
=
'chapter'
)
matches
=
modulestore
()
.
get_items
(
locator
,
qualifiers
=
{
'category'
:
'chapter'
}
)
self
.
assertEqual
(
len
(
matches
),
3
)
self
.
assertEqual
(
len
(
matches
),
3
)
matches
=
modulestore
()
.
get_items
(
locator
,
category
=
'garbage'
)
matches
=
modulestore
()
.
get_items
(
locator
,
qualifiers
=
{
'category'
:
'garbage'
}
)
self
.
assertEqual
(
len
(
matches
),
0
)
self
.
assertEqual
(
len
(
matches
),
0
)
matches
=
modulestore
()
.
get_items
(
matches
=
modulestore
()
.
get_items
(
locator
,
locator
,
category
=
'chapter'
,
qualifiers
=
{
'category'
:
'chapter'
}
,
settings
=
{
'display_name'
:
re
.
compile
(
r'Hera'
)},
settings
=
{
'display_name'
:
re
.
compile
(
r'Hera'
)},
)
)
self
.
assertEqual
(
len
(
matches
),
2
)
self
.
assertEqual
(
len
(
matches
),
2
)
matches
=
modulestore
()
.
get_items
(
locator
,
children
=
'chapter2'
)
matches
=
modulestore
()
.
get_items
(
locator
,
qualifiers
=
{
'children'
:
'chapter2'
}
)
self
.
assertEqual
(
len
(
matches
),
1
)
self
.
assertEqual
(
len
(
matches
),
1
)
self
.
assertEqual
(
matches
[
0
]
.
location
.
block_id
,
'head12345'
)
self
.
assertEqual
(
matches
[
0
]
.
location
.
block_id
,
'head12345'
)
...
@@ -1324,7 +1324,7 @@ class TestItemCrud(SplitModuleTest):
...
@@ -1324,7 +1324,7 @@ class TestItemCrud(SplitModuleTest):
reusable_location
=
course
.
id
.
version_agnostic
()
.
for_branch
(
BRANCH_NAME_DRAFT
)
reusable_location
=
course
.
id
.
version_agnostic
()
.
for_branch
(
BRANCH_NAME_DRAFT
)
# delete a leaf
# delete a leaf
problems
=
modulestore
()
.
get_items
(
reusable_location
,
category
=
'problem'
)
problems
=
modulestore
()
.
get_items
(
reusable_location
,
qualifiers
=
{
'category'
:
'problem'
}
)
locn_to_del
=
problems
[
0
]
.
location
locn_to_del
=
problems
[
0
]
.
location
new_course_loc
=
modulestore
()
.
delete_item
(
locn_to_del
,
self
.
user_id
)
new_course_loc
=
modulestore
()
.
delete_item
(
locn_to_del
,
self
.
user_id
)
deleted
=
locn_to_del
.
version_agnostic
()
deleted
=
locn_to_del
.
version_agnostic
()
...
@@ -1336,7 +1336,7 @@ class TestItemCrud(SplitModuleTest):
...
@@ -1336,7 +1336,7 @@ class TestItemCrud(SplitModuleTest):
self
.
assertNotEqual
(
new_course_loc
.
version_guid
,
course
.
location
.
version_guid
)
self
.
assertNotEqual
(
new_course_loc
.
version_guid
,
course
.
location
.
version_guid
)
# delete a subtree
# delete a subtree
nodes
=
modulestore
()
.
get_items
(
reusable_location
,
category
=
'chapter'
)
nodes
=
modulestore
()
.
get_items
(
reusable_location
,
qualifiers
=
{
'category'
:
'chapter'
}
)
new_course_loc
=
modulestore
()
.
delete_item
(
nodes
[
0
]
.
location
,
self
.
user_id
)
new_course_loc
=
modulestore
()
.
delete_item
(
nodes
[
0
]
.
location
,
self
.
user_id
)
# check subtree
# check subtree
...
...
common/lib/xmodule/xmodule/modulestore/xml.py
View file @
9efe5d92
...
@@ -24,6 +24,7 @@ from xmodule.modulestore import ModuleStoreEnum, ModuleStoreReadBase
...
@@ -24,6 +24,7 @@ from xmodule.modulestore import ModuleStoreEnum, ModuleStoreReadBase
from
xmodule.tabs
import
CourseTabList
from
xmodule.tabs
import
CourseTabList
from
opaque_keys.edx.keys
import
UsageKey
from
opaque_keys.edx.keys
import
UsageKey
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
,
Location
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
,
Location
from
opaque_keys.edx.locator
import
CourseLocator
from
xblock.field_data
import
DictFieldData
from
xblock.field_data
import
DictFieldData
from
xblock.runtime
import
DictKeyValueStore
,
IdGenerator
from
xblock.runtime
import
DictKeyValueStore
,
IdGenerator
...
@@ -403,7 +404,6 @@ class XMLModuleStore(ModuleStoreReadBase):
...
@@ -403,7 +404,6 @@ class XMLModuleStore(ModuleStoreReadBase):
self
.
default_class
=
class_
self
.
default_class
=
class_
self
.
parent_trackers
=
defaultdict
(
ParentTracker
)
self
.
parent_trackers
=
defaultdict
(
ParentTracker
)
self
.
reference_type
=
Location
# All field data will be stored in an inheriting field data.
# All field data will be stored in an inheriting field data.
self
.
field_data
=
inheriting_field_data
(
kvs
=
DictKeyValueStore
())
self
.
field_data
=
inheriting_field_data
(
kvs
=
DictKeyValueStore
())
...
@@ -700,7 +700,7 @@ class XMLModuleStore(ModuleStoreReadBase):
...
@@ -700,7 +700,7 @@ class XMLModuleStore(ModuleStoreReadBase):
"""
"""
return
usage_key
in
self
.
modules
[
usage_key
.
course_key
]
return
usage_key
in
self
.
modules
[
usage_key
.
course_key
]
def
get_item
(
self
,
usage_key
,
depth
=
0
):
def
get_item
(
self
,
usage_key
,
depth
=
0
,
**
kwargs
):
"""
"""
Returns an XBlock instance for the item for this UsageKey.
Returns an XBlock instance for the item for this UsageKey.
...
@@ -717,7 +717,7 @@ class XMLModuleStore(ModuleStoreReadBase):
...
@@ -717,7 +717,7 @@ class XMLModuleStore(ModuleStoreReadBase):
except
KeyError
:
except
KeyError
:
raise
ItemNotFoundError
(
usage_key
)
raise
ItemNotFoundError
(
usage_key
)
def
get_items
(
self
,
course_id
,
settings
=
None
,
content
=
None
,
revision
=
None
,
**
kwargs
):
def
get_items
(
self
,
course_id
,
settings
=
None
,
content
=
None
,
revision
=
None
,
qualifiers
=
None
,
**
kwargs
):
"""
"""
Returns:
Returns:
list of XModuleDescriptor instances for the matching items within the course with
list of XModuleDescriptor instances for the matching items within the course with
...
@@ -729,10 +729,10 @@ class XMLModuleStore(ModuleStoreReadBase):
...
@@ -729,10 +729,10 @@ class XMLModuleStore(ModuleStoreReadBase):
Args:
Args:
course_id (CourseKey): the course identifier
course_id (CourseKey): the course identifier
settings (dict): fields to look for which have settings scope. Follows same syntax
settings (dict): fields to look for which have settings scope. Follows same syntax
and rules as
kwarg
s below
and rules as
qualifier
s below
content (dict): fields to look for which have content scope. Follows same syntax and
content (dict): fields to look for which have content scope. Follows same syntax and
rules as
kwarg
s below.
rules as
qualifier
s below.
kwargs (key=value
): what to look for within the course.
qualifiers (dict
): what to look for within the course.
Common qualifiers are ``category`` or any field name. if the target field is a list,
Common qualifiers are ``category`` or any field name. if the target field is a list,
then it searches for the given value in the list not list equivalence.
then it searches for the given value in the list not list equivalence.
Substring matching pass a regex object.
Substring matching pass a regex object.
...
@@ -747,8 +747,9 @@ class XMLModuleStore(ModuleStoreReadBase):
...
@@ -747,8 +747,9 @@ class XMLModuleStore(ModuleStoreReadBase):
items
=
[]
items
=
[]
category
=
kwargs
.
pop
(
'category'
,
None
)
qualifiers
=
qualifiers
.
copy
()
if
qualifiers
else
{}
# copy the qualifiers (destructively manipulated here)
name
=
kwargs
.
pop
(
'name'
,
None
)
category
=
qualifiers
.
pop
(
'category'
,
None
)
name
=
qualifiers
.
pop
(
'name'
,
None
)
def
_block_matches_all
(
mod_loc
,
module
):
def
_block_matches_all
(
mod_loc
,
module
):
if
category
and
mod_loc
.
category
!=
category
:
if
category
and
mod_loc
.
category
!=
category
:
...
@@ -757,7 +758,7 @@ class XMLModuleStore(ModuleStoreReadBase):
...
@@ -757,7 +758,7 @@ class XMLModuleStore(ModuleStoreReadBase):
return
False
return
False
return
all
(
return
all
(
self
.
_block_matches
(
module
,
fields
or
{})
self
.
_block_matches
(
module
,
fields
or
{})
for
fields
in
[
settings
,
content
,
kwarg
s
]
for
fields
in
[
settings
,
content
,
qualifier
s
]
)
)
for
mod_loc
,
module
in
self
.
modules
[
course_id
]
.
iteritems
():
for
mod_loc
,
module
in
self
.
modules
[
course_id
]
.
iteritems
():
...
@@ -766,7 +767,16 @@ class XMLModuleStore(ModuleStoreReadBase):
...
@@ -766,7 +767,16 @@ class XMLModuleStore(ModuleStoreReadBase):
return
items
return
items
def
get_courses
(
self
,
depth
=
0
):
def
make_course_key
(
self
,
org
,
course
,
run
):
"""
Return a valid :class:`~opaque_keys.edx.keys.CourseKey` for this modulestore
that matches the supplied `org`, `course`, and `run`.
This key may represent a course that doesn't exist in this modulestore.
"""
return
CourseLocator
(
org
,
course
,
run
,
deprecated
=
True
)
def
get_courses
(
self
,
depth
=
0
,
**
kwargs
):
"""
"""
Returns a list of course descriptors. If there were errors on loading,
Returns a list of course descriptors. If there were errors on loading,
some of these may be ErrorDescriptors instead.
some of these may be ErrorDescriptors instead.
...
@@ -780,7 +790,7 @@ class XMLModuleStore(ModuleStoreReadBase):
...
@@ -780,7 +790,7 @@ class XMLModuleStore(ModuleStoreReadBase):
"""
"""
return
dict
((
k
,
self
.
errored_courses
[
k
]
.
errors
)
for
k
in
self
.
errored_courses
)
return
dict
((
k
,
self
.
errored_courses
[
k
]
.
errors
)
for
k
in
self
.
errored_courses
)
def
get_orphans
(
self
,
course_key
):
def
get_orphans
(
self
,
course_key
,
**
kwargs
):
"""
"""
Get all of the xblocks in the given course which have no parents and are not of types which are
Get all of the xblocks in the given course which have no parents and are not of types which are
usually orphaned. NOTE: may include xblocks which still have references via xblocks which don't
usually orphaned. NOTE: may include xblocks which still have references via xblocks which don't
...
@@ -806,7 +816,7 @@ class XMLModuleStore(ModuleStoreReadBase):
...
@@ -806,7 +816,7 @@ class XMLModuleStore(ModuleStoreReadBase):
"""
"""
return
ModuleStoreEnum
.
Type
.
xml
return
ModuleStoreEnum
.
Type
.
xml
def
get_courses_for_wiki
(
self
,
wiki_slug
):
def
get_courses_for_wiki
(
self
,
wiki_slug
,
**
kwargs
):
"""
"""
Return the list of courses which use this wiki_slug
Return the list of courses which use this wiki_slug
:param wiki_slug: the course wiki root slug
:param wiki_slug: the course wiki root slug
...
...
common/lib/xmodule/xmodule/modulestore/xml_exporter.py
View file @
9efe5d92
...
@@ -106,7 +106,7 @@ def export_to_xml(modulestore, contentstore, course_key, root_dir, course_dir):
...
@@ -106,7 +106,7 @@ def export_to_xml(modulestore, contentstore, course_key, root_dir, course_dir):
# and index here since the XML modulestore cannot load draft modules
# and index here since the XML modulestore cannot load draft modules
draft_verticals
=
modulestore
.
get_items
(
draft_verticals
=
modulestore
.
get_items
(
course_key
,
course_key
,
category
=
'vertical'
,
qualifiers
=
{
'category'
:
'vertical'
}
,
revision
=
ModuleStoreEnum
.
RevisionOption
.
draft_only
revision
=
ModuleStoreEnum
.
RevisionOption
.
draft_only
)
)
if
len
(
draft_verticals
)
>
0
:
if
len
(
draft_verticals
)
>
0
:
...
@@ -144,7 +144,7 @@ def _export_field_content(xblock_item, item_dir):
...
@@ -144,7 +144,7 @@ def _export_field_content(xblock_item, item_dir):
def
export_extra_content
(
export_fs
,
modulestore
,
course_key
,
category_type
,
dirname
,
file_suffix
=
''
):
def
export_extra_content
(
export_fs
,
modulestore
,
course_key
,
category_type
,
dirname
,
file_suffix
=
''
):
items
=
modulestore
.
get_items
(
course_key
,
category
=
category_type
)
items
=
modulestore
.
get_items
(
course_key
,
qualifiers
=
{
'category'
:
category_type
}
)
if
len
(
items
)
>
0
:
if
len
(
items
)
>
0
:
item_dir
=
export_fs
.
makeopendir
(
dirname
)
item_dir
=
export_fs
.
makeopendir
(
dirname
)
...
...
common/lib/xmodule/xmodule/modulestore/xml_importer.py
View file @
9efe5d92
...
@@ -17,7 +17,7 @@ from .store_utilities import rewrite_nonportable_content_links
...
@@ -17,7 +17,7 @@ from .store_utilities import rewrite_nonportable_content_links
import
xblock
import
xblock
from
xmodule.tabs
import
CourseTabList
from
xmodule.tabs
import
CourseTabList
from
xmodule.modulestore.django
import
ASSET_IGNORE_REGEX
from
xmodule.modulestore.django
import
ASSET_IGNORE_REGEX
from
xmodule.modulestore.exceptions
import
InvalidLocation
Error
from
xmodule.modulestore.exceptions
import
DuplicateCourse
Error
from
xmodule.modulestore.mongo.base
import
MongoRevisionKey
from
xmodule.modulestore.mongo.base
import
MongoRevisionKey
from
xmodule.modulestore
import
ModuleStoreEnum
from
xmodule.modulestore
import
ModuleStoreEnum
...
@@ -174,8 +174,9 @@ def import_from_xml(
...
@@ -174,8 +174,9 @@ def import_from_xml(
if
create_new_course_if_not_present
and
not
store
.
has_course
(
dest_course_id
,
ignore_case
=
True
):
if
create_new_course_if_not_present
and
not
store
.
has_course
(
dest_course_id
,
ignore_case
=
True
):
try
:
try
:
store
.
create_course
(
dest_course_id
.
org
,
dest_course_id
.
course
,
dest_course_id
.
run
,
user_id
)
store
.
create_course
(
dest_course_id
.
org
,
dest_course_id
.
course
,
dest_course_id
.
run
,
user_id
)
except
InvalidLocation
Error
:
except
DuplicateCourse
Error
:
# course w/ same org and course exists
# course w/ same org and course exists
# The Mongo modulestore checks *with* the run in has_course, but not in create_course.
log
.
debug
(
log
.
debug
(
"Skipping import of course with id, {0},"
"Skipping import of course with id, {0},"
"since it collides with an existing one"
.
format
(
dest_course_id
)
"since it collides with an existing one"
.
format
(
dest_course_id
)
...
...
lms/djangoapps/courseware/tests/test_draft_modulestore.py
View file @
9efe5d92
...
@@ -13,7 +13,7 @@ class TestDraftModuleStore(TestCase):
...
@@ -13,7 +13,7 @@ class TestDraftModuleStore(TestCase):
store
=
modulestore
()
store
=
modulestore
()
# fix was to allow get_items() to take the course_id parameter
# fix was to allow get_items() to take the course_id parameter
store
.
get_items
(
SlashSeparatedCourseKey
(
'a'
,
'b'
,
'c'
),
category
=
'vertical'
)
store
.
get_items
(
SlashSeparatedCourseKey
(
'a'
,
'b'
,
'c'
),
qualifiers
=
{
'category'
:
'vertical'
}
)
# test success is just getting through the above statement.
# test success is just getting through the above statement.
# The bug was that 'course_id' argument was
# The bug was that 'course_id' argument was
...
...
lms/djangoapps/courseware/tests/tests.py
View file @
9efe5d92
...
@@ -163,7 +163,7 @@ class TestDraftModuleStore(ModuleStoreTestCase):
...
@@ -163,7 +163,7 @@ class TestDraftModuleStore(ModuleStoreTestCase):
store
=
modulestore
()
store
=
modulestore
()
# fix was to allow get_items() to take the course_id parameter
# fix was to allow get_items() to take the course_id parameter
store
.
get_items
(
SlashSeparatedCourseKey
(
'abc'
,
'def'
,
'ghi'
),
category
=
'vertical'
)
store
.
get_items
(
SlashSeparatedCourseKey
(
'abc'
,
'def'
,
'ghi'
),
qualifiers
=
{
'category'
:
'vertical'
}
)
# test success is just getting through the above statement.
# test success is just getting through the above statement.
# The bug was that 'course_id' argument was
# The bug was that 'course_id' argument was
...
...
lms/djangoapps/courseware/views.py
View file @
9efe5d92
...
@@ -463,7 +463,7 @@ def jump_to_id(request, course_id, module_id):
...
@@ -463,7 +463,7 @@ def jump_to_id(request, course_id, module_id):
passed in. This assumes that id is unique within the course_id namespace
passed in. This assumes that id is unique within the course_id namespace
"""
"""
course_key
=
SlashSeparatedCourseKey
.
from_deprecated_string
(
course_id
)
course_key
=
SlashSeparatedCourseKey
.
from_deprecated_string
(
course_id
)
items
=
modulestore
()
.
get_items
(
course_key
,
name
=
module_id
)
items
=
modulestore
()
.
get_items
(
course_key
,
qualifiers
=
{
'name'
:
module_id
}
)
if
len
(
items
)
==
0
:
if
len
(
items
)
==
0
:
raise
Http404
(
raise
Http404
(
...
@@ -937,7 +937,7 @@ def get_course_lti_endpoints(request, course_id):
...
@@ -937,7 +937,7 @@ def get_course_lti_endpoints(request, course_id):
anonymous_user
=
AnonymousUser
()
anonymous_user
=
AnonymousUser
()
anonymous_user
.
known
=
False
# make these "noauth" requests like module_render.handle_xblock_callback_noauth
anonymous_user
.
known
=
False
# make these "noauth" requests like module_render.handle_xblock_callback_noauth
lti_descriptors
=
modulestore
()
.
get_items
(
course
.
id
,
category
=
'lti'
)
lti_descriptors
=
modulestore
()
.
get_items
(
course
.
id
,
qualifiers
=
{
'category'
:
'lti'
}
)
lti_noauth_modules
=
[
lti_noauth_modules
=
[
get_module_for_descriptor
(
get_module_for_descriptor
(
...
...
lms/djangoapps/django_comment_client/utils.py
View file @
9efe5d92
...
@@ -57,7 +57,7 @@ def has_forum_access(uname, course_id, rolename):
...
@@ -57,7 +57,7 @@ def has_forum_access(uname, course_id, rolename):
def
_get_discussion_modules
(
course
):
def
_get_discussion_modules
(
course
):
all_modules
=
modulestore
()
.
get_items
(
course
.
id
,
category
=
'discussion'
)
all_modules
=
modulestore
()
.
get_items
(
course
.
id
,
qualifiers
=
{
'category'
:
'discussion'
}
)
def
has_required_keys
(
module
):
def
has_required_keys
(
module
):
for
key
in
(
'discussion_id'
,
'discussion_category'
,
'discussion_target'
):
for
key
in
(
'discussion_id'
,
'discussion_category'
,
'discussion_target'
):
...
...
lms/djangoapps/open_ended_grading/views.py
View file @
9efe5d92
...
@@ -92,7 +92,7 @@ def find_peer_grading_module(course):
...
@@ -92,7 +92,7 @@ def find_peer_grading_module(course):
problem_url
=
""
problem_url
=
""
# Get the peer grading modules currently in the course. Explicitly specify the course id to avoid issues with different runs.
# Get the peer grading modules currently in the course. Explicitly specify the course id to avoid issues with different runs.
items
=
modulestore
()
.
get_items
(
course
.
id
,
category
=
'peergrading'
)
items
=
modulestore
()
.
get_items
(
course
.
id
,
qualifiers
=
{
'category'
:
'peergrading'
}
)
# See if any of the modules are centralized modules (ie display info from multiple problems)
# See if any of the modules are centralized modules (ie display info from multiple problems)
items
=
[
i
for
i
in
items
if
not
getattr
(
i
,
"use_for_single_location"
,
True
)]
items
=
[
i
for
i
in
items
if
not
getattr
(
i
,
"use_for_single_location"
,
True
)]
# Loop through all potential peer grading modules, and find the first one that has a path to it.
# Loop through all potential peer grading modules, and find the first one that has a path to it.
...
...
lms/envs/bok_choy.auth.json
View file @
9efe5d92
...
@@ -51,7 +51,6 @@
...
@@ -51,7 +51,6 @@
"ENGINE"
:
"xmodule.modulestore.mixed.MixedModuleStore"
,
"ENGINE"
:
"xmodule.modulestore.mixed.MixedModuleStore"
,
"OPTIONS"
:
{
"OPTIONS"
:
{
"mappings"
:
{},
"mappings"
:
{},
"reference_type"
:
"Location"
,
"stores"
:
[
"stores"
:
[
{
{
"NAME"
:
"draft"
,
"NAME"
:
"draft"
,
...
...
lms/envs/common.py
View file @
9efe5d92
...
@@ -504,7 +504,6 @@ MODULESTORE = {
...
@@ -504,7 +504,6 @@ MODULESTORE = {
'ENGINE'
:
'xmodule.modulestore.mixed.MixedModuleStore'
,
'ENGINE'
:
'xmodule.modulestore.mixed.MixedModuleStore'
,
'OPTIONS'
:
{
'OPTIONS'
:
{
'mappings'
:
{},
'mappings'
:
{},
'reference_type'
:
'Location'
,
'stores'
:
[
'stores'
:
[
{
{
'NAME'
:
'draft'
,
'NAME'
:
'draft'
,
...
@@ -524,6 +523,16 @@ MODULESTORE = {
...
@@ -524,6 +523,16 @@ MODULESTORE = {
'default_class'
:
'xmodule.hidden_module.HiddenDescriptor'
,
'default_class'
:
'xmodule.hidden_module.HiddenDescriptor'
,
}
}
},
},
{
'NAME'
:
'split'
,
'ENGINE'
:
'xmodule.modulestore.split_mongo.split_draft.DraftVersioningModuleStore'
,
'DOC_STORE_CONFIG'
:
DOC_STORE_CONFIG
,
'OPTIONS'
:
{
'default_class'
:
'xmodule.hidden_module.HiddenDescriptor'
,
'fs_root'
:
DATA_DIR
,
'render_template'
:
'edxmako.shortcuts.render_to_string'
,
}
},
]
]
}
}
}
}
...
...
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