Commit 0e8b303d by Calen Pennington

Cache CcxFieldOverrides on a per-request basis

parent 63ddbd9b
...@@ -84,35 +84,40 @@ def get_override_for_ccx(ccx, block, name, default=None): ...@@ -84,35 +84,40 @@ def get_override_for_ccx(ccx, block, name, default=None):
specify the block and the name of the field. If the field is not specify the block and the name of the field. If the field is not
overridden for the given ccx, returns `default`. overridden for the given ccx, returns `default`.
""" """
if not hasattr(block, '_ccx_overrides'): overrides = _get_overrides_for_ccx(ccx)
block._ccx_overrides = {} # pylint: disable=protected-access
overrides = block._ccx_overrides.get(ccx.id) # pylint: disable=protected-access if isinstance(block.location, CCXBlockUsageLocator):
if overrides is None: non_ccx_key = block.location.to_block_locator()
overrides = _get_overrides_for_ccx(ccx, block) else:
block._ccx_overrides[ccx.id] = overrides # pylint: disable=protected-access non_ccx_key = block.location
return overrides.get(name, default)
block_overrides = overrides.get(non_ccx_key, {})
if name in block_overrides:
return block.fields[name].from_json(block_overrides[name])
else:
return default
def _get_overrides_for_ccx(ccx, block): def _get_overrides_for_ccx(ccx):
""" """
Returns a dictionary mapping field name to overriden value for any Returns a dictionary mapping field name to overriden value for any
overrides set on this block for this CCX. overrides set on this block for this CCX.
""" """
overrides = {} overrides_cache = request_cache.get_cache('ccx-overrides')
# block as passed in may have a location specific to a CCX, we must strip
# that for this query if ccx not in overrides_cache:
location = block.location overrides = {}
if isinstance(block.location, CCXBlockUsageLocator): query = CcxFieldOverride.objects.filter(
location = block.location.to_block_locator() ccx=ccx,
query = CcxFieldOverride.objects.filter( )
ccx=ccx,
location=location for override in query:
) block_overrides = overrides.setdefault(override.location, {})
for override in query: block_overrides[override.field] = json.loads(override.value)
field = block.fields[override.field]
value = field.from_json(json.loads(override.value)) overrides_cache[ccx] = overrides
overrides[override.field] = value
return overrides return overrides_cache[ccx]
@transaction.commit_on_success @transaction.commit_on_success
...@@ -123,23 +128,25 @@ def override_field_for_ccx(ccx, block, name, value): ...@@ -123,23 +128,25 @@ def override_field_for_ccx(ccx, block, name, value):
value to set for the given field. value to set for the given field.
""" """
field = block.fields[name] field = block.fields[name]
value = json.dumps(field.to_json(value)) value_json = field.to_json(value)
serialized_value = json.dumps(value_json)
try: try:
override = CcxFieldOverride.objects.create( override = CcxFieldOverride.objects.create(
ccx=ccx, ccx=ccx,
location=block.location, location=block.location,
field=name, field=name,
value=value) value=serialized_value
)
except IntegrityError: except IntegrityError:
transaction.commit() transaction.commit()
override = CcxFieldOverride.objects.get( override = CcxFieldOverride.objects.get(
ccx=ccx, ccx=ccx,
location=block.location, location=block.location,
field=name) field=name
override.value = value )
override.value = serialized_value
override.save() override.save()
if hasattr(block, '_ccx_overrides'): _get_overrides_for_ccx(ccx).setdefault(block.location, {})[name] = value_json
del block._ccx_overrides[ccx.id] # pylint: disable=protected-access
def clear_override_for_ccx(ccx, block, name): def clear_override_for_ccx(ccx, block, name):
...@@ -155,8 +162,7 @@ def clear_override_for_ccx(ccx, block, name): ...@@ -155,8 +162,7 @@ def clear_override_for_ccx(ccx, block, name):
location=block.location, location=block.location,
field=name).delete() field=name).delete()
if hasattr(block, '_ccx_overrides'): _get_overrides_for_ccx(ccx).setdefault(block.location, {}).pop(name)
del block._ccx_overrides[ccx.id] # pylint: disable=protected-access
except CcxFieldOverride.DoesNotExist: except CcxFieldOverride.DoesNotExist:
pass pass
...@@ -250,9 +250,9 @@ class TestFieldOverrideSplitPerformance(FieldOverridePerformanceTestCase): ...@@ -250,9 +250,9 @@ class TestFieldOverrideSplitPerformance(FieldOverridePerformanceTestCase):
('ccx', 1, True, False): (23, 4, 9), ('ccx', 1, True, False): (23, 4, 9),
('ccx', 2, True, False): (68, 19, 54), ('ccx', 2, True, False): (68, 19, 54),
('ccx', 3, True, False): (263, 84, 215), ('ccx', 3, True, False): (263, 84, 215),
('ccx', 1, True, True): (33, 4, 13), ('ccx', 1, True, True): (25, 4, 13),
('ccx', 2, True, True): (68, 19, 84), ('ccx', 2, True, True): (70, 19, 84),
('ccx', 3, True, True): (263, 84, 335), ('ccx', 3, True, True): (265, 84, 335),
('no_overrides', 1, False, False): (23, 4, 9), ('no_overrides', 1, False, False): (23, 4, 9),
('no_overrides', 2, False, False): (68, 19, 54), ('no_overrides', 2, False, False): (68, 19, 54),
('no_overrides', 3, False, False): (263, 84, 215), ('no_overrides', 3, False, False): (263, 84, 215),
......
...@@ -100,7 +100,7 @@ class TestFieldOverrides(ModuleStoreTestCase): ...@@ -100,7 +100,7 @@ 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(4): with self.assertNumQueries(3):
override_field_for_ccx(self.ccx, chapter, 'start', ccx_start) override_field_for_ccx(self.ccx, chapter, 'start', ccx_start)
dummy = chapter.start dummy = chapter.start
...@@ -110,7 +110,7 @@ class TestFieldOverrides(ModuleStoreTestCase): ...@@ -110,7 +110,7 @@ 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(4): with self.assertNumQueries(3):
override_field_for_ccx(self.ccx, chapter, 'start', ccx_start) override_field_for_ccx(self.ccx, chapter, 'start', ccx_start)
dummy1 = chapter.start dummy1 = chapter.start
dummy2 = chapter.start dummy2 = chapter.start
......
...@@ -21,6 +21,7 @@ from django.test.utils import override_settings ...@@ -21,6 +21,7 @@ from django.test.utils import override_settings
from django.test import RequestFactory from django.test import RequestFactory
from edxmako.shortcuts import render_to_response # pylint: disable=import-error from edxmako.shortcuts import render_to_response # pylint: disable=import-error
from student.models import CourseEnrollment from student.models import CourseEnrollment
from request_cache.middleware import RequestCache
from student.roles import CourseCcxCoachRole # pylint: disable=import-error from student.roles import CourseCcxCoachRole # pylint: disable=import-error
from student.tests.factories import ( # pylint: disable=import-error from student.tests.factories import ( # pylint: disable=import-error
AdminFactory, AdminFactory,
...@@ -503,26 +504,6 @@ class TestCCXGrades(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -503,26 +504,6 @@ class TestCCXGrades(ModuleStoreTestCase, LoginEnrollmentTestCase):
role.add_users(coach) role.add_users(coach)
ccx = CcxFactory(course_id=course.id, coach=self.coach) ccx = CcxFactory(course_id=course.id, coach=self.coach)
# Apparently the test harness doesn't use LmsFieldStorage, and I'm not
# sure if there's a way to poke the test harness to do so. So, we'll
# just inject the override field storage in this brute force manner.
OverrideFieldData.provider_classes = None
# pylint: disable=protected-access
for block in iter_blocks(course):
block._field_data = OverrideFieldData.wrap(coach, course, block._field_data)
new_cache = {'tabs': [], 'discussion_topics': []}
if 'grading_policy' in block._field_data_cache:
new_cache['grading_policy'] = block._field_data_cache['grading_policy']
block._field_data_cache = new_cache
def cleanup_provider_classes():
"""
After everything is done, clean up by un-doing the change to the
OverrideFieldData object that is done during the wrap method.
"""
OverrideFieldData.provider_classes = None
self.addCleanup(cleanup_provider_classes)
# override course grading policy and make last section invisible to students # override course grading policy and make last section invisible to students
override_field_for_ccx(ccx, course, 'grading_policy', { override_field_for_ccx(ccx, course, 'grading_policy', {
'GRADER': [ 'GRADER': [
...@@ -561,9 +542,13 @@ class TestCCXGrades(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -561,9 +542,13 @@ class TestCCXGrades(ModuleStoreTestCase, LoginEnrollmentTestCase):
self.client.login(username=coach.username, password="test") self.client.login(username=coach.username, password="test")
self.addCleanup(RequestCache.clear_request_cache)
@patch('ccx.views.render_to_response', intercept_renderer) @patch('ccx.views.render_to_response', intercept_renderer)
def test_gradebook(self): def test_gradebook(self):
self.course.enable_ccx = True self.course.enable_ccx = True
RequestCache.clear_request_cache()
url = reverse( url = reverse(
'ccx_gradebook', 'ccx_gradebook',
kwargs={'course_id': self.ccx_key} kwargs={'course_id': self.ccx_key}
...@@ -580,6 +565,8 @@ class TestCCXGrades(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -580,6 +565,8 @@ class TestCCXGrades(ModuleStoreTestCase, LoginEnrollmentTestCase):
def test_grades_csv(self): def test_grades_csv(self):
self.course.enable_ccx = True self.course.enable_ccx = True
RequestCache.clear_request_cache()
url = reverse( url = reverse(
'ccx_grades_csv', 'ccx_grades_csv',
kwargs={'course_id': self.ccx_key} kwargs={'course_id': self.ccx_key}
...@@ -591,11 +578,11 @@ class TestCCXGrades(ModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -591,11 +578,11 @@ class TestCCXGrades(ModuleStoreTestCase, LoginEnrollmentTestCase):
response.content.strip().split('\n') response.content.strip().split('\n')
) )
data = dict(zip(headers, row)) data = dict(zip(headers, row))
self.assertTrue('HW 04' not in data)
self.assertEqual(data['HW 01'], '0.75') self.assertEqual(data['HW 01'], '0.75')
self.assertEqual(data['HW 02'], '0.5') self.assertEqual(data['HW 02'], '0.5')
self.assertEqual(data['HW 03'], '0.25') self.assertEqual(data['HW 03'], '0.25')
self.assertEqual(data['HW Avg'], '0.5') self.assertEqual(data['HW Avg'], '0.5')
self.assertTrue('HW 04' not in data)
@patch('courseware.views.render_to_response', intercept_renderer) @patch('courseware.views.render_to_response', intercept_renderer)
def test_student_progress(self): def test_student_progress(self):
......
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