Commit 49dfee0e by Syed Hassan Raza

save_cxx slow transaction

parent c2197608
...@@ -93,7 +93,10 @@ def get_override_for_ccx(ccx, block, name, default=None): ...@@ -93,7 +93,10 @@ def get_override_for_ccx(ccx, block, name, default=None):
block_overrides = overrides.get(non_ccx_key, {}) block_overrides = overrides.get(non_ccx_key, {})
if name in block_overrides: if name in block_overrides:
return block.fields[name].from_json(block_overrides[name]) try:
return block.fields[name].from_json(block_overrides[name])
except KeyError:
return block_overrides[name]
else: else:
return default return default
...@@ -114,6 +117,8 @@ def _get_overrides_for_ccx(ccx): ...@@ -114,6 +117,8 @@ def _get_overrides_for_ccx(ccx):
for override in query: for override in query:
block_overrides = overrides.setdefault(override.location, {}) block_overrides = overrides.setdefault(override.location, {})
block_overrides[override.field] = json.loads(override.value) block_overrides[override.field] = json.loads(override.value)
block_overrides[override.field + "_id"] = override.id
block_overrides[override.field + "_instance"] = override
overrides_cache[ccx] = overrides overrides_cache[ccx] = overrides
...@@ -130,23 +135,33 @@ def override_field_for_ccx(ccx, block, name, value): ...@@ -130,23 +135,33 @@ def override_field_for_ccx(ccx, block, name, value):
field = block.fields[name] field = block.fields[name]
value_json = field.to_json(value) value_json = field.to_json(value)
serialized_value = json.dumps(value_json) serialized_value = json.dumps(value_json)
try: override_has_changes = False
override = CcxFieldOverride.objects.create(
ccx=ccx, override = get_override_for_ccx(ccx, block, name + "_instance")
location=block.location, if override:
field=name, override_has_changes = serialized_value != override.value
value=serialized_value
) if not override:
except IntegrityError: try:
transaction.commit() override = CcxFieldOverride.objects.create(
override = CcxFieldOverride.objects.get( ccx=ccx,
ccx=ccx, location=block.location,
location=block.location, field=name,
field=name value=serialized_value
) )
_get_overrides_for_ccx(ccx).setdefault(block.location, {})[name + "_id"] = override.id
except IntegrityError:
transaction.commit()
kwargs = {'ccx': ccx, 'location': block.location, 'field': name}
override = CcxFieldOverride.objects.get(**kwargs)
override_has_changes = serialized_value != override.value
if override_has_changes:
override.value = serialized_value override.value = serialized_value
override.save() override.save()
_get_overrides_for_ccx(ccx).setdefault(block.location, {})[name] = value_json _get_overrides_for_ccx(ccx).setdefault(block.location, {})[name] = value_json
_get_overrides_for_ccx(ccx).setdefault(block.location, {})[name + "_instance"] = override
def clear_override_for_ccx(ccx, block, name): def clear_override_for_ccx(ccx, block, name):
...@@ -162,7 +177,30 @@ def clear_override_for_ccx(ccx, block, name): ...@@ -162,7 +177,30 @@ def clear_override_for_ccx(ccx, block, name):
location=block.location, location=block.location,
field=name).delete() field=name).delete()
_get_overrides_for_ccx(ccx).setdefault(block.location, {}).pop(name) clear_ccx_field_info_from_ccx_map(ccx, block, name)
except CcxFieldOverride.DoesNotExist: except CcxFieldOverride.DoesNotExist:
pass pass
def clear_ccx_field_info_from_ccx_map(ccx, block, name): # pylint: disable=invalid-name
"""
Remove field information from ccx overrides mapping dictionary
"""
try:
ccx_override_map = _get_overrides_for_ccx(ccx).setdefault(block.location, {})
ccx_override_map.pop(name)
ccx_override_map.pop(name + "_id")
ccx_override_map.pop(name + "_instance")
except KeyError:
pass
def bulk_delete_ccx_override_fields(ccx, ids):
"""
Bulk delete for CcxFieldOverride model
"""
ids = filter(None, ids)
ids = list(set(ids))
if ids:
CcxFieldOverride.objects.filter(ccx=ccx, id__in=ids).delete()
...@@ -94,15 +94,35 @@ class TestFieldOverrides(ModuleStoreTestCase): ...@@ -94,15 +94,35 @@ class TestFieldOverrides(ModuleStoreTestCase):
override_field_for_ccx(self.ccx, chapter, 'start', ccx_start) override_field_for_ccx(self.ccx, chapter, 'start', ccx_start)
self.assertEquals(chapter.start, ccx_start) self.assertEquals(chapter.start, ccx_start)
def test_override_num_queries(self): def test_override_num_queries_new_field(self):
""" """
Test that overriding and accessing a field produce same number of queries. Test that for creating new field executed only create query
""" """
ccx_start = datetime.datetime(2014, 12, 25, 00, 00, tzinfo=pytz.UTC) ccx_start = datetime.datetime(2014, 12, 25, 00, 00, tzinfo=pytz.UTC)
chapter = self.ccx.course.get_children()[0] chapter = self.ccx.course.get_children()[0]
with self.assertNumQueries(3): with self.assertNumQueries(1):
override_field_for_ccx(self.ccx, chapter, 'start', ccx_start)
def test_override_num_queries_update_existing_field(self):
"""
Test that overriding existing field executed create, fetch and update queries.
"""
ccx_start = datetime.datetime(2014, 12, 25, 00, 00, tzinfo=pytz.UTC)
new_ccx_start = datetime.datetime(2015, 12, 25, 00, 00, tzinfo=pytz.UTC)
chapter = self.ccx.course.get_children()[0]
override_field_for_ccx(self.ccx, chapter, 'start', ccx_start)
with self.assertNumQueries(2):
override_field_for_ccx(self.ccx, chapter, 'start', new_ccx_start)
def test_override_num_queries_field_value_not_changed(self):
"""
Test that if value of field does not changed no query execute.
"""
ccx_start = datetime.datetime(2014, 12, 25, 00, 00, tzinfo=pytz.UTC)
chapter = self.ccx.course.get_children()[0]
override_field_for_ccx(self.ccx, chapter, 'start', ccx_start)
with self.assertNumQueries(0):
override_field_for_ccx(self.ccx, chapter, 'start', ccx_start) override_field_for_ccx(self.ccx, chapter, 'start', ccx_start)
dummy = chapter.start
def test_overriden_field_access_produces_no_extra_queries(self): def test_overriden_field_access_produces_no_extra_queries(self):
""" """
...@@ -110,11 +130,8 @@ class TestFieldOverrides(ModuleStoreTestCase): ...@@ -110,11 +130,8 @@ class TestFieldOverrides(ModuleStoreTestCase):
""" """
ccx_start = datetime.datetime(2014, 12, 25, 00, 00, tzinfo=pytz.UTC) ccx_start = datetime.datetime(2014, 12, 25, 00, 00, tzinfo=pytz.UTC)
chapter = self.ccx.course.get_children()[0] chapter = self.ccx.course.get_children()[0]
with self.assertNumQueries(3): with self.assertNumQueries(1):
override_field_for_ccx(self.ccx, chapter, 'start', ccx_start) override_field_for_ccx(self.ccx, chapter, 'start', ccx_start)
dummy1 = chapter.start
dummy2 = chapter.start
dummy3 = chapter.start
def test_override_is_inherited(self): def test_override_is_inherited(self):
""" """
......
...@@ -53,6 +53,8 @@ from .overrides import ( ...@@ -53,6 +53,8 @@ from .overrides import (
clear_override_for_ccx, clear_override_for_ccx,
get_override_for_ccx, get_override_for_ccx,
override_field_for_ccx, override_field_for_ccx,
clear_ccx_field_info_from_ccx_map,
bulk_delete_ccx_override_fields,
) )
...@@ -196,42 +198,50 @@ def save_ccx(request, course, ccx=None): ...@@ -196,42 +198,50 @@ def save_ccx(request, course, ccx=None):
if not ccx: if not ccx:
raise Http404 raise Http404
def override_fields(parent, data, graded, earliest=None): def override_fields(parent, data, graded, earliest=None, ccx_ids_to_delete=None):
""" """
Recursively apply CCX schedule data to CCX by overriding the Recursively apply CCX schedule data to CCX by overriding the
`visible_to_staff_only`, `start` and `due` fields for units in the `visible_to_staff_only`, `start` and `due` fields for units in the
course. course.
""" """
if ccx_ids_to_delete is None:
ccx_ids_to_delete = []
blocks = { blocks = {
str(child.location): child str(child.location): child
for child in parent.get_children()} for child in parent.get_children()}
for unit in data: for unit in data:
block = blocks[unit['location']] block = blocks[unit['location']]
override_field_for_ccx( override_field_for_ccx(
ccx, block, 'visible_to_staff_only', unit['hidden']) ccx, block, 'visible_to_staff_only', unit['hidden'])
start = parse_date(unit['start']) start = parse_date(unit['start'])
if start: if start:
if not earliest or start < earliest: if not earliest or start < earliest:
earliest = start earliest = start
override_field_for_ccx(ccx, block, 'start', start) override_field_for_ccx(ccx, block, 'start', start)
else: else:
clear_override_for_ccx(ccx, block, 'start') ccx_ids_to_delete.append(get_override_for_ccx(ccx, block, 'start_id'))
clear_ccx_field_info_from_ccx_map(ccx, block, 'start')
due = parse_date(unit['due']) due = parse_date(unit['due'])
if due: if due:
override_field_for_ccx(ccx, block, 'due', due) override_field_for_ccx(ccx, block, 'due', due)
else: else:
clear_override_for_ccx(ccx, block, 'due') ccx_ids_to_delete.append(get_override_for_ccx(ccx, block, 'due_id'))
clear_ccx_field_info_from_ccx_map(ccx, block, 'due')
if not unit['hidden'] and block.graded: if not unit['hidden'] and block.graded:
graded[block.format] = graded.get(block.format, 0) + 1 graded[block.format] = graded.get(block.format, 0) + 1
children = unit.get('children', None) children = unit.get('children', None)
if children: if children:
override_fields(block, children, graded, earliest) override_fields(block, children, graded, earliest, ccx_ids_to_delete)
return earliest return earliest, ccx_ids_to_delete
graded = {} graded = {}
earliest = override_fields(course, json.loads(request.body), graded) earliest, ccx_ids_to_delete = override_fields(course, json.loads(request.body), graded, [])
bulk_delete_ccx_override_fields(ccx, ccx_ids_to_delete)
if earliest: if earliest:
override_field_for_ccx(ccx, course, 'start', earliest) override_field_for_ccx(ccx, course, 'start', earliest)
......
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