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
c32823df
Commit
c32823df
authored
Nov 25, 2015
by
Awais Jibran
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Render cms course listing using CourseSummary class.
parent
5df15fcf
Show whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
395 additions
and
50 deletions
+395
-50
cms/djangoapps/contentstore/tests/test_course_listing.py
+82
-27
cms/djangoapps/contentstore/views/course.py
+40
-11
common/lib/xmodule/xmodule/course_module.py
+53
-0
common/lib/xmodule/xmodule/modulestore/exceptions.py
+7
-0
common/lib/xmodule/xmodule/modulestore/mixed.py
+20
-0
common/lib/xmodule/xmodule/modulestore/mongo/base.py
+35
-0
common/lib/xmodule/xmodule/modulestore/split_mongo/caching_descriptor_system.py
+1
-1
common/lib/xmodule/xmodule/modulestore/split_mongo/mongo_connection.py
+20
-0
common/lib/xmodule/xmodule/modulestore/split_mongo/split.py
+85
-11
common/lib/xmodule/xmodule/modulestore/split_mongo/split_draft.py
+16
-0
common/lib/xmodule/xmodule/modulestore/tests/test_semantics.py
+30
-0
common/lib/xmodule/xmodule/modulestore/xml.py
+6
-0
No files found.
cms/djangoapps/contentstore/tests/test_course_listing.py
View file @
c32823df
...
@@ -9,8 +9,11 @@ from mock import patch, Mock
...
@@ -9,8 +9,11 @@ from mock import patch, Mock
import
ddt
import
ddt
from
django.test
import
RequestFactory
from
django.test
import
RequestFactory
from
xmodule.course_module
import
CourseSummary
from
contentstore.views.course
import
_accessible_courses_list
,
_accessible_courses_list_from_groups
,
AccessListFallback
from
contentstore.views.course
import
(
_accessible_courses_list
,
_accessible_courses_list_from_groups
,
AccessListFallback
,
get_courses_accessible_to_user
,
_staff_accessible_course_list
)
from
contentstore.utils
import
delete_course_and_groups
from
contentstore.utils
import
delete_course_and_groups
from
contentstore.tests.utils
import
AjaxEnabledTestClient
from
contentstore.tests.utils
import
AjaxEnabledTestClient
from
student.tests.factories
import
UserFactory
from
student.tests.factories
import
UserFactory
...
@@ -19,7 +22,6 @@ from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
...
@@ -19,7 +22,6 @@ from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from
xmodule.modulestore.tests.factories
import
CourseFactory
,
check_mongo_calls
from
xmodule.modulestore.tests.factories
import
CourseFactory
,
check_mongo_calls
from
xmodule.modulestore
import
ModuleStoreEnum
from
xmodule.modulestore
import
ModuleStoreEnum
from
opaque_keys.edx.locations
import
CourseLocator
from
opaque_keys.edx.locations
import
CourseLocator
from
xmodule.modulestore.django
import
modulestore
from
xmodule.error_module
import
ErrorDescriptor
from
xmodule.error_module
import
ErrorDescriptor
from
course_action_state.models
import
CourseRerunState
from
course_action_state.models
import
CourseRerunState
...
@@ -46,7 +48,7 @@ class TestCourseListing(ModuleStoreTestCase):
...
@@ -46,7 +48,7 @@ class TestCourseListing(ModuleStoreTestCase):
self
.
client
=
AjaxEnabledTestClient
()
self
.
client
=
AjaxEnabledTestClient
()
self
.
client
.
login
(
username
=
self
.
user
.
username
,
password
=
'test'
)
self
.
client
.
login
(
username
=
self
.
user
.
username
,
password
=
'test'
)
def
_create_course_with_access_groups
(
self
,
course_location
,
user
=
None
):
def
_create_course_with_access_groups
(
self
,
course_location
,
user
=
None
,
store
=
ModuleStoreEnum
.
Type
.
split
):
"""
"""
Create dummy course with 'CourseFactory' and role (instructor/staff) groups
Create dummy course with 'CourseFactory' and role (instructor/staff) groups
"""
"""
...
@@ -54,7 +56,7 @@ class TestCourseListing(ModuleStoreTestCase):
...
@@ -54,7 +56,7 @@ class TestCourseListing(ModuleStoreTestCase):
org
=
course_location
.
org
,
org
=
course_location
.
org
,
number
=
course_location
.
course
,
number
=
course_location
.
course
,
run
=
course_location
.
run
,
run
=
course_location
.
run
,
default_store
=
ModuleStoreEnum
.
Type
.
mongo
default_store
=
store
)
)
if
user
is
not
None
:
if
user
is
not
None
:
...
@@ -87,17 +89,23 @@ class TestCourseListing(ModuleStoreTestCase):
...
@@ -87,17 +89,23 @@ class TestCourseListing(ModuleStoreTestCase):
# check both course lists have same courses
# check both course lists have same courses
self
.
assertEqual
(
courses_list
,
courses_list_by_groups
)
self
.
assertEqual
(
courses_list
,
courses_list_by_groups
)
def
test_errored_course_global_staff
(
self
):
@ddt.data
(
(
ModuleStoreEnum
.
Type
.
split
,
'xmodule.modulestore.split_mongo.split_mongo_kvs.SplitMongoKVS'
),
(
ModuleStoreEnum
.
Type
.
mongo
,
'xmodule.modulestore.mongo.base.MongoKeyValueStore'
)
)
@ddt.unpack
def
test_errored_course_global_staff
(
self
,
store
,
path_to_patch
):
"""
"""
Test the course list for global staff when get_course returns an ErrorDescriptor
Test the course list for global staff when get_course returns an ErrorDescriptor
"""
"""
GlobalStaff
()
.
add_users
(
self
.
user
)
GlobalStaff
()
.
add_users
(
self
.
user
)
with
self
.
store
.
default_store
(
store
):
course_key
=
self
.
store
.
make_course_key
(
'Org1'
,
'Course1'
,
'Run1'
)
course_key
=
self
.
store
.
make_course_key
(
'Org1'
,
'Course1'
,
'Run1'
)
self
.
_create_course_with_access_groups
(
course_key
,
self
.
user
)
self
.
_create_course_with_access_groups
(
course_key
,
self
.
user
,
store
=
store
)
with
patch
(
'xmodule.modulestore.mongo.base.MongoKeyValueStore'
,
Mock
(
side_effect
=
Exception
)):
with
patch
(
path_to_patch
,
Mock
(
side_effect
=
Exception
)):
self
.
assertIsInstance
(
modulestore
()
.
get_course
(
course_key
),
ErrorDescriptor
)
self
.
assertIsInstance
(
self
.
store
.
get_course
(
course_key
),
ErrorDescriptor
)
# get courses through iterating all courses
# get courses through iterating all courses
courses_list
,
__
=
_accessible_courses_list
(
self
.
request
)
courses_list
,
__
=
_accessible_courses_list
(
self
.
request
)
...
@@ -107,18 +115,57 @@ class TestCourseListing(ModuleStoreTestCase):
...
@@ -107,18 +115,57 @@ class TestCourseListing(ModuleStoreTestCase):
courses_list_by_groups
,
__
=
_accessible_courses_list_from_groups
(
self
.
request
)
courses_list_by_groups
,
__
=
_accessible_courses_list_from_groups
(
self
.
request
)
self
.
assertEqual
(
courses_list_by_groups
,
[])
self
.
assertEqual
(
courses_list_by_groups
,
[])
def
test_errored_course_regular_access
(
self
):
@ddt.data
(
(
ModuleStoreEnum
.
Type
.
split
,
5
),
(
ModuleStoreEnum
.
Type
.
mongo
,
3
)
)
@ddt.unpack
def
test_staff_course_listing
(
self
,
default_store
,
mongo_calls
):
"""
Create courses and verify they take certain amount of mongo calls to call get_courses_accessible_to_user.
Also verify that fetch accessible courses list for staff user returns CourseSummary instances.
"""
# Assign & verify staff role to the user
GlobalStaff
()
.
add_users
(
self
.
user
)
self
.
assertTrue
(
GlobalStaff
()
.
has_user
(
self
.
user
))
with
self
.
store
.
default_store
(
default_store
):
# Create few courses
for
num
in
xrange
(
TOTAL_COURSES_COUNT
):
course_location
=
self
.
store
.
make_course_key
(
'Org'
,
'CreatedCourse'
+
str
(
num
),
'Run'
)
self
.
_create_course_with_access_groups
(
course_location
,
self
.
user
,
default_store
)
# Fetch accessible courses list & verify their count
courses_list_by_staff
,
__
=
get_courses_accessible_to_user
(
self
.
request
)
self
.
assertEqual
(
len
(
courses_list_by_staff
),
TOTAL_COURSES_COUNT
)
# Verify fetched accessible courses list is a list of CourseSummery instances
self
.
assertTrue
(
all
(
isinstance
(
course
,
CourseSummary
)
for
course
in
courses_list_by_staff
))
# Now count the db queries for staff
with
check_mongo_calls
(
mongo_calls
):
_staff_accessible_course_list
(
self
.
request
)
@ddt.data
(
(
ModuleStoreEnum
.
Type
.
split
,
'xmodule.modulestore.split_mongo.split_mongo_kvs.SplitMongoKVS'
),
(
ModuleStoreEnum
.
Type
.
mongo
,
'xmodule.modulestore.mongo.base.MongoKeyValueStore'
)
)
@ddt.unpack
def
test_errored_course_regular_access
(
self
,
store
,
path_to_patch
):
"""
"""
Test the course list for regular staff when get_course returns an ErrorDescriptor
Test the course list for regular staff when get_course returns an ErrorDescriptor
"""
"""
GlobalStaff
()
.
remove_users
(
self
.
user
)
GlobalStaff
()
.
remove_users
(
self
.
user
)
with
self
.
store
.
default_store
(
store
):
CourseStaffRole
(
self
.
store
.
make_course_key
(
'Non'
,
'Existent'
,
'Course'
))
.
add_users
(
self
.
user
)
CourseStaffRole
(
self
.
store
.
make_course_key
(
'Non'
,
'Existent'
,
'Course'
))
.
add_users
(
self
.
user
)
course_key
=
self
.
store
.
make_course_key
(
'Org1'
,
'Course1'
,
'Run1'
)
course_key
=
self
.
store
.
make_course_key
(
'Org1'
,
'Course1'
,
'Run1'
)
self
.
_create_course_with_access_groups
(
course_key
,
self
.
user
)
self
.
_create_course_with_access_groups
(
course_key
,
self
.
user
,
store
)
with
patch
(
'xmodule.modulestore.mongo.base.MongoKeyValueStore'
,
Mock
(
side_effect
=
Exception
)):
with
patch
(
path_to_patch
,
Mock
(
side_effect
=
Exception
)):
self
.
assertIsInstance
(
modulestore
()
.
get_course
(
course_key
),
ErrorDescriptor
)
self
.
assertIsInstance
(
self
.
store
.
get_course
(
course_key
),
ErrorDescriptor
)
# get courses through iterating all courses
# get courses through iterating all courses
courses_list
,
__
=
_accessible_courses_list
(
self
.
request
)
courses_list
,
__
=
_accessible_courses_list
(
self
.
request
)
...
@@ -129,12 +176,14 @@ class TestCourseListing(ModuleStoreTestCase):
...
@@ -129,12 +176,14 @@ class TestCourseListing(ModuleStoreTestCase):
self
.
assertEqual
(
courses_list_by_groups
,
[])
self
.
assertEqual
(
courses_list_by_groups
,
[])
self
.
assertEqual
(
courses_list
,
courses_list_by_groups
)
self
.
assertEqual
(
courses_list
,
courses_list_by_groups
)
def
test_get_course_list_with_invalid_course_location
(
self
):
@ddt.data
(
ModuleStoreEnum
.
Type
.
split
,
ModuleStoreEnum
.
Type
.
mongo
)
def
test_get_course_list_with_invalid_course_location
(
self
,
store
):
"""
"""
Test getting courses with invalid course location (course deleted from modulestore).
Test getting courses with invalid course location (course deleted from modulestore).
"""
"""
with
self
.
store
.
default_store
(
store
):
course_key
=
self
.
store
.
make_course_key
(
'Org'
,
'Course'
,
'Run'
)
course_key
=
self
.
store
.
make_course_key
(
'Org'
,
'Course'
,
'Run'
)
self
.
_create_course_with_access_groups
(
course_key
,
self
.
user
)
self
.
_create_course_with_access_groups
(
course_key
,
self
.
user
,
store
)
# get courses through iterating all courses
# get courses through iterating all courses
courses_list
,
__
=
_accessible_courses_list
(
self
.
request
)
courses_list
,
__
=
_accessible_courses_list
(
self
.
request
)
...
@@ -155,7 +204,12 @@ class TestCourseListing(ModuleStoreTestCase):
...
@@ -155,7 +204,12 @@ class TestCourseListing(ModuleStoreTestCase):
courses_list
,
__
=
_accessible_courses_list
(
self
.
request
)
courses_list
,
__
=
_accessible_courses_list
(
self
.
request
)
self
.
assertEqual
(
len
(
courses_list
),
0
)
self
.
assertEqual
(
len
(
courses_list
),
0
)
def
test_course_listing_performance
(
self
):
@ddt.data
(
(
ModuleStoreEnum
.
Type
.
split
,
150
,
505
),
(
ModuleStoreEnum
.
Type
.
mongo
,
USER_COURSES_COUNT
,
3
)
)
@ddt.unpack
def
test_course_listing_performance
(
self
,
store
,
courses_list_from_group_calls
,
courses_list_calls
):
"""
"""
Create large number of courses and give access of some of these courses to the user and
Create large number of courses and give access of some of these courses to the user and
compare the time to fetch accessible courses for the user through traversing all courses and
compare the time to fetch accessible courses for the user through traversing all courses and
...
@@ -165,15 +219,16 @@ class TestCourseListing(ModuleStoreTestCase):
...
@@ -165,15 +219,16 @@ class TestCourseListing(ModuleStoreTestCase):
user_course_ids
=
random
.
sample
(
range
(
TOTAL_COURSES_COUNT
),
USER_COURSES_COUNT
)
user_course_ids
=
random
.
sample
(
range
(
TOTAL_COURSES_COUNT
),
USER_COURSES_COUNT
)
# create courses and assign those to the user which have their number in user_course_ids
# create courses and assign those to the user which have their number in user_course_ids
with
self
.
store
.
default_store
(
store
):
for
number
in
range
(
TOTAL_COURSES_COUNT
):
for
number
in
range
(
TOTAL_COURSES_COUNT
):
org
=
'Org{0}'
.
format
(
number
)
org
=
'Org{0}'
.
format
(
number
)
course
=
'Course{0}'
.
format
(
number
)
course
=
'Course{0}'
.
format
(
number
)
run
=
'Run{0}'
.
format
(
number
)
run
=
'Run{0}'
.
format
(
number
)
course_location
=
self
.
store
.
make_course_key
(
org
,
course
,
run
)
course_location
=
self
.
store
.
make_course_key
(
org
,
course
,
run
)
if
number
in
user_course_ids
:
if
number
in
user_course_ids
:
self
.
_create_course_with_access_groups
(
course_location
,
self
.
user
)
self
.
_create_course_with_access_groups
(
course_location
,
self
.
user
,
store
=
store
)
else
:
else
:
self
.
_create_course_with_access_groups
(
course_location
)
self
.
_create_course_with_access_groups
(
course_location
,
store
=
store
)
# time the get courses by iterating through all courses
# time the get courses by iterating through all courses
with
Timer
()
as
iteration_over_courses_time_1
:
with
Timer
()
as
iteration_over_courses_time_1
:
...
@@ -201,29 +256,29 @@ class TestCourseListing(ModuleStoreTestCase):
...
@@ -201,29 +256,29 @@ class TestCourseListing(ModuleStoreTestCase):
self
.
assertGreaterEqual
(
iteration_over_courses_time_2
.
elapsed
,
iteration_over_groups_time_2
.
elapsed
)
self
.
assertGreaterEqual
(
iteration_over_courses_time_2
.
elapsed
,
iteration_over_groups_time_2
.
elapsed
)
# Now count the db queries
# Now count the db queries
with
check_mongo_calls
(
USER_COURSES_COUNT
):
with
check_mongo_calls
(
courses_list_from_group_calls
):
_accessible_courses_list_from_groups
(
self
.
request
)
_accessible_courses_list_from_groups
(
self
.
request
)
with
check_mongo_calls
(
courses_list_calls
):
_accessible_courses_list
(
self
.
request
)
# Calls:
# Calls:
# 1) query old mongo
# 1) query old mongo
# 2) get_more on old mongo
# 2) get_more on old mongo
# 3) query split (but no courses so no fetching of data)
# 3) query split (but no courses so no fetching of data)
with
check_mongo_calls
(
3
):
_accessible_courses_list
(
self
.
request
)
def
test_course_listing_errored_deleted_courses
(
self
):
@ddt.data
(
ModuleStoreEnum
.
Type
.
split
,
ModuleStoreEnum
.
Type
.
mongo
)
def
test_course_listing_errored_deleted_courses
(
self
,
store
):
"""
"""
Create good courses, courses that won't load, and deleted courses which still have
Create good courses, courses that won't load, and deleted courses which still have
roles. Test course listing.
roles. Test course listing.
"""
"""
store
=
modulestore
()
.
_get_modulestore_by_type
(
ModuleStoreEnum
.
Type
.
mongo
)
with
self
.
store
.
default_store
(
store
):
course_location
=
self
.
store
.
make_course_key
(
'testOrg'
,
'testCourse'
,
'RunBabyRun'
)
course_location
=
self
.
store
.
make_course_key
(
'testOrg'
,
'testCourse'
,
'RunBabyRun'
)
self
.
_create_course_with_access_groups
(
course_location
,
self
.
user
)
self
.
_create_course_with_access_groups
(
course_location
,
self
.
user
,
store
)
course_location
=
self
.
store
.
make_course_key
(
'testOrg'
,
'doomedCourse'
,
'RunBabyRun'
)
course_location
=
self
.
store
.
make_course_key
(
'testOrg'
,
'doomedCourse'
,
'RunBabyRun'
)
self
.
_create_course_with_access_groups
(
course_location
,
self
.
user
)
self
.
_create_course_with_access_groups
(
course_location
,
self
.
user
,
store
)
store
.
delete_course
(
course_location
,
self
.
user
.
id
)
self
.
store
.
delete_course
(
course_location
,
self
.
user
.
id
)
# pylint: disable=no-member
courses_list
,
__
=
_accessible_courses_list_from_groups
(
self
.
request
)
courses_list
,
__
=
_accessible_courses_list_from_groups
(
self
.
request
)
self
.
assertEqual
(
len
(
courses_list
),
1
,
courses_list
)
self
.
assertEqual
(
len
(
courses_list
),
1
,
courses_list
)
...
@@ -241,7 +296,7 @@ class TestCourseListing(ModuleStoreTestCase):
...
@@ -241,7 +296,7 @@ class TestCourseListing(ModuleStoreTestCase):
run
=
org_course_one
.
run
run
=
org_course_one
.
run
)
)
org_course_two
=
self
.
store
.
make_course_key
(
'AwesomeOrg'
,
'Course2'
,
'Run
Run
Run'
)
org_course_two
=
self
.
store
.
make_course_key
(
'AwesomeOrg'
,
'Course2'
,
'Run
Baby
Run'
)
CourseFactory
.
create
(
CourseFactory
.
create
(
org
=
org_course_two
.
org
,
org
=
org_course_two
.
org
,
number
=
org_course_two
.
course
,
number
=
org_course_two
.
course
,
...
...
cms/djangoapps/contentstore/views/course.py
View file @
c32823df
...
@@ -17,6 +17,7 @@ import django.utils
...
@@ -17,6 +17,7 @@ import django.utils
from
django.utils.translation
import
ugettext
as
_
from
django.utils.translation
import
ugettext
as
_
from
django.views.decorators.http
import
require_http_methods
,
require_GET
from
django.views.decorators.http
import
require_http_methods
,
require_GET
from
django.views.decorators.csrf
import
ensure_csrf_cookie
from
django.views.decorators.csrf
import
ensure_csrf_cookie
from
opaque_keys
import
InvalidKeyError
from
opaque_keys
import
InvalidKeyError
from
opaque_keys.edx.keys
import
CourseKey
from
opaque_keys.edx.keys
import
CourseKey
from
opaque_keys.edx.locations
import
Location
from
opaque_keys.edx.locations
import
Location
...
@@ -345,6 +346,39 @@ def _course_outline_json(request, course_module):
...
@@ -345,6 +346,39 @@ def _course_outline_json(request, course_module):
)
)
def
get_in_process_course_actions
(
request
):
"""
Get all in-process course actions
"""
return
[
course
for
course
in
CourseRerunState
.
objects
.
find_all
(
exclude_args
=
{
'state'
:
CourseRerunUIStateManager
.
State
.
SUCCEEDED
},
should_display
=
True
)
if
has_studio_read_access
(
request
.
user
,
course
.
course_key
)
]
def
_staff_accessible_course_list
(
request
):
"""
List all courses available to the logged in user by iterating through all the courses
"""
def
course_filter
(
course_summary
):
"""
Filter out unusable and inaccessible courses
"""
# pylint: disable=fixme
# TODO remove this condition when templates purged from db
if
course_summary
.
location
.
course
==
'templates'
:
return
False
return
has_studio_read_access
(
request
.
user
,
course_summary
.
id
)
courses_summary
=
filter
(
course_filter
,
modulestore
()
.
get_course_summaries
())
in_process_course_actions
=
get_in_process_course_actions
(
request
)
return
courses_summary
,
in_process_course_actions
def
_accessible_courses_list
(
request
):
def
_accessible_courses_list
(
request
):
"""
"""
List all courses available to the logged in user by iterating through all the courses
List all courses available to the logged in user by iterating through all the courses
...
@@ -364,13 +398,8 @@ def _accessible_courses_list(request):
...
@@ -364,13 +398,8 @@ def _accessible_courses_list(request):
return
has_studio_read_access
(
request
.
user
,
course
.
id
)
return
has_studio_read_access
(
request
.
user
,
course
.
id
)
courses
=
filter
(
course_filter
,
modulestore
()
.
get_courses
())
courses
=
filter
(
course_filter
,
modulestore
()
.
get_courses
())
in_process_course_actions
=
[
course
for
course
in
in_process_course_actions
=
get_in_process_course_actions
(
request
)
CourseRerunState
.
objects
.
find_all
(
exclude_args
=
{
'state'
:
CourseRerunUIStateManager
.
State
.
SUCCEEDED
},
should_display
=
True
)
if
has_studio_read_access
(
request
.
user
,
course
.
course_key
)
]
return
courses
,
in_process_course_actions
return
courses
,
in_process_course_actions
...
@@ -593,7 +622,7 @@ def get_courses_accessible_to_user(request):
...
@@ -593,7 +622,7 @@ def get_courses_accessible_to_user(request):
"""
"""
if
GlobalStaff
()
.
has_user
(
request
.
user
):
if
GlobalStaff
()
.
has_user
(
request
.
user
):
# user has global access so no need to get courses from django groups
# user has global access so no need to get courses from django groups
courses
,
in_process_course_actions
=
_
accessible_courses
_list
(
request
)
courses
,
in_process_course_actions
=
_
staff_accessible_course
_list
(
request
)
else
:
else
:
try
:
try
:
courses
,
in_process_course_actions
=
_accessible_courses_list_from_groups
(
request
)
courses
,
in_process_course_actions
=
_accessible_courses_list_from_groups
(
request
)
...
@@ -626,9 +655,9 @@ def _remove_in_process_courses(courses, in_process_course_actions):
...
@@ -626,9 +655,9 @@ def _remove_in_process_courses(courses, in_process_course_actions):
in_process_action_course_keys
=
[
uca
.
course_key
for
uca
in
in_process_course_actions
]
in_process_action_course_keys
=
[
uca
.
course_key
for
uca
in
in_process_course_actions
]
courses
=
[
courses
=
[
format_course_for_view
(
c
)
format_course_for_view
(
c
ourse
)
for
c
in
courses
for
c
ourse
in
courses
if
not
isinstance
(
c
,
ErrorDescriptor
)
and
(
c
.
id
not
in
in_process_action_course_keys
)
if
not
isinstance
(
c
ourse
,
ErrorDescriptor
)
and
(
course
.
id
not
in
in_process_action_course_keys
)
]
]
return
courses
return
courses
...
...
common/lib/xmodule/xmodule/course_module.py
View file @
c32823df
...
@@ -1367,3 +1367,56 @@ class CourseDescriptor(CourseFields, SequenceDescriptor, LicenseMixin):
...
@@ -1367,3 +1367,56 @@ class CourseDescriptor(CourseFields, SequenceDescriptor, LicenseMixin):
bool: False if the course has already started, True otherwise.
bool: False if the course has already started, True otherwise.
"""
"""
return
datetime
.
now
(
UTC
())
<=
self
.
start
return
datetime
.
now
(
UTC
())
<=
self
.
start
class
CourseSummary
(
object
):
"""
A lightweight course summary class, which constructs split/mongo course summary without loading
the course. It is used at cms for listing courses to global staff user.
"""
course_info_fields
=
[
'display_name'
,
'display_coursenumber'
,
'display_organization'
]
def
__init__
(
self
,
course_locator
,
display_name
=
u"Empty"
,
display_coursenumber
=
None
,
display_organization
=
None
):
"""
Initialize and construct course summary
Arguments:
course_locator (CourseLocator): CourseLocator object of the course.
display_name (unicode): display name of the course. When you create a course from console, display_name
isn't set (course block has no key `display_name`). "Empty" name is returned when we load the course.
If `display_name` isn't present in the course block, use the `Empty` as default display name.
We can set None as a display_name in Course Advance Settings; Do not use "Empty" when display_name is
set to None.
display_coursenumber (unicode|None): Course number that is specified & appears in the courseware
display_organization (unicode|None): Course organization that is specified & appears in the courseware
"""
self
.
display_coursenumber
=
display_coursenumber
self
.
display_organization
=
display_organization
self
.
display_name
=
display_name
self
.
id
=
course_locator
# pylint: disable=invalid-name
self
.
location
=
course_locator
.
make_usage_key
(
'course'
,
'course'
)
@property
def
display_org_with_default
(
self
):
"""
Return a display organization if it has been specified, otherwise return the 'org' that
is in the location
"""
if
self
.
display_organization
:
return
self
.
display_organization
return
self
.
location
.
org
@property
def
display_number_with_default
(
self
):
"""
Return a display course number if it has been specified, otherwise return the 'course' that
is in the location
"""
if
self
.
display_coursenumber
:
return
self
.
display_coursenumber
return
self
.
location
.
course
common/lib/xmodule/xmodule/modulestore/exceptions.py
View file @
c32823df
...
@@ -11,6 +11,13 @@ class ItemWriteConflictError(Exception):
...
@@ -11,6 +11,13 @@ class ItemWriteConflictError(Exception):
pass
pass
class
MultipleCourseBlocksFound
(
Exception
):
"""
Raise this exception when Iterating over the course blocks return multiple course blocks.
"""
pass
class
InsufficientSpecificationError
(
Exception
):
class
InsufficientSpecificationError
(
Exception
):
pass
pass
...
...
common/lib/xmodule/xmodule/modulestore/mixed.py
View file @
c32823df
...
@@ -266,6 +266,26 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
...
@@ -266,6 +266,26 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
return
store
.
get_items
(
course_key
,
**
kwargs
)
return
store
.
get_items
(
course_key
,
**
kwargs
)
@strip_key
@strip_key
def
get_course_summaries
(
self
,
**
kwargs
):
"""
Returns a list containing the course information in CourseSummary objects.
Information contains `location`, `display_name`, `locator` of the courses in this modulestore.
"""
course_summaries
=
{}
for
store
in
self
.
modulestores
:
for
course_summary
in
store
.
get_course_summaries
(
**
kwargs
):
course_id
=
self
.
_clean_locator_for_mapping
(
locator
=
course_summary
.
id
)
# Check if course is indeed unique. Save it in result if unique
if
course_id
in
course_summaries
:
log
.
warning
(
u"Modulestore
%
s have duplicate courses
%
s; skipping from result."
,
store
,
course_id
)
else
:
course_summaries
[
course_id
]
=
course_summary
return
course_summaries
.
values
()
@strip_key
def
get_courses
(
self
,
**
kwargs
):
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.
...
...
common/lib/xmodule/xmodule/modulestore/mongo/base.py
View file @
c32823df
...
@@ -39,6 +39,7 @@ from xblock.fields import Scope, ScopeIds, Reference, ReferenceList, ReferenceVa
...
@@ -39,6 +39,7 @@ from xblock.fields import Scope, ScopeIds, Reference, ReferenceList, ReferenceVa
from
xblock.runtime
import
KvsFieldData
from
xblock.runtime
import
KvsFieldData
from
xmodule.assetstore
import
AssetMetadata
,
CourseAssetsFromStorage
from
xmodule.assetstore
import
AssetMetadata
,
CourseAssetsFromStorage
from
xmodule.course_module
import
CourseSummary
from
xmodule.error_module
import
ErrorDescriptor
from
xmodule.error_module
import
ErrorDescriptor
from
xmodule.errortracker
import
null_error_tracker
,
exc_info_to_str
from
xmodule.errortracker
import
null_error_tracker
,
exc_info_to_str
from
xmodule.exceptions
import
HeartbeatFailure
from
xmodule.exceptions
import
HeartbeatFailure
...
@@ -969,6 +970,40 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase, Mongo
...
@@ -969,6 +970,40 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase, Mongo
return
apply_cached_metadata
return
apply_cached_metadata
@autoretry_read
()
@autoretry_read
()
def
get_course_summaries
(
self
,
**
kwargs
):
"""
Returns a list of `CourseSummary`. This accepts an optional parameter of 'org' which
will apply an efficient filter to only get courses with the specified ORG
"""
def
extract_course_summary
(
course
):
"""
Extract course information from the course block for mongo.
"""
return
{
field
:
course
[
'metadata'
][
field
]
for
field
in
CourseSummary
.
course_info_fields
if
field
in
course
[
'metadata'
]
}
course_org_filter
=
kwargs
.
get
(
'org'
)
query
=
{
'_id.category'
:
'course'
}
if
course_org_filter
:
query
[
'_id.org'
]
=
course_org_filter
course_records
=
self
.
collection
.
find
(
query
,
{
'metadata'
:
True
})
courses_summaries
=
[]
for
course
in
course_records
:
if
not
(
course
[
'_id'
][
'org'
]
==
'edx'
and
course
[
'_id'
][
'course'
]
==
'templates'
):
locator
=
SlashSeparatedCourseKey
(
course
[
'_id'
][
'org'
],
course
[
'_id'
][
'course'
],
course
[
'_id'
][
'name'
])
course_summary
=
extract_course_summary
(
course
)
courses_summaries
.
append
(
CourseSummary
(
locator
,
**
course_summary
)
)
return
courses_summaries
@autoretry_read
()
def
get_courses
(
self
,
**
kwargs
):
def
get_courses
(
self
,
**
kwargs
):
'''
'''
Returns a list of course descriptors. This accepts an optional parameter of 'org' which
Returns a list of course descriptors. This accepts an optional parameter of 'org' which
...
...
common/lib/xmodule/xmodule/modulestore/split_mongo/caching_descriptor_system.py
View file @
c32823df
...
@@ -209,6 +209,7 @@ class CachingDescriptorSystem(MakoDescriptorSystem, EditInfoRuntimeMixin):
...
@@ -209,6 +209,7 @@ class CachingDescriptorSystem(MakoDescriptorSystem, EditInfoRuntimeMixin):
parent
=
course_key
.
make_usage_key
(
parent_key
.
type
,
parent_key
.
id
)
parent
=
course_key
.
make_usage_key
(
parent_key
.
type
,
parent_key
.
id
)
else
:
else
:
parent
=
None
parent
=
None
try
:
kvs
=
SplitMongoKVS
(
kvs
=
SplitMongoKVS
(
definition_loader
,
definition_loader
,
converted_fields
,
converted_fields
,
...
@@ -222,7 +223,6 @@ class CachingDescriptorSystem(MakoDescriptorSystem, EditInfoRuntimeMixin):
...
@@ -222,7 +223,6 @@ class CachingDescriptorSystem(MakoDescriptorSystem, EditInfoRuntimeMixin):
else
:
else
:
field_data
=
KvsFieldData
(
kvs
)
field_data
=
KvsFieldData
(
kvs
)
try
:
module
=
self
.
construct_xblock_from_class
(
module
=
self
.
construct_xblock_from_class
(
class_
,
class_
,
ScopeIds
(
None
,
block_key
.
type
,
definition_id
,
block_locator
),
ScopeIds
(
None
,
block_key
.
type
,
definition_id
,
block_locator
),
...
...
common/lib/xmodule/xmodule/modulestore/split_mongo/mongo_connection.py
View file @
c32823df
...
@@ -354,6 +354,26 @@ class MongoConnection(object):
...
@@ -354,6 +354,26 @@ class MongoConnection(object):
return
docs
return
docs
@autoretry_read
()
@autoretry_read
()
def
find_course_blocks_by_id
(
self
,
ids
,
course_context
=
None
):
"""
Find all structures that specified in `ids`. Among the blocks only return block whose type is `course`.
Arguments:
ids (list): A list of structure ids
"""
with
TIMER
.
timer
(
"find_course_blocks_by_id"
,
course_context
)
as
tagger
:
tagger
.
measure
(
"requested_ids"
,
len
(
ids
))
docs
=
[
structure_from_mongo
(
structure
,
course_context
)
for
structure
in
self
.
structures
.
find
(
{
'_id'
:
{
'$in'
:
ids
}},
{
'blocks'
:
{
'$elemMatch'
:
{
'block_type'
:
'course'
}},
'root'
:
1
}
)
]
tagger
.
measure
(
"structures"
,
len
(
docs
))
return
docs
@autoretry_read
()
def
find_structures_derived_from
(
self
,
ids
,
course_context
=
None
):
def
find_structures_derived_from
(
self
,
ids
,
course_context
=
None
):
"""
"""
Return all structures that were immediately derived from a structure listed in ``ids``.
Return all structures that were immediately derived from a structure listed in ``ids``.
...
...
common/lib/xmodule/xmodule/modulestore/split_mongo/split.py
View file @
c32823df
...
@@ -66,13 +66,14 @@ from bson.objectid import ObjectId
...
@@ -66,13 +66,14 @@ from bson.objectid import ObjectId
from
xblock.core
import
XBlock
from
xblock.core
import
XBlock
from
xblock.fields
import
Scope
,
Reference
,
ReferenceList
,
ReferenceValueDict
from
xblock.fields
import
Scope
,
Reference
,
ReferenceList
,
ReferenceValueDict
from
xmodule.course_module
import
CourseSummary
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
,
LibraryLocator
,
VersionTree
,
LocalId
,
BlockUsageLocator
,
DefinitionLocator
,
CourseLocator
,
LibraryLocator
,
VersionTree
,
LocalId
,
)
)
from
ccx_keys.locator
import
CCXLocator
,
CCXBlockUsageLocator
from
ccx_keys.locator
import
CCXLocator
,
CCXBlockUsageLocator
from
xmodule.modulestore.exceptions
import
InsufficientSpecificationError
,
VersionConflictError
,
DuplicateItemError
,
\
from
xmodule.modulestore.exceptions
import
InsufficientSpecificationError
,
VersionConflictError
,
DuplicateItemError
,
\
DuplicateCourseError
DuplicateCourseError
,
MultipleCourseBlocksFound
from
xmodule.modulestore
import
(
from
xmodule.modulestore
import
(
inheritance
,
ModuleStoreWriteBase
,
ModuleStoreEnum
,
inheritance
,
ModuleStoreWriteBase
,
ModuleStoreEnum
,
BulkOpsRecord
,
BulkOperationsMixin
,
SortedAssetList
,
BlockData
BulkOpsRecord
,
BulkOperationsMixin
,
SortedAssetList
,
BlockData
...
@@ -539,6 +540,17 @@ class SplitBulkWriteMixin(BulkOperationsMixin):
...
@@ -539,6 +540,17 @@ class SplitBulkWriteMixin(BulkOperationsMixin):
return
indexes
return
indexes
def
find_course_blocks_by_id
(
self
,
ids
):
"""
Find all structures that specified in `ids`. Filter the course blocks to only return whose
`block_type` is `course`
Arguments:
ids (list): A list of structure ids
"""
ids
=
set
(
ids
)
return
self
.
db_connection
.
find_course_blocks_by_id
(
list
(
ids
))
def
find_structures_by_id
(
self
,
ids
):
def
find_structures_by_id
(
self
,
ids
):
"""
"""
Return all structures that specified in ``ids``.
Return all structures that specified in ``ids``.
...
@@ -849,15 +861,39 @@ class SplitMongoModuleStore(SplitBulkWriteMixin, ModuleStoreWriteBase):
...
@@ -849,15 +861,39 @@ class SplitMongoModuleStore(SplitBulkWriteMixin, ModuleStoreWriteBase):
# add it in the envelope for the structure.
# add it in the envelope for the structure.
return
CourseEnvelope
(
course_key
.
replace
(
version_guid
=
version_guid
),
entry
)
return
CourseEnvelope
(
course_key
.
replace
(
version_guid
=
version_guid
),
entry
)
def
_get_course_blocks_for_branch
(
self
,
branch
,
**
kwargs
):
"""
Internal generator for fetching lists of courses without loading them.
"""
version_guids
,
id_version_map
=
self
.
collect_ids_from_matching_indexes
(
branch
,
**
kwargs
)
if
not
version_guids
:
return
for
entry
in
self
.
find_course_blocks_by_id
(
version_guids
):
for
course_index
in
id_version_map
[
entry
[
'_id'
]]:
yield
entry
,
course_index
def
_get_structures_for_branch
(
self
,
branch
,
**
kwargs
):
def
_get_structures_for_branch
(
self
,
branch
,
**
kwargs
):
"""
"""
Internal generator for fetching lists of courses, libraries, etc.
Internal generator for fetching lists of courses, libraries, etc.
"""
"""
version_guids
,
id_version_map
=
self
.
collect_ids_from_matching_indexes
(
branch
,
**
kwargs
)
# if we pass in a 'org' parameter that means to
if
not
version_guids
:
# only get the course which match the passed in
return
# ORG
for
entry
in
self
.
find_structures_by_id
(
version_guids
):
for
course_index
in
id_version_map
[
entry
[
'_id'
]]:
yield
entry
,
course_index
def
collect_ids_from_matching_indexes
(
self
,
branch
,
**
kwargs
):
"""
Find the course_indexes which have the specified branch. if `kwargs` contains `org`
to apply an ORG filter to return only the courses that are part of that ORG. Extract `version_guids`
from the course_indexes.
"""
matching_indexes
=
self
.
find_matching_course_indexes
(
matching_indexes
=
self
.
find_matching_course_indexes
(
branch
,
branch
,
search_targets
=
None
,
search_targets
=
None
,
...
@@ -871,13 +907,7 @@ class SplitMongoModuleStore(SplitBulkWriteMixin, ModuleStoreWriteBase):
...
@@ -871,13 +907,7 @@ class SplitMongoModuleStore(SplitBulkWriteMixin, ModuleStoreWriteBase):
version_guid
=
course_index
[
'versions'
][
branch
]
version_guid
=
course_index
[
'versions'
][
branch
]
version_guids
.
append
(
version_guid
)
version_guids
.
append
(
version_guid
)
id_version_map
[
version_guid
]
.
append
(
course_index
)
id_version_map
[
version_guid
]
.
append
(
course_index
)
return
version_guids
,
id_version_map
if
not
version_guids
:
return
for
entry
in
self
.
find_structures_by_id
(
version_guids
):
for
course_index
in
id_version_map
[
entry
[
'_id'
]]:
yield
entry
,
course_index
def
_get_structures_for_branch_and_locator
(
self
,
branch
,
locator_factory
,
**
kwargs
):
def
_get_structures_for_branch_and_locator
(
self
,
branch
,
locator_factory
,
**
kwargs
):
...
@@ -933,6 +963,50 @@ class SplitMongoModuleStore(SplitBulkWriteMixin, ModuleStoreWriteBase):
...
@@ -933,6 +963,50 @@ class SplitMongoModuleStore(SplitBulkWriteMixin, ModuleStoreWriteBase):
# get the blocks for each course index (s/b the root)
# get the blocks for each course index (s/b the root)
return
self
.
_get_structures_for_branch_and_locator
(
branch
,
self
.
_create_course_locator
,
**
kwargs
)
return
self
.
_get_structures_for_branch_and_locator
(
branch
,
self
.
_create_course_locator
,
**
kwargs
)
@autoretry_read
()
def
get_course_summaries
(
self
,
branch
,
**
kwargs
):
"""
Returns a list of `CourseSummary` which matching any given qualifiers.
qualifiers should be a dict of keywords matching the db fields or any
legal query for mongo to use against the active_versions collection.
Note, this is to find the current head of the named branch type.
To get specific versions via guid use get_course.
:param branch: the branch for which to return courses.
"""
def
extract_course_summary
(
course
):
"""
Extract course information from the course block for split.
"""
return
{
field
:
course
.
fields
[
field
]
for
field
in
CourseSummary
.
course_info_fields
if
field
in
course
.
fields
}
courses_summaries
=
[]
for
entry
,
structure_info
in
self
.
_get_course_blocks_for_branch
(
branch
,
**
kwargs
):
course_locator
=
self
.
_create_course_locator
(
structure_info
,
branch
=
None
)
course_block
=
[
block_data
for
block_key
,
block_data
in
entry
[
'blocks'
]
.
items
()
if
block_key
.
type
==
"course"
]
if
not
course_block
:
raise
ItemNotFoundError
if
len
(
course_block
)
>
1
:
raise
MultipleCourseBlocksFound
(
"Expected 1 course block to be found in the course, but found {0}"
.
format
(
len
(
course_block
))
)
course_summary
=
extract_course_summary
(
course_block
[
0
])
courses_summaries
.
append
(
CourseSummary
(
course_locator
,
**
course_summary
)
)
return
courses_summaries
def
get_libraries
(
self
,
branch
=
"library"
,
**
kwargs
):
def
get_libraries
(
self
,
branch
=
"library"
,
**
kwargs
):
"""
"""
Returns a list of "library" root blocks matching any given qualifiers.
Returns a list of "library" root blocks matching any given qualifiers.
...
...
common/lib/xmodule/xmodule/modulestore/split_mongo/split_draft.py
View file @
c32823df
...
@@ -74,6 +74,22 @@ class DraftVersioningModuleStore(SplitMongoModuleStore, ModuleStoreDraftAndPubli
...
@@ -74,6 +74,22 @@ class DraftVersioningModuleStore(SplitMongoModuleStore, ModuleStoreDraftAndPubli
source_course_id
,
dest_course_id
,
user_id
,
fields
=
fields
,
**
kwargs
source_course_id
,
dest_course_id
,
user_id
,
fields
=
fields
,
**
kwargs
)
)
def
get_course_summaries
(
self
,
**
kwargs
):
"""
Returns course summaries on the Draft or Published branch depending on the branch setting.
"""
branch_setting
=
self
.
get_branch_setting
()
if
branch_setting
==
ModuleStoreEnum
.
Branch
.
draft_preferred
:
return
super
(
DraftVersioningModuleStore
,
self
)
.
get_course_summaries
(
ModuleStoreEnum
.
BranchName
.
draft
,
**
kwargs
)
elif
branch_setting
==
ModuleStoreEnum
.
Branch
.
published_only
:
return
super
(
DraftVersioningModuleStore
,
self
)
.
get_course_summaries
(
ModuleStoreEnum
.
BranchName
.
published
,
**
kwargs
)
else
:
raise
InsufficientSpecificationError
()
def
get_courses
(
self
,
**
kwargs
):
def
get_courses
(
self
,
**
kwargs
):
"""
"""
Returns all the courses on the Draft or Published branch depending on the branch setting.
Returns all the courses on the Draft or Published branch depending on the branch setting.
...
...
common/lib/xmodule/xmodule/modulestore/tests/test_semantics.py
View file @
c32823df
...
@@ -5,6 +5,7 @@ Tests of modulestore semantics: How do the interfaces methods of ModuleStore rel
...
@@ -5,6 +5,7 @@ Tests of modulestore semantics: How do the interfaces methods of ModuleStore rel
import
ddt
import
ddt
import
itertools
import
itertools
from
collections
import
namedtuple
from
collections
import
namedtuple
from
xmodule.course_module
import
CourseSummary
from
xmodule.modulestore.tests.utils
import
(
from
xmodule.modulestore.tests.utils
import
(
PureModulestoreTestCase
,
MongoModulestoreBuilder
,
PureModulestoreTestCase
,
MongoModulestoreBuilder
,
...
@@ -177,6 +178,20 @@ class DirectOnlyCategorySemantics(PureModulestoreTestCase):
...
@@ -177,6 +178,20 @@ class DirectOnlyCategorySemantics(PureModulestoreTestCase):
"""
"""
self
.
assertNotParentOf
(
self
.
course
.
scope_ids
.
usage_id
,
block_usage_key
,
draft
=
draft
)
self
.
assertNotParentOf
(
self
.
course
.
scope_ids
.
usage_id
,
block_usage_key
,
draft
=
draft
)
def
assertCourseSummaryFields
(
self
,
course_summaries
):
"""
Assert that the `course_summary` of a course has all expected fields.
Arguments:
course_summaries: list of CourseSummary class objects.
"""
def
verify_course_summery_fields
(
course_summary
):
""" Verify that every `course_summary` object has all the required fields """
expected_fields
=
CourseSummary
.
course_info_fields
+
[
'id'
,
'location'
]
return
all
([
hasattr
(
course_summary
,
field
)
for
field
in
expected_fields
])
self
.
assertTrue
(
all
(
verify_course_summery_fields
(
course_summary
)
for
course_summary
in
course_summaries
))
def
is_detached
(
self
,
block_type
):
def
is_detached
(
self
,
block_type
):
"""
"""
Return True if ``block_type`` is a detached block.
Return True if ``block_type`` is a detached block.
...
@@ -259,6 +274,21 @@ class DirectOnlyCategorySemantics(PureModulestoreTestCase):
...
@@ -259,6 +274,21 @@ class DirectOnlyCategorySemantics(PureModulestoreTestCase):
self
.
assertCourseDoesntPointToBlock
(
block_usage_key
)
self
.
assertCourseDoesntPointToBlock
(
block_usage_key
)
self
.
assertBlockDoesntExist
(
block_usage_key
)
self
.
assertBlockDoesntExist
(
block_usage_key
)
@ddt.data
(
ModuleStoreEnum
.
Branch
.
draft_preferred
,
ModuleStoreEnum
.
Branch
.
published_only
)
def
test_course_summaries
(
self
,
branch
):
""" Test that `get_course_summaries` method in modulestore work as expected. """
with
self
.
store
.
branch_setting
(
branch_setting
=
branch
):
course_summaries
=
self
.
store
.
get_course_summaries
()
# Verify course summaries
self
.
assertEqual
(
len
(
course_summaries
),
1
)
# Verify that all course summary objects have the required attributes.
self
.
assertCourseSummaryFields
(
course_summaries
)
# Verify fetched accessible courses list is a list of CourseSummery instances
self
.
assertTrue
(
all
(
isinstance
(
course
,
CourseSummary
)
for
course
in
course_summaries
))
@ddt.data
(
*
itertools
.
product
([
'chapter'
,
'sequential'
],
[
True
,
False
]))
@ddt.data
(
*
itertools
.
product
([
'chapter'
,
'sequential'
],
[
True
,
False
]))
@ddt.unpack
@ddt.unpack
def
test_delete_child
(
self
,
block_type
,
child_published
):
def
test_delete_child
(
self
,
block_type
,
child_published
):
...
...
common/lib/xmodule/xmodule/modulestore/xml.py
View file @
c32823df
...
@@ -835,6 +835,12 @@ class XMLModuleStore(ModuleStoreReadBase):
...
@@ -835,6 +835,12 @@ class XMLModuleStore(ModuleStoreReadBase):
"""
"""
return
self
.
courses
.
values
()
return
self
.
courses
.
values
()
def
get_course_summaries
(
self
,
**
kwargs
):
"""
Returns `self.get_courses()`. Use to list courses to the global staff user.
"""
return
self
.
get_courses
(
**
kwargs
)
def
get_errored_courses
(
self
):
def
get_errored_courses
(
self
):
"""
"""
Return a dictionary of course_dir -> [(msg, exception_str)], for each
Return a dictionary of course_dir -> [(msg, exception_str)], for each
...
...
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