Commit cb3f8584 by noraiz-anwar

use CourseSummary for course listing on studio for non-global staff users

parent 725edbbc
...@@ -390,7 +390,6 @@ def _accessible_courses_summary_iter(request, org=None): ...@@ -390,7 +390,6 @@ def _accessible_courses_summary_iter(request, org=None):
def _accessible_courses_iter(request): def _accessible_courses_iter(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.
This method is only used by tests.
""" """
def course_filter(course): def course_filter(course):
""" """
...@@ -417,6 +416,35 @@ def _accessible_courses_iter(request): ...@@ -417,6 +416,35 @@ def _accessible_courses_iter(request):
return courses, in_process_course_actions return courses, in_process_course_actions
def _accessible_courses_iter_for_tests(request):
"""
List all courses available to the logged in user by iterating through all the courses.
CourseSummary objects are used for listing purposes.
This method is only used by tests.
"""
def course_filter(course):
"""
Filter out unusable and inaccessible courses
"""
# Custom Courses for edX (CCX) is an edX feature for re-using course content.
# CCXs cannot be edited in Studio (aka cms) and should not be shown in this dashboard.
if isinstance(course.id, CCXLocator):
return False
# pylint: disable=fixme
# TODO remove this condition when templates purged from db
if course.location.course == 'templates':
return False
return has_studio_read_access(request.user, course.id)
courses = six.moves.filter(course_filter, modulestore().get_course_summaries())
in_process_course_actions = get_in_process_course_actions(request)
return courses, in_process_course_actions
def _accessible_courses_list_from_groups(request): def _accessible_courses_list_from_groups(request):
""" """
List all courses available to the logged in user by reversing access group names List all courses available to the logged in user by reversing access group names
...@@ -425,39 +453,23 @@ def _accessible_courses_list_from_groups(request): ...@@ -425,39 +453,23 @@ def _accessible_courses_list_from_groups(request):
""" CCXs cannot be edited in Studio and should not be shown in this dashboard """ """ CCXs cannot be edited in Studio and should not be shown in this dashboard """
return not isinstance(course_access.course_id, CCXLocator) return not isinstance(course_access.course_id, CCXLocator)
courses_list = {}
in_process_course_actions = []
instructor_courses = UserBasedRole(request.user, CourseInstructorRole.ROLE).courses_with_role() instructor_courses = UserBasedRole(request.user, CourseInstructorRole.ROLE).courses_with_role()
staff_courses = UserBasedRole(request.user, CourseStaffRole.ROLE).courses_with_role() staff_courses = UserBasedRole(request.user, CourseStaffRole.ROLE).courses_with_role()
all_courses = filter(filter_ccx, instructor_courses | staff_courses) all_courses = filter(filter_ccx, instructor_courses | staff_courses)
courses_list = []
course_keys = {}
for course_access in all_courses: for course_access in all_courses:
course_key = course_access.course_id if course_access.course_id is None:
if course_key is None:
# If the course_access does not have a course_id, it's an org-based role, so we fall back
raise AccessListFallback raise AccessListFallback
if course_key not in courses_list: course_keys[course_access.course_id] = course_access.course_id
# check for any course action state for this course
in_process_course_actions.extend( course_keys = course_keys.values()
CourseRerunState.objects.find_all(
exclude_args={'state': CourseRerunUIStateManager.State.SUCCEEDED},
should_display=True,
course_key=course_key,
)
)
# check for the course itself
try:
course = modulestore().get_course(course_key)
except ItemNotFoundError:
# If a user has access to a course that doesn't exist, don't do anything with that course
pass
if course is not None and not isinstance(course, ErrorDescriptor): if course_keys:
# ignore deleted, errored or ccx courses courses_list = modulestore().get_course_summaries(course_keys=course_keys)
courses_list[course_key] = course
return courses_list.values(), in_process_course_actions return courses_list, []
def _accessible_libraries_iter(user, org=None): def _accessible_libraries_iter(user, org=None):
......
...@@ -392,8 +392,8 @@ class TestCourseIndexArchived(CourseTestCase): ...@@ -392,8 +392,8 @@ class TestCourseIndexArchived(CourseTestCase):
@ddt.data( @ddt.data(
# Staff user has course staff access # Staff user has course staff access
(True, 'staff', None, 4, 21), (True, 'staff', None, 3, 17),
(False, 'staff', None, 4, 21), (False, 'staff', None, 3, 17),
# Base user has global staff access # Base user has global staff access
(True, 'user', ORG, 3, 17), (True, 'user', ORG, 3, 17),
(False, 'user', ORG, 3, 17), (False, 'user', ORG, 3, 17),
......
...@@ -1010,11 +1010,23 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase, Mongo ...@@ -1010,11 +1010,23 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase, Mongo
if field in course['metadata'] if field in course['metadata']
} }
course_org_filter = kwargs.get('org') course_records = []
query = {'_id.category': 'course'} query = {'_id.category': 'course'}
course_org_filter = kwargs.get('org')
if course_org_filter: course_keys = kwargs.get('course_keys')
query['_id.org'] = course_org_filter
if course_keys:
course_queries = []
for course_key in course_keys:
course_query = {
'_id.{}'.format(value_attr): getattr(course_key, key_attr)
for key_attr, value_attr in {'org': 'org', 'course': 'course', 'run': 'name'}.iteritems()
}
course_query.update(query)
course_queries.append(course_query)
query = {'$or': course_queries}
elif course_org_filter:
query['_id.org'] = course_org_filter
course_records = self.collection.find(query, {'metadata': True}) course_records = self.collection.find(query, {'metadata': True})
...@@ -1028,6 +1040,7 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase, Mongo ...@@ -1028,6 +1040,7 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase, Mongo
courses_summaries.append( courses_summaries.append(
CourseSummary(locator, **course_summary) CourseSummary(locator, **course_summary)
) )
return courses_summaries return courses_summaries
@autoretry_read() @autoretry_read()
......
...@@ -451,7 +451,15 @@ class MongoConnection(object): ...@@ -451,7 +451,15 @@ class MongoConnection(object):
} }
return self.course_index.find_one(query) return self.course_index.find_one(query)
def find_matching_course_indexes(self, branch=None, search_targets=None, org_target=None, course_context=None): def find_matching_course_indexes(
self,
branch=None,
search_targets=None,
org_target=None,
course_context=None,
course_keys=None
):
""" """
Find the course_index matching particular conditions. Find the course_index matching particular conditions.
...@@ -464,18 +472,41 @@ class MongoConnection(object): ...@@ -464,18 +472,41 @@ class MongoConnection(object):
""" """
with TIMER.timer("find_matching_course_indexes", course_context): with TIMER.timer("find_matching_course_indexes", course_context):
query = {} query = {}
if branch is not None: if course_keys:
query['versions.{}'.format(branch)] = {'$exists': True} courses_queries = self._generate_query_from_course_keys(branch, course_keys)
query['$or'] = courses_queries
else:
if branch is not None:
query['versions.{}'.format(branch)] = {'$exists': True}
if search_targets: if search_targets:
for key, value in search_targets.iteritems(): for key, value in search_targets.iteritems():
query['search_targets.{}'.format(key)] = value query['search_targets.{}'.format(key)] = value
if org_target: if org_target:
query['org'] = org_target query['org'] = org_target
return self.course_index.find(query) return self.course_index.find(query)
def _generate_query_from_course_keys(self, branch, course_keys):
"""
Generate query for courses using course keys
"""
courses_queries = []
query = {}
if branch:
query = {'versions.{}'.format(branch): {'$exists': True}}
for course_key in course_keys:
course_query = {
key_attr: getattr(course_key, key_attr)
for key_attr in ('org', 'course', 'run')
}
course_query.update(query)
courses_queries.append(course_query)
return courses_queries
def insert_course_index(self, course_index, course_context=None): def insert_course_index(self, course_index, course_context=None):
""" """
Create the course_index in the db Create the course_index in the db
......
...@@ -497,7 +497,7 @@ class SplitBulkWriteMixin(BulkOperationsMixin): ...@@ -497,7 +497,7 @@ class SplitBulkWriteMixin(BulkOperationsMixin):
block_data.edit_info.original_usage = original_usage block_data.edit_info.original_usage = original_usage
block_data.edit_info.original_usage_version = original_usage_version block_data.edit_info.original_usage_version = original_usage_version
def find_matching_course_indexes(self, branch=None, search_targets=None, org_target=None): def find_matching_course_indexes(self, branch=None, search_targets=None, org_target=None, course_keys=None):
""" """
Find the course_indexes which have the specified branch and search_targets. An optional org_target Find the course_indexes which have the specified branch and search_targets. An optional org_target
can be specified to apply an ORG filter to return only the courses that are part of can be specified to apply an ORG filter to return only the courses that are part of
...@@ -506,19 +506,44 @@ class SplitBulkWriteMixin(BulkOperationsMixin): ...@@ -506,19 +506,44 @@ class SplitBulkWriteMixin(BulkOperationsMixin):
Returns: Returns:
a Cursor if there are no changes in flight or a list if some have changed in current bulk op a Cursor if there are no changes in flight or a list if some have changed in current bulk op
""" """
indexes = self.db_connection.find_matching_course_indexes(branch, search_targets, org_target) indexes = self.db_connection.find_matching_course_indexes(
branch,
search_targets,
org_target,
course_keys=course_keys)
indexes = self._add_indexes_from_active_records(
indexes,
branch,
search_targets,
org_target,
course_keys=course_keys
)
return indexes
def _add_indexes_from_active_records(
self,
course_indexes,
branch=None,
search_targets=None,
org_target=None,
course_keys=None
):
"""
Add any being built but not yet persisted or in the process of being updated
"""
def _replace_or_append_index(altered_index): def _replace_or_append_index(altered_index):
""" """
If the index is already in indexes, replace it. Otherwise, append it. If the index is already in indexes, replace it. Otherwise, append it.
""" """
for index, existing in enumerate(indexes): for index, existing in enumerate(course_indexes):
if all(existing[attr] == altered_index[attr] for attr in ['org', 'course', 'run']): if all(existing[attr] == altered_index[attr] for attr in ['org', 'course', 'run']):
indexes[index] = altered_index course_indexes[index] = altered_index
return return
indexes.append(altered_index) course_indexes.append(altered_index)
# add any being built but not yet persisted or in the process of being updated
for _, record in self._active_records: for _, record in self._active_records:
if branch and branch not in record.index.get('versions', {}): if branch and branch not in record.index.get('versions', {}):
continue continue
...@@ -531,7 +556,6 @@ class SplitBulkWriteMixin(BulkOperationsMixin): ...@@ -531,7 +556,6 @@ class SplitBulkWriteMixin(BulkOperationsMixin):
for field, value in search_targets.iteritems() for field, value in search_targets.iteritems()
): ):
continue continue
# if we've specified a filter by org, # if we've specified a filter by org,
# make sure we've honored that filter when # make sure we've honored that filter when
# integrating in-transit records # integrating in-transit records
...@@ -539,12 +563,22 @@ class SplitBulkWriteMixin(BulkOperationsMixin): ...@@ -539,12 +563,22 @@ class SplitBulkWriteMixin(BulkOperationsMixin):
if record.index['org'] != org_target: if record.index['org'] != org_target:
continue continue
if not hasattr(indexes, 'append'): # Just in time conversion to list from cursor if course_keys:
indexes = list(indexes) index_exists_in_active_records = False
for course_key in course_keys:
if all(record.index[key_attr] == getattr(course_key, key_attr)
for key_attr in ['org', 'course', 'run']):
index_exists_in_active_records = True
break
if not index_exists_in_active_records:
continue
if not hasattr(course_indexes, 'append'): # Just in time conversion to list from cursor
course_indexes = list(course_indexes)
_replace_or_append_index(record.index) _replace_or_append_index(record.index)
return indexes return course_indexes
def find_course_blocks_by_id(self, ids): def find_course_blocks_by_id(self, ids):
""" """
...@@ -905,15 +939,15 @@ class SplitMongoModuleStore(SplitBulkWriteMixin, ModuleStoreWriteBase): ...@@ -905,15 +939,15 @@ class SplitMongoModuleStore(SplitBulkWriteMixin, ModuleStoreWriteBase):
def collect_ids_from_matching_indexes(self, branch, **kwargs): def collect_ids_from_matching_indexes(self, branch, **kwargs):
""" """
Find the course_indexes which have the specified branch. if `kwargs` contains `org` Find the course_indexes which have the specified branch. Extract `version_guids`
to apply an ORG filter to return only the courses that are part of that ORG. Extract `version_guids`
from the course_indexes. 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,
org_target=kwargs.get('org') org_target=kwargs.get('org'),
course_keys=kwargs.get('course_keys')
) )
# collect ids and then query for those # collect ids and then query for those
......
...@@ -302,7 +302,13 @@ class TestBulkWriteMixinFindMethods(TestBulkWriteMixin): ...@@ -302,7 +302,13 @@ class TestBulkWriteMixinFindMethods(TestBulkWriteMixin):
org_targets = None org_targets = None
self.conn.find_matching_course_indexes.return_value = [Mock(name='result')] self.conn.find_matching_course_indexes.return_value = [Mock(name='result')]
result = self.bulk.find_matching_course_indexes(branch, search_targets) result = self.bulk.find_matching_course_indexes(branch, search_targets)
self.assertConnCalls(call.find_matching_course_indexes(branch, search_targets, org_targets)) self.assertConnCalls(call.find_matching_course_indexes(
branch,
search_targets,
org_targets,
course_keys=None
)
)
self.assertEqual(result, self.conn.find_matching_course_indexes.return_value) self.assertEqual(result, self.conn.find_matching_course_indexes.return_value)
self.assertCacheNotCleared() self.assertCacheNotCleared()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment