MixedModulestore wraps most getters, update_item, delete_item

with code to translate between addressing schemes based on app
and persistence layer addressing scheme specification.

STUD-1206
This commit is contained in:
Don Mitchell
2014-01-16 15:41:26 -05:00
parent ea594a2980
commit c601b75675
45 changed files with 633 additions and 560 deletions

View File

@@ -102,7 +102,7 @@ def update_course_updates(location, update, passed_id=None):
# update db record
course_updates.data = html.tostring(course_html_parsed)
modulestore('direct').update_item(location, course_updates.data)
modulestore('direct').update_item(course_updates, 'course_info_model')
return {
"id": idx,
@@ -158,7 +158,7 @@ def delete_course_update(location, update, passed_id):
# update db record
course_updates.data = html.tostring(course_html_parsed)
store = modulestore('direct')
store.update_item(location, course_updates.data)
store.update_item(course_updates, 'course_info_model')
return get_course_updates(location, None)

View File

@@ -54,7 +54,7 @@ class ChecklistTestCase(CourseTestCase):
# Save the changed `checklists` to the underlying KeyValueStore before updating the modulestore
self.course.save()
modulestore = get_modulestore(self.course.location)
modulestore.update_metadata(self.course.location, own_metadata(self.course))
modulestore.update_item(self.course, self.user.pk)
self.assertEqual(self.get_persisted_checklists(), None)
response = self.client.get(self.checklists_url)
self.assertEqual(payload, response.content)

View File

@@ -47,8 +47,6 @@ from xmodule.exceptions import NotFoundError
from django_comment_common.utils import are_permissions_roles_seeded
from xmodule.exceptions import InvalidVersionError
import datetime
from pytz import UTC
from uuid import uuid4
from pymongo import MongoClient
from student.models import CourseEnrollment
@@ -126,11 +124,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
course.advanced_modules = component_types
# Save the data that we've just changed to the underlying
# MongoKeyValueStore before we update the mongo datastore.
course.save()
store.update_metadata(course.location, own_metadata(course))
store.update_item(course, self.user.username)
# just pick one vertical
descriptor = store.get_items(Location('i4x', 'edX', 'simple', 'vertical', None, None))[0]
@@ -269,7 +263,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
self.assertIn('graceperiod', own_metadata(html_module))
self.assertEqual(html_module.graceperiod, new_graceperiod)
draft_store.update_metadata(html_module.location, own_metadata(html_module))
draft_store.update_item(html_module, self.user.username)
# read back to make sure it reads as 'own-metadata'
html_module = draft_store.get_item(Location('i4x', 'edX', 'simple', 'html', 'test_html', None))
@@ -385,8 +379,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
self.assertEqual(course.tabs, expected_tabs)
item.display_name = 'Updated'
item.save()
module_store.update_metadata(item.location, own_metadata(item))
module_store.update_item(item, self.user.username)
course = module_store.get_item(course_location)
@@ -834,9 +827,9 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
html_module = module_store.get_instance(source_location.course_id, html_module_location)
self.assertIsInstance(html_module.data, basestring)
new_data = html_module.data.replace('/static/', '/c4x/{0}/{1}/asset/'.format(
new_data = html_module.data = html_module.data.replace('/static/', '/c4x/{0}/{1}/asset/'.format(
source_location.org, source_location.course))
module_store.update_item(html_module_location, new_data)
module_store.update_item(html_module, None)
html_module = module_store.get_instance(source_location.course_id, html_module_location)
self.assertEqual(new_data, html_module.data)
@@ -858,22 +851,18 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
draft_store = modulestore('draft')
direct_store = modulestore('direct')
CourseFactory.create(org='MITx', course='999', display_name='Robot Super Course')
course = CourseFactory.create(org='MITx', course='999', display_name='Robot Super Course')
location = Location('i4x://MITx/999/chapter/neuvo')
# Ensure draft mongo store does not allow us to create chapters either directly or via convert to draft
self.assertRaises(InvalidVersionError, draft_store.create_and_save_xmodule, location)
direct_store.create_and_save_xmodule(location)
self.assertRaises(InvalidVersionError, draft_store.convert_to_draft, location)
chapter = draft_store.get_instance(course.id, location)
chapter.data = 'chapter data'
self.assertRaises(InvalidVersionError, draft_store.update_item, location, 'chapter data')
# taking advantage of update_children and other functions never checking that the ids are valid
self.assertRaises(InvalidVersionError, draft_store.update_children, location,
['i4x://MITx/999/problem/doesntexist'])
self.assertRaises(InvalidVersionError, draft_store.update_metadata, location,
{'due': datetime.datetime.now(UTC)})
with self.assertRaises(InvalidVersionError):
draft_store.update_item(chapter, 'user')
self.assertRaises(InvalidVersionError, draft_store.unpublish, location)
@@ -992,8 +981,8 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
sequential = module_store.get_item(Location(['i4x', 'edX', 'toy',
'sequential', 'vertical_sequential', None]))
private_location_no_draft = private_vertical.location.replace(revision=None)
module_store.update_children(sequential.location, sequential.children +
[private_location_no_draft.url()])
sequential.children.append(private_location_no_draft.url())
module_store.update_item(sequential, 'user')
# read back the sequential, to make sure we have a pointer to
sequential = module_store.get_item(Location(['i4x', 'edX', 'toy',
@@ -1285,31 +1274,6 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
self.assertFalse(Location(['i4x', 'edX', 'toy', 'vertical', 'vertical_test', None])
in course.system.module_data)
def test_export_course_with_unknown_metadata(self):
module_store = modulestore('direct')
content_store = contentstore()
import_from_xml(module_store, 'common/test/data/', ['toy'])
location = CourseDescriptor.id_to_location('edX/toy/2012_Fall')
root_dir = path(mkdtemp_clean())
course = module_store.get_item(location)
metadata = own_metadata(course)
# add a bool piece of unknown metadata so we can verify we don't throw an exception
metadata['new_metadata'] = True
# Save the data that we've just changed to the underlying
# MongoKeyValueStore before we update the mongo datastore.
course.save()
module_store.update_metadata(location, metadata)
print 'Exporting to tempdir = {0}'.format(root_dir)
# export out to a tempdir
export_to_xml(module_store, content_store, location, root_dir, 'test_export')
def test_export_course_without_content_store(self):
module_store = modulestore('direct')
content_store = contentstore()
@@ -1319,16 +1283,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
import_from_xml(module_store, 'common/test/data/', ['toy'])
location = CourseDescriptor.id_to_location('edX/toy/2012_Fall')
# Add a sequence
stub_location = Location(['i4x', 'edX', 'toy', 'sequential', 'vertical_sequential'])
sequential = module_store.get_item(stub_location)
module_store.update_children(sequential.location, sequential.children)
# Get course and export it without a content_store
course = module_store.get_item(location)
course.save()
root_dir = path(mkdtemp_clean())
@@ -1343,7 +1298,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
module_store, root_dir, ['test_export_no_content_store'],
draft_store=None,
static_content_store=None,
target_location_namespace=course.location
target_location_namespace=location
)
# Verify reimported course
@@ -1810,7 +1765,8 @@ class ContentStoreTest(ModuleStoreTestCase):
# crate a new module and add it as a child to a vertical
module_store.create_and_save_xmodule(new_component_location)
parent = verticals[0]
module_store.update_children(parent.location, parent.children + [new_component_location.url()])
parent.children.append(new_component_location.url())
module_store.update_item(parent, 'user')
# flush the cache
module_store.refresh_cached_metadata_inheritance_tree(new_component_location)
@@ -1827,8 +1783,7 @@ class ContentStoreTest(ModuleStoreTestCase):
# now let's define an override at the leaf node level
#
new_module.graceperiod = timedelta(1)
new_module.save()
module_store.update_metadata(new_module.location, own_metadata(new_module))
module_store.update_item(new_module, self.user.username)
# flush the cache and refetch
module_store.refresh_cached_metadata_inheritance_tree(new_component_location)
@@ -1942,10 +1897,7 @@ class MetadataSaveTestCase(ModuleStoreTestCase):
delattr(self.video_descriptor, field_name)
self.assertNotIn('html5_sources', own_metadata(self.video_descriptor))
get_modulestore(location).update_metadata(
location,
own_metadata(self.video_descriptor)
)
get_modulestore(location).update_item(self.video_descriptor, 'testuser')
module = get_modulestore(location).get_item(location)
self.assertNotIn('html5_sources', own_metadata(module))
@@ -2001,7 +1953,7 @@ def _course_factory_create_course():
Creates a course via the CourseFactory and returns the locator for it.
"""
course = CourseFactory.create(org='MITx', course='999', display_name='Robot Super Course')
return loc_mapper().translate_location(course.location.course_id, course.location, True, True)
return loc_mapper().translate_location(course.id, course.location, False, True)
def _get_course_id(test_course_data):

View File

@@ -123,7 +123,7 @@ class CourseUpdateTest(CourseTestCase):
modulestore('direct').create_and_save_xmodule(location)
course_updates = modulestore('direct').get_item(location)
course_updates.data = 'bad news'
modulestore('direct').update_item(location, course_updates.data)
modulestore('direct').update_item(course_updates, 'test_course_updates')
init_content = '<iframe width="560" height="315" src="http://www.youtube.com/embed/RocY-Jd93XU" frameborder="0">'
content = init_content + '</iframe>'

View File

@@ -4,7 +4,6 @@ Test finding orphans via the view and django config
import json
from contentstore.tests.utils import CourseTestCase
from xmodule.modulestore.django import editable_modulestore, loc_mapper
from django.core.urlresolvers import reverse
from student.models import CourseEnrollment
class TestOrphan(CourseTestCase):
@@ -35,7 +34,7 @@ class TestOrphan(CourseTestCase):
parent_location = self.course.location.replace(category=parent_category, name=parent_name)
parent = editable_modulestore('direct').get_item(parent_location)
parent.children.append(location.url())
editable_modulestore('direct').update_children(parent_location, parent.children)
editable_modulestore('direct').update_item(parent, self.user.pk)
def test_mongo_orphan(self):
"""

View File

@@ -58,11 +58,8 @@ class TextbookIndexTestCase(CourseTestCase):
}
]
self.course.pdf_textbooks = content
# Save the data that we've just changed to the underlying
# MongoKeyValueStore before we update the mongo datastore.
self.course.save()
store = get_modulestore(self.course.location)
store.update_metadata(self.course.location, own_metadata(self.course))
store.update_item(self.course, self.user.pk)
resp = self.client.get(
self.url,
@@ -200,7 +197,7 @@ class TextbookDetailTestCase(CourseTestCase):
# MongoKeyValueStore before we update the mongo datastore.
self.course.save()
self.store = get_modulestore(self.course.location)
self.store.update_metadata(self.course.location, own_metadata(self.course))
self.store.update_item(self.course, self.user.pk)
self.url_nonexist = self.course_locator.url_reverse("textbooks", "20")
def test_get_1(self):

View File

@@ -63,13 +63,13 @@ class Basetranscripts(CourseTestCase):
self.item_locator, self.item_location = self._get_locator(resp)
self.assertEqual(resp.status_code, 200)
self.item = modulestore().get_item(self.item_location)
# hI10vDNYz4M - valid Youtube ID with transcripts.
# JMD_ifUUfsU, AKqURZnYqpk, DYpADpL7jAY - valid Youtube IDs without transcripts.
data = '<video youtube="0.75:JMD_ifUUfsU,1.0:hI10vDNYz4M,1.25:AKqURZnYqpk,1.50:DYpADpL7jAY" />'
modulestore().update_item(self.item_location, data)
self.item.data = '<video youtube="0.75:JMD_ifUUfsU,1.0:hI10vDNYz4M,1.25:AKqURZnYqpk,1.50:DYpADpL7jAY" />'
modulestore().update_item(self.item, 'test_transcripts')
self.item = modulestore().get_item(self.item_location)
# Remove all transcripts for current module.
self.clear_subs_content()
@@ -130,14 +130,14 @@ class TestUploadtranscripts(Basetranscripts):
self.bad_name_srt_file.seek(0)
def test_success_video_module_source_subs_uploading(self):
data = textwrap.dedent("""
self.item.data = textwrap.dedent("""
<video youtube="">
<source src="http://www.quirksmode.org/html5/videos/big_buck_bunny.mp4"/>
<source src="http://www.quirksmode.org/html5/videos/big_buck_bunny.webm"/>
<source src="http://www.quirksmode.org/html5/videos/big_buck_bunny.ogv"/>
</video>
""")
modulestore().update_item(self.item_location, data)
modulestore().update_item(self.item, 'test_transcripts')
link = reverse('upload_transcripts')
filename = os.path.splitext(os.path.basename(self.good_srt_file.name))[0]
@@ -212,8 +212,9 @@ class TestUploadtranscripts(Basetranscripts):
}
resp = self.client.ajax_post('/xblock', data)
item_locator, item_location = self._get_locator(resp)
data = '<non_video youtube="0.75:JMD_ifUUfsU,1.0:hI10vDNYz4M" />'
modulestore().update_item(item_location, data)
item = modulestore().get_item(item_location)
item.data = '<non_video youtube="0.75:JMD_ifUUfsU,1.0:hI10vDNYz4M" />'
modulestore().update_item(item, 'test_transcripts')
# non_video module: testing
@@ -232,8 +233,8 @@ class TestUploadtranscripts(Basetranscripts):
self.assertEqual(json.loads(resp.content).get('status'), 'Transcripts are supported only for "video" modules.')
def test_fail_bad_xml(self):
data = '<<<video youtube="0.75:JMD_ifUUfsU,1.25:AKqURZnYqpk,1.50:DYpADpL7jAY" />'
modulestore().update_item(self.item_location, data)
self.item.data = '<<<video youtube="0.75:JMD_ifUUfsU,1.25:AKqURZnYqpk,1.50:DYpADpL7jAY" />'
modulestore().update_item(self.item, 'test_transcripts')
link = reverse('upload_transcripts')
filename = os.path.splitext(os.path.basename(self.good_srt_file.name))[0]
@@ -344,8 +345,8 @@ class TestDownloadtranscripts(Basetranscripts):
pass
def test_success_download_youtube(self):
data = '<video youtube="1:JMD_ifUUfsU" />'
modulestore().update_item(self.item_location, data)
self.item.data = '<video youtube="1:JMD_ifUUfsU" />'
modulestore().update_item(self.item, 'test_transcripts')
subs = {
'start': [100, 200, 240],
@@ -365,14 +366,14 @@ class TestDownloadtranscripts(Basetranscripts):
def test_success_download_nonyoutube(self):
subs_id = str(uuid4())
data = textwrap.dedent("""
self.item.data = textwrap.dedent("""
<video youtube="" sub="{}">
<source src="http://www.quirksmode.org/html5/videos/big_buck_bunny.mp4"/>
<source src="http://www.quirksmode.org/html5/videos/big_buck_bunny.webm"/>
<source src="http://www.quirksmode.org/html5/videos/big_buck_bunny.ogv"/>
</video>
""".format(subs_id))
modulestore().update_item(self.item_location, data)
modulestore().update_item(self.item, 'test_transcripts')
subs = {
'start': [100, 200, 240],
@@ -424,14 +425,15 @@ class TestDownloadtranscripts(Basetranscripts):
resp = self.client.ajax_post('/xblock', data)
item_locator, item_location = self._get_locator(resp)
subs_id = str(uuid4())
data = textwrap.dedent("""
item = modulestore().get_item(item_location)
item.data = textwrap.dedent("""
<videoalpha youtube="" sub="{}">
<source src="http://www.quirksmode.org/html5/videos/big_buck_bunny.mp4"/>
<source src="http://www.quirksmode.org/html5/videos/big_buck_bunny.webm"/>
<source src="http://www.quirksmode.org/html5/videos/big_buck_bunny.ogv"/>
</videoalpha>
""".format(subs_id))
modulestore().update_item(item_location, data)
modulestore().update_item(item, 'test_transcripts')
subs = {
'start': [100, 200, 240],
@@ -449,28 +451,28 @@ class TestDownloadtranscripts(Basetranscripts):
self.assertEqual(resp.status_code, 404)
def test_fail_nonyoutube_subs_dont_exist(self):
data = textwrap.dedent("""
self.item.data = textwrap.dedent("""
<video youtube="" sub="UNDEFINED">
<source src="http://www.quirksmode.org/html5/videos/big_buck_bunny.mp4"/>
<source src="http://www.quirksmode.org/html5/videos/big_buck_bunny.webm"/>
<source src="http://www.quirksmode.org/html5/videos/big_buck_bunny.ogv"/>
</video>
""")
modulestore().update_item(self.item_location, data)
modulestore().update_item(self.item, 'test_transcripts')
link = reverse('download_transcripts')
resp = self.client.get(link, {'locator': self.item_locator})
self.assertEqual(resp.status_code, 404)
def test_empty_youtube_attr_and_sub_attr(self):
data = textwrap.dedent("""
self.item.data = textwrap.dedent("""
<video youtube="">
<source src="http://www.quirksmode.org/html5/videos/big_buck_bunny.mp4"/>
<source src="http://www.quirksmode.org/html5/videos/big_buck_bunny.webm"/>
<source src="http://www.quirksmode.org/html5/videos/big_buck_bunny.ogv"/>
</video>
""")
modulestore().update_item(self.item_location, data)
modulestore().update_item(self.item, 'test_transcripts')
link = reverse('download_transcripts')
resp = self.client.get(link, {'locator': self.item_locator})
@@ -479,14 +481,14 @@ class TestDownloadtranscripts(Basetranscripts):
def test_fail_bad_sjson_subs(self):
subs_id = str(uuid4())
data = textwrap.dedent("""
self.item.data = textwrap.dedent("""
<video youtube="" sub="{}">
<source src="http://www.quirksmode.org/html5/videos/big_buck_bunny.mp4"/>
<source src="http://www.quirksmode.org/html5/videos/big_buck_bunny.webm"/>
<source src="http://www.quirksmode.org/html5/videos/big_buck_bunny.ogv"/>
</video>
""".format(subs_id))
modulestore().update_item(self.item_location, data)
modulestore().update_item(self.item, 'test_transcripts')
subs = {
'start': [100, 200, 240],
@@ -532,14 +534,14 @@ class TestChecktranscripts(Basetranscripts):
def test_success_download_nonyoutube(self):
subs_id = str(uuid4())
data = textwrap.dedent("""
self.item.data = textwrap.dedent("""
<video youtube="" sub="{}">
<source src="http://www.quirksmode.org/html5/videos/big_buck_bunny.mp4"/>
<source src="http://www.quirksmode.org/html5/videos/big_buck_bunny.webm"/>
<source src="http://www.quirksmode.org/html5/videos/big_buck_bunny.ogv"/>
</video>
""".format(subs_id))
modulestore().update_item(self.item_location, data)
modulestore().update_item(self.item, 'test_transcripts')
subs = {
'start': [100, 200, 240],
@@ -582,8 +584,8 @@ class TestChecktranscripts(Basetranscripts):
transcripts_utils.remove_subs_from_store(subs_id, self.item)
def test_check_youtube(self):
data = '<video youtube="1:JMD_ifUUfsU" />'
modulestore().update_item(self.item_location, data)
self.item.data = '<video youtube="1:JMD_ifUUfsU" />'
modulestore().update_item(self.item, 'test_transcripts')
subs = {
'start': [100, 200, 240],
@@ -674,14 +676,15 @@ class TestChecktranscripts(Basetranscripts):
resp = self.client.ajax_post('/xblock', data)
item_locator, item_location = self._get_locator(resp)
subs_id = str(uuid4())
data = textwrap.dedent("""
item = modulestore().get_item(item_location)
item.data = textwrap.dedent("""
<not_video youtube="" sub="{}">
<source src="http://www.quirksmode.org/html5/videos/big_buck_bunny.mp4"/>
<source src="http://www.quirksmode.org/html5/videos/big_buck_bunny.webm"/>
<source src="http://www.quirksmode.org/html5/videos/big_buck_bunny.ogv"/>
</videoalpha>
""".format(subs_id))
modulestore().update_item(item_location, data)
modulestore().update_item(item, 'test_transcript')
subs = {
'start': [100, 200, 240],

View File

@@ -286,7 +286,7 @@ def save_module(item):
"""
item.save()
store = get_modulestore(Location(item.id))
store.update_metadata(item.id, own_metadata(item))
store.update_item(item, 'save_module')
def copy_or_rename_transcript(new_name, old_name, item, delete_old=False):

View File

@@ -222,16 +222,6 @@ def compute_unit_state(unit):
return UnitState.public
def update_item(location, value):
"""
If value is None, delete the db entry. Otherwise, update it using the correct modulestore.
"""
if value is None:
get_modulestore(location).delete_item(location)
else:
get_modulestore(location).update_item(location, value)
def add_extra_panel_tab(tab_type, course):
"""
Used to add the panel tab to a course if it does not exist.

View File

@@ -51,8 +51,7 @@ def checklists_handler(request, tag=None, package_id=None, branch=None, version_
# from the template.
if not course_module.checklists:
course_module.checklists = CourseDescriptor.checklists.default
course_module.save()
modulestore.update_metadata(old_location, own_metadata(course_module))
modulestore.update_item(course_module, request.user.username)
expanded_checklists = expand_all_action_urls(course_module)
if json_request:
@@ -81,7 +80,7 @@ def checklists_handler(request, tag=None, package_id=None, branch=None, version_
# not default
course_module.checklists = course_module.checklists
course_module.save()
modulestore.update_metadata(old_location, own_metadata(course_module))
modulestore.update_item(course_module, request.user.username)
expanded_checklist = expand_checklist_action_url(course_module, persisted_checklist)
return JsonResponse(expanded_checklist)
else:

View File

@@ -163,8 +163,7 @@ def course_listing(request):
"""
List all courses available to the logged in user
"""
# there's an index on category which will be used if none of its antecedents are set
courses = modulestore('direct').get_items(Location(None, None, None, 'course', None))
courses = modulestore('direct').get_courses()
# filter out courses that we don't have access too
def course_filter(course):
@@ -743,10 +742,7 @@ def textbooks_list_handler(request, tag=None, package_id=None, branch=None, vers
if not any(tab['type'] == 'pdf_textbooks' for tab in course.tabs):
course.tabs.append({"type": "pdf_textbooks"})
course.pdf_textbooks = textbooks
store.update_metadata(
course.location,
own_metadata(course)
)
store.update_item(course, request.user.username)
return JsonResponse(course.pdf_textbooks)
elif request.method == 'POST':
# create a new textbook for the course
@@ -764,7 +760,7 @@ def textbooks_list_handler(request, tag=None, package_id=None, branch=None, vers
tabs = course.tabs
tabs.append({"type": "pdf_textbooks"})
course.tabs = tabs
store.update_metadata(course.location, own_metadata(course))
store.update_item(course, request.user.username)
resp = JsonResponse(textbook, status=201)
resp["Location"] = locator.url_reverse('textbooks', textbook["id"])
return resp
@@ -815,10 +811,7 @@ def textbooks_detail_handler(request, tid, tag=None, package_id=None, branch=Non
course.pdf_textbooks = new_textbooks
else:
course.pdf_textbooks.append(new_textbook)
store.update_metadata(
course.location,
own_metadata(course)
)
store.update_item(course, request.user.username)
return JsonResponse(new_textbook, status=201)
elif request.method == 'DELETE':
if not textbook:
@@ -827,10 +820,7 @@ def textbooks_detail_handler(request, tid, tag=None, package_id=None, branch=Non
new_textbooks = course.pdf_textbooks[0:i]
new_textbooks.extend(course.pdf_textbooks[i + 1:])
course.pdf_textbooks = new_textbooks
store.update_metadata(
course.location,
own_metadata(course)
)
store.update_item(course, request.user.username)
return JsonResponse()

View File

@@ -232,7 +232,8 @@ def _save_item(request, usage_loc, item_location, data=None, children=None, meta
modulestore().convert_to_draft(item_location)
if data:
store.update_item(item_location, data)
# TODO Allow any scope.content fields not just "data" (exactly like the get below this)
existing_item.data = data
else:
data = existing_item.get_explicitly_set_fields_by_scope(Scope.content)
@@ -242,9 +243,9 @@ def _save_item(request, usage_loc, item_location, data=None, children=None, meta
for child_locator
in children
]
store.update_children(item_location, children_ids)
existing_item.children = children_ids
# cdodge: also commit any metadata which might have been passed along
# also commit any metadata which might have been passed along
if nullout is not None or metadata is not None:
# the postback is not the complete metadata, as there's system metadata which is
# not presented to the end-user for editing. So let's use the original (existing_item) and
@@ -269,15 +270,12 @@ def _save_item(request, usage_loc, item_location, data=None, children=None, meta
return JsonResponse({"error": "Invalid data"}, 400)
field.write_to(existing_item, value)
# Save the data that we've just changed to the underlying
# MongoKeyValueStore before we update the mongo datastore.
existing_item.save()
# commit to datastore
store.update_metadata(item_location, own_metadata(existing_item))
if existing_item.category == 'video':
manage_video_subtitles_save(existing_item, existing_item)
# commit to datastore
store.update_item(existing_item, request.user)
result = {
'id': unicode(usage_loc),
'data': data,
@@ -339,7 +337,8 @@ def _create_item(request):
# TODO replace w/ nicer accessor
if not 'detached' in parent.runtime.load_block_type(category)._class_tags:
get_modulestore(parent.location).update_children(parent_location, parent.children + [dest_location.url()])
parent.children.append(dest_location.url())
get_modulestore(parent.location).update_item(parent, request.user)
course_location = loc_mapper().translate_locator_to_location(parent_locator, get_course=True)
locator = loc_mapper().translate_location(course_location.course_id, dest_location, False, True)
@@ -373,13 +372,14 @@ def _duplicate_item(parent_location, duplicate_source_location, display_name=Non
system=source_item.runtime,
)
dest_module = get_modulestore(category).get_item(dest_location)
# Children are not automatically copied over (and not all xblocks have a 'children' attribute).
# Because DAGs are not fully supported, we need to actually duplicate each child as well.
if source_item.has_children:
copied_children = []
dest_module.children = []
for child in source_item.children:
copied_children.append(_duplicate_item(dest_location, Location(child)).url())
get_modulestore(dest_location).update_children(dest_location, copied_children)
dest_module.children.append(_duplicate_item(dest_location, Location(child)).url())
get_modulestore(dest_location).update_item(dest_module, 'user')
if not 'detached' in source_item.runtime.load_block_type(category)._class_tags:
parent = get_modulestore(parent_location).get_item(parent_location)
@@ -390,7 +390,7 @@ def _duplicate_item(parent_location, duplicate_source_location, display_name=Non
parent.children.insert(source_index + 1, dest_location.url())
else:
parent.children.append(dest_location.url())
get_modulestore(parent_location).update_children(parent_location, parent.children)
get_modulestore(parent_location).update_item(parent, 'user')
return dest_location
@@ -406,22 +406,19 @@ def _delete_item_at_location(item_location, delete_children=False, delete_all_ve
item = store.get_item(item_location)
if delete_children:
_xmodule_recurse(item, lambda i: store.delete_item(i.location, delete_all_versions))
_xmodule_recurse(item, lambda i: store.delete_item(i.location, delete_all_versions=delete_all_versions))
else:
store.delete_item(item.location, delete_all_versions)
store.delete_item(item.location, delete_all_versions=delete_all_versions)
# cdodge: we need to remove our parent's pointer to us so that it is no longer dangling
if delete_all_versions:
parent_locs = modulestore('direct').get_parent_locations(item_location, None)
item_url = item_location.url()
for parent_loc in parent_locs:
parent = modulestore('direct').get_item(parent_loc)
item_url = item_location.url()
if item_url in parent.children:
children = parent.children
children.remove(item_url)
parent.children = children
modulestore('direct').update_children(parent.location, parent.children)
parent.children.remove(item_url)
modulestore('direct').update_item(parent, 'user')
return JsonResponse()
@@ -452,7 +449,7 @@ def orphan_handler(request, tag=None, package_id=None, branch=None, version_guid
if request.user.is_staff:
items = modulestore().get_orphans(old_location, 'draft')
for item in items:
modulestore('draft').delete_item(item, True)
modulestore('draft').delete_item(item, delete_all_versions=True)
return JsonResponse({'deleted': items})
else:
raise PermissionDenied()

View File

@@ -47,7 +47,7 @@ def initialize_course_tabs(course):
{"type": "progress", "name": _("Progress")},
]
modulestore('direct').update_metadata(course.location.url(), own_metadata(course))
modulestore('direct').update_item(course, 'system')
@expect_json
@login_required
@@ -123,7 +123,7 @@ def tabs_handler(request, tag=None, package_id=None, branch=None, version_guid=N
# OK, re-assemble the static tabs in the new order
course_item.tabs = reordered_tabs
modulestore('direct').update_metadata(course_item.location, own_metadata(course_item))
modulestore('direct').update_item(course_item, request.user.username)
return JsonResponse()
else:
raise NotImplementedError('Creating or changing tab content is not supported.')
@@ -179,7 +179,7 @@ def primitive_delete(course, num):
# Note for future implementations: if you delete a static_tab, then Chris Dodge
# points out that there's other stuff to delete beyond this element.
# This code happens to not delete static_tab so it doesn't come up.
modulestore('direct').update_metadata(course.location, own_metadata(course))
modulestore('direct').update_item(course, 'primitive delete')
def primitive_insert(course, num, tab_type, name):
@@ -188,5 +188,5 @@ def primitive_insert(course, num, tab_type, name):
new_tab = {u'type': unicode(tab_type), u'name': unicode(name)}
tabs = course.tabs
tabs.insert(num, new_tab)
modulestore('direct').update_metadata(course.location, own_metadata(course))
modulestore('direct').update_item(course, 'primitive insert')

View File

@@ -6,10 +6,8 @@ from json.encoder import JSONEncoder
from xmodule.modulestore import Location
from xmodule.modulestore.exceptions import ItemNotFoundError
from xmodule.modulestore.inheritance import own_metadata
from contentstore.utils import get_modulestore, course_image_url
from models.settings import course_grading
from contentstore.utils import update_item
from xmodule.fields import Date
from xmodule.modulestore.django import loc_mapper
@@ -74,6 +72,24 @@ class CourseDetails(object):
return course
@classmethod
def update_about_item(cls, course_old_location, about_key, data, course):
"""
Update the about item with the new data blob. If data is None, then
delete the about item.
"""
temploc = Location(course_old_location).replace(category='about', name=about_key)
store = get_modulestore(temploc)
if data is None:
store.delete_item(temploc)
else:
try:
about_item = store.get_item(temploc)
except ItemNotFoundError:
about_item = store.create_xmodule(temploc, system=course.runtime)
about_item.data = data
store.update_item(about_item, 'update_about_item')
@classmethod
def update_from_json(cls, course_locator, jsondict):
"""
@@ -130,26 +146,15 @@ class CourseDetails(object):
dirty = True
if dirty:
# Save the data that we've just changed to the underlying
# MongoKeyValueStore before we update the mongo datastore.
descriptor.save()
get_modulestore(course_old_location).update_metadata(course_old_location, own_metadata(descriptor))
get_modulestore(course_old_location).update_item(descriptor, 'course_details')
# NOTE: below auto writes to the db w/o verifying that any of the fields actually changed
# to make faster, could compare against db or could have client send over a list of which fields changed.
temploc = Location(course_old_location).replace(category='about', name='syllabus')
update_item(temploc, jsondict['syllabus'])
for about_type in ['syllabus', 'overview', 'effort']:
cls.update_about_item(course_old_location, about_type, jsondict[about_type], descriptor)
temploc = temploc.replace(name='overview')
update_item(temploc, jsondict['overview'])
temploc = temploc.replace(name='effort')
update_item(temploc, jsondict['effort'])
temploc = temploc.replace(name='video')
recomposed_video_tag = CourseDetails.recompose_video_tag(jsondict['intro_video'])
update_item(temploc, recomposed_video_tag)
cls.update_about_item(course_old_location, 'video', recomposed_video_tag, descriptor)
# Could just return jsondict w/o doing any db reads, but I put the reads in as a means to confirm
# it persisted correctly

View File

@@ -65,9 +65,7 @@ class CourseGradingModel(object):
descriptor.raw_grader = graders_parsed
descriptor.grade_cutoffs = jsondict['grade_cutoffs']
get_modulestore(course_old_location).update_item(
course_old_location, descriptor.get_explicitly_set_fields_by_scope(Scope.content)
)
get_modulestore(course_old_location).update_item(descriptor, 'course_grading')
CourseGradingModel.update_grace_period_from_json(course_locator, jsondict['grace_period'])
@@ -91,9 +89,7 @@ class CourseGradingModel(object):
else:
descriptor.raw_grader.append(grader)
get_modulestore(course_old_location).update_item(
course_old_location, descriptor.get_explicitly_set_fields_by_scope(Scope.content)
)
get_modulestore(course_old_location).update_item(descriptor, 'course_grading')
return CourseGradingModel.jsonize_grader(index, descriptor.raw_grader[index])
@@ -107,9 +103,7 @@ class CourseGradingModel(object):
descriptor = get_modulestore(course_old_location).get_item(course_old_location)
descriptor.grade_cutoffs = cutoffs
get_modulestore(course_old_location).update_item(
course_old_location, descriptor.get_explicitly_set_fields_by_scope(Scope.content)
)
get_modulestore(course_old_location).update_item(descriptor, 'course_grading')
return cutoffs
@@ -132,9 +126,7 @@ class CourseGradingModel(object):
grace_timedelta = timedelta(**graceperiodjson)
descriptor.graceperiod = grace_timedelta
get_modulestore(course_old_location).update_metadata(
course_old_location, descriptor.get_explicitly_set_fields_by_scope(Scope.settings)
)
get_modulestore(course_old_location).update_item(descriptor, 'update_grace_period')
@staticmethod
def delete_grader(course_location, index):
@@ -150,9 +142,7 @@ class CourseGradingModel(object):
# force propagation to definition
descriptor.raw_grader = descriptor.raw_grader
get_modulestore(course_old_location).update_item(
course_old_location, descriptor.get_explicitly_set_fields_by_scope(Scope.content)
)
get_modulestore(course_old_location).update_item(descriptor, 'delete_grader')
@staticmethod
def delete_grace_period(course_location):
@@ -164,9 +154,7 @@ class CourseGradingModel(object):
del descriptor.graceperiod
get_modulestore(course_old_location).update_metadata(
course_old_location, descriptor.get_explicitly_set_fields_by_scope(Scope.settings)
)
get_modulestore(course_old_location).update_item(descriptor, 'delete_grace_period')
@staticmethod
def get_section_grader_type(location):
@@ -186,9 +174,7 @@ class CourseGradingModel(object):
del descriptor.format
del descriptor.graded
get_modulestore(descriptor.location).update_metadata(
descriptor.location, descriptor.get_explicitly_set_fields_by_scope(Scope.settings)
)
get_modulestore(descriptor.location).update_item(descriptor, 'update_grader')
return {'graderType': grader_type}
@staticmethod

View File

@@ -1,7 +1,6 @@
from xblock.fields import Scope
from contentstore.utils import get_modulestore
from xmodule.modulestore.inheritance import own_metadata
from cms.lib.xblock.mixin import CmsBlockMixin
@@ -78,6 +77,6 @@ class CourseMetadata(object):
setattr(descriptor, key, value)
if dirty:
get_modulestore(descriptor.location).update_metadata(descriptor.location, own_metadata(descriptor))
get_modulestore(descriptor.location).update_item(descriptor, 'update_settings')
return cls.fetch(descriptor)