get_courses_for_wiki return course_keys across all modulestores [LMS-2914]
This commit is contained in:
@@ -262,11 +262,23 @@ class CourseTestCase(ModuleStoreTestCase):
|
||||
self.store.compute_publish_state(course2_item)
|
||||
)
|
||||
except AssertionError:
|
||||
# old mongo calls things draft if draft exists even if it's != published; so, do more work
|
||||
self.assertEqual(
|
||||
self.compute_real_state(course1_item),
|
||||
self.compute_real_state(course2_item)
|
||||
)
|
||||
# TODO LMS-11017 "Studio auto-publish course-wide features and settings"
|
||||
# Temporary hack until autopublish implemented - right now, because we call
|
||||
# update_item within create_course to set the wiki & other course-wide settings,
|
||||
# the publish version does not necessarily equal the draft version in split.
|
||||
# So if either item is in Split, just continue on
|
||||
if not isinstance(course1_item.runtime.modulestore, SplitMongoModuleStore) and \
|
||||
not isinstance(course2_item.runtime.modulestore, SplitMongoModuleStore):
|
||||
# old mongo calls things draft if draft exists even if it's != published; so, do more work
|
||||
c1_state = self.compute_real_state(course1_item)
|
||||
c2_state = self.compute_real_state(course2_item)
|
||||
self.assertEqual(
|
||||
c1_state,
|
||||
c2_state,
|
||||
"Course item {} in state {} != course item {} in state {}".format(
|
||||
course1_item, c1_state, course2_item, c2_state
|
||||
)
|
||||
)
|
||||
|
||||
# compare data
|
||||
self.assertEqual(hasattr(course1_item, 'data'), hasattr(course2_item, 'data'))
|
||||
@@ -329,11 +341,15 @@ class CourseTestCase(ModuleStoreTestCase):
|
||||
# see if the draft differs from the published
|
||||
published = self.store.get_item(item.location, revision=ModuleStoreEnum.RevisionOption.published_only)
|
||||
if item.get_explicitly_set_fields_by_scope() != published.get_explicitly_set_fields_by_scope():
|
||||
# checking content: if published differs from item, return draft
|
||||
return supposed_state
|
||||
if item.get_explicitly_set_fields_by_scope(Scope.settings) != published.get_explicitly_set_fields_by_scope(Scope.settings):
|
||||
# checking settings: if published differs from item, return draft
|
||||
return supposed_state
|
||||
if item.has_children and item.children != published.children:
|
||||
# checking children: if published differs from item, return draft
|
||||
return supposed_state
|
||||
# published == item in all respects, so return public
|
||||
return PublishState.public
|
||||
elif supposed_state == PublishState.public and item.location.category in mongo.base.DIRECT_ONLY_CATEGORIES:
|
||||
if not all([
|
||||
|
||||
@@ -286,6 +286,15 @@ class ModuleStoreRead(object):
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_courses_for_wiki(self, wiki_slug):
|
||||
"""
|
||||
Return the list of courses which use this wiki_slug
|
||||
:param wiki_slug: the course wiki root slug
|
||||
:return: list of course keys
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def compute_publish_state(self, xblock):
|
||||
"""
|
||||
|
||||
@@ -414,7 +414,7 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
|
||||
"""
|
||||
Return the list of courses which use this wiki_slug
|
||||
:param wiki_slug: the course wiki root slug
|
||||
:return: list of course locations
|
||||
:return: list of course keys
|
||||
"""
|
||||
courses = []
|
||||
for modulestore in self.modulestores:
|
||||
|
||||
@@ -60,6 +60,10 @@ BLOCK_TYPES_WITH_CHILDREN = list(set(
|
||||
name for name, class_ in XBlock.load_classes() if getattr(class_, 'has_children', False)
|
||||
))
|
||||
|
||||
# Allow us to call _from_deprecated_(son|string) throughout the file
|
||||
# pylint: disable=protected-access
|
||||
|
||||
|
||||
class MongoRevisionKey(object):
|
||||
"""
|
||||
Key Revision constants to use for Location and Usage Keys in the Mongo modulestore
|
||||
@@ -1252,14 +1256,17 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
|
||||
"""
|
||||
Return the list of courses which use this wiki_slug
|
||||
:param wiki_slug: the course wiki root slug
|
||||
:return: list of course locations
|
||||
:return: list of course keys
|
||||
"""
|
||||
courses = self.collection.find(
|
||||
{'_id.category': 'course', 'definition.data.wiki_slug': wiki_slug},
|
||||
{'_id': True}
|
||||
)
|
||||
# the course's run == its name. It's the only xblock for which that's necessarily true.
|
||||
return [Location._from_deprecated_son(course['_id'], course['_id']['name']) for course in courses]
|
||||
return [
|
||||
Location._from_deprecated_son(course['_id'], course['_id']['name']).course_key
|
||||
for course in courses
|
||||
]
|
||||
|
||||
def _create_new_field_data(self, _category, _location, definition_data, metadata):
|
||||
"""
|
||||
|
||||
@@ -108,8 +108,8 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
|
||||
|
||||
SCHEMA_VERSION = 1
|
||||
reference_type = Locator
|
||||
# a list of field name to store in course index search_targets. Note, this will
|
||||
# only record one value per key. If the draft and published disagree, the last one set wins.
|
||||
# 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.
|
||||
# It won't recompute the value on operations such as update_course_index (e.g., to revert to a prev
|
||||
# version) but those functions will have an optional arg for setting these.
|
||||
SEARCH_TARGET_DICT = ['wiki_slug']
|
||||
@@ -997,6 +997,9 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
|
||||
master_branch: the tag (key) for the version name in the dict which is the DRAFT version. Not the actual
|
||||
version guid, but what to call it.
|
||||
|
||||
search_targets: a dict of search key and value. For example, wiki_slug. Add any fields whose edits
|
||||
should change the search targets to SplitMongoModuleStore.SEARCH_TARGET dict
|
||||
|
||||
versions_dict: the starting version ids where the keys are the tags such as DRAFT and PUBLISHED
|
||||
and the values are structure guids. If provided, the new course will reuse this version (unless you also
|
||||
provide any fields overrides, see above). if not provided, will create a mostly empty course
|
||||
@@ -1887,13 +1890,13 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
|
||||
"""
|
||||
Find all the courses which cached that they have the given field with the given value.
|
||||
|
||||
Returns: list of course_keys
|
||||
Returns: list of branch-agnostic course_keys
|
||||
"""
|
||||
entries = self.db_connection.find_matching_course_indexes(
|
||||
{'search_targets.{}'.format(field_name): field_value}
|
||||
)
|
||||
return [
|
||||
CourseLocator(entry['org'], entry['course'], entry['run']) # which branch? TODO
|
||||
CourseLocator(entry['org'], entry['course'], entry['run']) # Branch agnostic
|
||||
for entry in entries
|
||||
]
|
||||
|
||||
@@ -1901,7 +1904,7 @@ class SplitMongoModuleStore(ModuleStoreWriteBase):
|
||||
"""
|
||||
Return the list of courses which use this wiki_slug
|
||||
:param wiki_slug: the course wiki root slug
|
||||
:return: list of course locations
|
||||
:return: list of course keys
|
||||
"""
|
||||
return self.find_courses_by_search_target('wiki_slug', wiki_slug)
|
||||
|
||||
|
||||
@@ -648,18 +648,21 @@ class TestMixedModuleStore(unittest.TestCase):
|
||||
"""
|
||||
self.initdb(default_ms)
|
||||
# Test XML wikis
|
||||
course_locations = self.store.get_courses_for_wiki('toy')
|
||||
self.assertEqual(len(course_locations), 1)
|
||||
self.assertIn(self.course_locations[self.XML_COURSEID1], course_locations)
|
||||
wiki_courses = self.store.get_courses_for_wiki('toy')
|
||||
self.assertEqual(len(wiki_courses), 1)
|
||||
self.assertIn(self.course_locations[self.XML_COURSEID1].course_key, wiki_courses)
|
||||
|
||||
course_locations = self.store.get_courses_for_wiki('simple')
|
||||
self.assertEqual(len(course_locations), 1)
|
||||
self.assertIn(self.course_locations[self.XML_COURSEID2], course_locations)
|
||||
wiki_courses = self.store.get_courses_for_wiki('simple')
|
||||
self.assertEqual(len(wiki_courses), 1)
|
||||
self.assertIn(self.course_locations[self.XML_COURSEID2].course_key, wiki_courses)
|
||||
|
||||
# Test Mongo wiki
|
||||
course_locations = self.store.get_courses_for_wiki('999')
|
||||
self.assertEqual(len(course_locations), 1)
|
||||
self.assertIn(self.course_locations[self.MONGO_COURSEID], course_locations)
|
||||
wiki_courses = self.store.get_courses_for_wiki('999')
|
||||
self.assertEqual(len(wiki_courses), 1)
|
||||
self.assertIn(
|
||||
self.course_locations[self.MONGO_COURSEID].course_key.replace(branch=None), # Branch agnostic
|
||||
wiki_courses
|
||||
)
|
||||
|
||||
self.assertEqual(len(self.store.get_courses_for_wiki('edX.simple.2012_Fall')), 0)
|
||||
self.assertEqual(len(self.store.get_courses_for_wiki('no_such_wiki')), 0)
|
||||
@@ -744,7 +747,6 @@ class TestMixedModuleStore(unittest.TestCase):
|
||||
self.assertTrue(self.store.has_changes(item.location))
|
||||
self.assertEquals(self.store.compute_publish_state(item), PublishState.draft)
|
||||
|
||||
|
||||
@ddt.data('draft', 'split')
|
||||
def test_get_courses_for_wiki_shared(self, default_ms):
|
||||
"""
|
||||
@@ -753,8 +755,11 @@ class TestMixedModuleStore(unittest.TestCase):
|
||||
self.initdb(default_ms)
|
||||
|
||||
# verify initial state - initially, we should have a wiki for the Mongo course
|
||||
course_locations = self.store.get_courses_for_wiki('999')
|
||||
self.assertEqual([self.course_locations[self.MONGO_COURSEID]], course_locations)
|
||||
wiki_courses = self.store.get_courses_for_wiki('999')
|
||||
self.assertIn(
|
||||
self.course_locations[self.MONGO_COURSEID].course_key.replace(branch=None), # Branch agnostic
|
||||
wiki_courses
|
||||
)
|
||||
|
||||
# set Mongo course to share the wiki with simple course
|
||||
mongo_course = self.store.get_course(self.course_locations[self.MONGO_COURSEID].course_key)
|
||||
@@ -762,23 +767,40 @@ class TestMixedModuleStore(unittest.TestCase):
|
||||
self.store.update_item(mongo_course, self.user_id)
|
||||
|
||||
# now mongo_course should not be retrievable with old wiki_slug
|
||||
course_locations = self.store.get_courses_for_wiki('999')
|
||||
self.assertEqual(len(course_locations), 0)
|
||||
wiki_courses = self.store.get_courses_for_wiki('999')
|
||||
self.assertEqual(len(wiki_courses), 0)
|
||||
|
||||
# but there should be two courses with wiki_slug 'simple'
|
||||
course_locations = self.store.get_courses_for_wiki('simple')
|
||||
self.assertEqual(len(course_locations), 2)
|
||||
for cid in [self.MONGO_COURSEID, self.XML_COURSEID2]:
|
||||
self.assertIn(self.course_locations[cid], course_locations)
|
||||
wiki_courses = self.store.get_courses_for_wiki('simple')
|
||||
self.assertEqual(len(wiki_courses), 2)
|
||||
self.assertIn(
|
||||
self.course_locations[self.MONGO_COURSEID].course_key.replace(branch=None),
|
||||
wiki_courses
|
||||
)
|
||||
self.assertIn(self.course_locations[self.XML_COURSEID2].course_key, wiki_courses)
|
||||
|
||||
# configure mongo course to use unique wiki_slug.
|
||||
mongo_course = self.store.get_course(self.course_locations[self.MONGO_COURSEID].course_key)
|
||||
mongo_course.wiki_slug = 'MITx.999.2013_Spring'
|
||||
self.store.update_item(mongo_course, self.user_id)
|
||||
# it should be retrievable with its new wiki_slug
|
||||
course_locations = self.store.get_courses_for_wiki('MITx.999.2013_Spring')
|
||||
self.assertEqual(len(course_locations), 1)
|
||||
self.assertIn(self.course_locations[self.MONGO_COURSEID], course_locations)
|
||||
wiki_courses = self.store.get_courses_for_wiki('MITx.999.2013_Spring')
|
||||
self.assertEqual(len(wiki_courses), 1)
|
||||
self.assertIn(
|
||||
self.course_locations[self.MONGO_COURSEID].course_key.replace(branch=None),
|
||||
wiki_courses
|
||||
)
|
||||
# and NOT retriveable with its old wiki_slug
|
||||
wiki_courses = self.store.get_courses_for_wiki('simple')
|
||||
self.assertEqual(len(wiki_courses), 1)
|
||||
self.assertNotIn(
|
||||
self.course_locations[self.MONGO_COURSEID].course_key.replace(branch=None),
|
||||
wiki_courses
|
||||
)
|
||||
self.assertIn(
|
||||
self.course_locations[self.XML_COURSEID2].course_key,
|
||||
wiki_courses
|
||||
)
|
||||
|
||||
|
||||
#=============================================================================================================
|
||||
|
||||
@@ -351,7 +351,7 @@ class TestMongoModuleStore(unittest.TestCase):
|
||||
for course_number in self.courses:
|
||||
course_locations = self.draft_store.get_courses_for_wiki(course_number)
|
||||
assert_equals(len(course_locations), 1)
|
||||
assert_equals(Location('edX', course_number, '2012_Fall', 'course', '2012_Fall'), course_locations[0])
|
||||
assert_equals(SlashSeparatedCourseKey('edX', course_number, '2012_Fall'), course_locations[0])
|
||||
|
||||
course_locations = self.draft_store.get_courses_for_wiki('no_such_wiki')
|
||||
assert_equals(len(course_locations), 0)
|
||||
@@ -369,7 +369,7 @@ class TestMongoModuleStore(unittest.TestCase):
|
||||
course_locations = self.draft_store.get_courses_for_wiki('simple')
|
||||
assert_equals(len(course_locations), 2)
|
||||
for course_number in ['toy', 'simple']:
|
||||
assert_in(Location('edX', course_number, '2012_Fall', 'course', '2012_Fall'), course_locations)
|
||||
assert_in(SlashSeparatedCourseKey('edX', course_number, '2012_Fall'), course_locations)
|
||||
|
||||
# configure simple course to use unique wiki_slug.
|
||||
simple_course = self.draft_store.get_course(SlashSeparatedCourseKey('edX', 'simple', '2012_Fall'))
|
||||
@@ -378,7 +378,7 @@ class TestMongoModuleStore(unittest.TestCase):
|
||||
# it should be retrievable with its new wiki_slug
|
||||
course_locations = self.draft_store.get_courses_for_wiki('edX.simple.2012_Fall')
|
||||
assert_equals(len(course_locations), 1)
|
||||
assert_in(Location('edX', 'simple', '2012_Fall', 'course', '2012_Fall'), course_locations)
|
||||
assert_in(SlashSeparatedCourseKey('edX', 'simple', '2012_Fall'), course_locations)
|
||||
|
||||
@Plugin.register_temp_plugin(ReferenceTestXBlock, 'ref_test')
|
||||
def test_reference_converters(self):
|
||||
|
||||
@@ -1395,10 +1395,12 @@ class TestCourseCreation(SplitModuleTest):
|
||||
self.assertEqual(index_info['edited_by'], 'create_user')
|
||||
# check structure info
|
||||
structure_info = modulestore().get_course_history_info(new_locator)
|
||||
# TODO uncomment these lines once bulk updater implemented; right now, these will not
|
||||
# pass because create_course calls update_item resulting in two versions. Bulk updater will fix this.
|
||||
# TODO LMS-11098 "Implement bulk_write in Split"
|
||||
# Right now, these assertions will not pass because create_course calls update_item,
|
||||
# resulting in two versions. Bulk updater will fix this.
|
||||
# self.assertEqual(structure_info['original_version'], index_info['versions'][BRANCH_NAME_DRAFT])
|
||||
# self.assertIsNone(structure_info['previous_version'])
|
||||
|
||||
self.assertEqual(structure_info['edited_by'], 'create_user')
|
||||
# check the returned course object
|
||||
self.assertIsInstance(new_course, CourseDescriptor)
|
||||
|
||||
@@ -81,7 +81,7 @@ class TestXMLModuleStore(unittest.TestCase):
|
||||
for course in store.get_courses():
|
||||
course_locations = store.get_courses_for_wiki(course.wiki_slug)
|
||||
self.assertEqual(len(course_locations), 1)
|
||||
self.assertIn(course.location, course_locations)
|
||||
self.assertIn(course.location.course_key, course_locations)
|
||||
|
||||
course_locations = store.get_courses_for_wiki('no_such_wiki')
|
||||
self.assertEqual(len(course_locations), 0)
|
||||
@@ -96,7 +96,7 @@ class TestXMLModuleStore(unittest.TestCase):
|
||||
course_locations = store.get_courses_for_wiki('simple')
|
||||
self.assertEqual(len(course_locations), 2)
|
||||
for course_number in ['toy', 'simple']:
|
||||
self.assertIn(Location('edX', course_number, '2012_Fall', 'course', '2012_Fall'), course_locations)
|
||||
self.assertIn(SlashSeparatedCourseKey('edX', course_number, '2012_Fall'), course_locations)
|
||||
|
||||
def test_has_course(self):
|
||||
"""
|
||||
|
||||
@@ -815,7 +815,7 @@ class XMLModuleStore(ModuleStoreReadBase):
|
||||
:return: list of course locations
|
||||
"""
|
||||
courses = self.get_courses()
|
||||
return [course.location for course in courses if (course.wiki_slug == wiki_slug)]
|
||||
return [course.location.course_key for course in courses if (course.wiki_slug == wiki_slug)]
|
||||
|
||||
def heartbeat(self):
|
||||
"""
|
||||
|
||||
@@ -27,20 +27,25 @@ def user_is_article_course_staff(user, article):
|
||||
if wiki_slug is None:
|
||||
return False
|
||||
|
||||
modstore = modulestore.django.modulestore()
|
||||
return _has_wiki_staff_access(user, wiki_slug, modstore)
|
||||
|
||||
|
||||
def _has_wiki_staff_access(user, wiki_slug, modstore):
|
||||
"""Returns whether the user has staff access to the wiki represented by wiki_slug"""
|
||||
course_keys = modstore.get_courses_for_wiki(wiki_slug)
|
||||
|
||||
# The wiki expects article slugs to contain at least one non-digit so if
|
||||
# the course number is just a number the course wiki root slug is set to
|
||||
# be '<course_number>_'. This means slug '202_' can belong to either
|
||||
# course numbered '202_' or '202' and so we need to consider both.
|
||||
if wiki_slug.endswith('_') and slug_is_numerical(wiki_slug[:-1]):
|
||||
course_keys.extend(modstore.get_courses_for_wiki(wiki_slug[:-1]))
|
||||
|
||||
courses = modulestore.django.modulestore().get_courses_for_wiki(wiki_slug)
|
||||
if any(courseware.access.has_access(user, 'staff', course, course.course_key) for course in courses):
|
||||
return True
|
||||
|
||||
if (wiki_slug.endswith('_') and slug_is_numerical(wiki_slug[:-1])):
|
||||
courses = modulestore.django.modulestore().get_courses_for_wiki(wiki_slug[:-1])
|
||||
if any(courseware.access.has_access(user, 'staff', course, course.course_key) for course in courses):
|
||||
for course_key in course_keys:
|
||||
course = modstore.get_course(course_key)
|
||||
if courseware.access.has_access(user, 'staff', course, course_key):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user