Commit d0fcabd4 by Nimisha Asthagiri

CCX: Use Modulestore Field Overrides instead of User-specific Overrides

parent 74a36652
...@@ -50,12 +50,11 @@ class CustomCoursesForEdxOverrideProvider(FieldOverrideProvider): ...@@ -50,12 +50,11 @@ class CustomCoursesForEdxOverrideProvider(FieldOverrideProvider):
return default return default
@classmethod @classmethod
def enabled_for(cls, course): def enabled_for(cls, block):
"""CCX field overrides are enabled per-course """
CCX field overrides are enabled for CCX blocks.
protect against missing attributes
""" """
return getattr(course, 'enable_ccx', False) return getattr(block.location, 'ccx', None) or getattr(block, 'enable_ccx', False)
def get_current_ccx(course_key): def get_current_ccx(course_key):
...@@ -86,12 +85,9 @@ def get_override_for_ccx(ccx, block, name, default=None): ...@@ -86,12 +85,9 @@ def get_override_for_ccx(ccx, block, name, default=None):
""" """
overrides = _get_overrides_for_ccx(ccx) overrides = _get_overrides_for_ccx(ccx)
if isinstance(block.location, CCXBlockUsageLocator): clean_ccx_key = _clean_ccx_key(block.location)
non_ccx_key = block.location.to_block_locator()
else:
non_ccx_key = block.location
block_overrides = overrides.get(non_ccx_key, {}) block_overrides = overrides.get(clean_ccx_key, {})
if name in block_overrides: if name in block_overrides:
try: try:
return block.fields[name].from_json(block_overrides[name]) return block.fields[name].from_json(block_overrides[name])
...@@ -101,6 +97,21 @@ def get_override_for_ccx(ccx, block, name, default=None): ...@@ -101,6 +97,21 @@ def get_override_for_ccx(ccx, block, name, default=None):
return default return default
def _clean_ccx_key(block_location):
"""
Converts the given BlockUsageKey from a CCX key to the
corresponding key for its parent course, while handling the case
where no conversion is needed. Also strips any version and
branch information from the key.
Returns the cleaned key.
"""
if isinstance(block_location, CCXBlockUsageLocator):
clean_key = block_location.to_block_locator()
else:
clean_key = block_location
return clean_key.version_agnostic().for_branch(None)
def _get_overrides_for_ccx(ccx): 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
...@@ -136,6 +147,7 @@ def override_field_for_ccx(ccx, block, name, value): ...@@ -136,6 +147,7 @@ def override_field_for_ccx(ccx, block, name, value):
value_json = field.to_json(value) value_json = field.to_json(value)
serialized_value = json.dumps(value_json) serialized_value = json.dumps(value_json)
override_has_changes = False override_has_changes = False
clean_ccx_key = _clean_ccx_key(block.location)
override = get_override_for_ccx(ccx, block, name + "_instance") override = get_override_for_ccx(ccx, block, name + "_instance")
if override: if override:
...@@ -149,7 +161,7 @@ def override_field_for_ccx(ccx, block, name, value): ...@@ -149,7 +161,7 @@ def override_field_for_ccx(ccx, block, name, value):
defaults={'value': serialized_value}, defaults={'value': serialized_value},
) )
if created: if created:
_get_overrides_for_ccx(ccx).setdefault(block.location, {})[name + "_id"] = override.id _get_overrides_for_ccx(ccx).setdefault(clean_ccx_key, {})[name + "_id"] = override.id
else: else:
override_has_changes = serialized_value != override.value override_has_changes = serialized_value != override.value
...@@ -157,8 +169,8 @@ def override_field_for_ccx(ccx, block, name, value): ...@@ -157,8 +169,8 @@ def override_field_for_ccx(ccx, block, name, value):
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(clean_ccx_key, {})[name] = value_json
_get_overrides_for_ccx(ccx).setdefault(block.location, {})[name + "_instance"] = override _get_overrides_for_ccx(ccx).setdefault(clean_ccx_key, {})[name + "_instance"] = override
def clear_override_for_ccx(ccx, block, name): def clear_override_for_ccx(ccx, block, name):
...@@ -185,7 +197,8 @@ def clear_ccx_field_info_from_ccx_map(ccx, block, name): # pylint: disable=inva ...@@ -185,7 +197,8 @@ def clear_ccx_field_info_from_ccx_map(ccx, block, name): # pylint: disable=inva
Remove field information from ccx overrides mapping dictionary Remove field information from ccx overrides mapping dictionary
""" """
try: try:
ccx_override_map = _get_overrides_for_ccx(ccx).setdefault(block.location, {}) clean_ccx_key = _clean_ccx_key(block.location)
ccx_override_map = _get_overrides_for_ccx(ccx).setdefault(clean_ccx_key, {})
ccx_override_map.pop(name) ccx_override_map.pop(name)
ccx_override_map.pop(name + "_id") ccx_override_map.pop(name + "_id")
ccx_override_map.pop(name + "_instance") ccx_override_map.pop(name + "_instance")
......
...@@ -9,6 +9,7 @@ from nose.plugins.skip import SkipTest ...@@ -9,6 +9,7 @@ from nose.plugins.skip import SkipTest
from courseware.views.views import progress from courseware.views.views import progress
from courseware.field_overrides import OverrideFieldData from courseware.field_overrides import OverrideFieldData
from courseware.testutils import FieldOverrideTestMixin
from datetime import datetime from datetime import datetime
from django.conf import settings from django.conf import settings
from django.core.cache import caches from django.core.cache import caches
...@@ -20,13 +21,13 @@ from request_cache.middleware import RequestCache ...@@ -20,13 +21,13 @@ from request_cache.middleware import RequestCache
from student.models import CourseEnrollment from student.models import CourseEnrollment
from student.tests.factories import UserFactory from student.tests.factories import UserFactory
from xblock.core import XBlock from xblock.core import XBlock
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase, \ from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase, \
TEST_DATA_SPLIT_MODULESTORE, TEST_DATA_MONGO_MODULESTORE TEST_DATA_SPLIT_MODULESTORE, TEST_DATA_MONGO_MODULESTORE
from xmodule.modulestore.tests.factories import check_mongo_calls_range, CourseFactory, check_sum_of_calls from xmodule.modulestore.tests.factories import check_mongo_calls_range, CourseFactory, check_sum_of_calls
from xmodule.modulestore.tests.utils import ProceduralCourseTestMixin from xmodule.modulestore.tests.utils import ProceduralCourseTestMixin
from ccx_keys.locator import CCXLocator from ccx_keys.locator import CCXLocator
from lms.djangoapps.ccx.tests.factories import CcxFactory from lms.djangoapps.ccx.tests.factories import CcxFactory
from openedx.core.djangoapps.content.block_structure.api import get_course_in_cache
@attr('shard_3') @attr('shard_3')
...@@ -38,8 +39,7 @@ from lms.djangoapps.ccx.tests.factories import CcxFactory ...@@ -38,8 +39,7 @@ from lms.djangoapps.ccx.tests.factories import CcxFactory
} }
) )
@ddt.ddt @ddt.ddt
class FieldOverridePerformanceTestCase(ProceduralCourseTestMixin, class FieldOverridePerformanceTestCase(FieldOverrideTestMixin, ProceduralCourseTestMixin, ModuleStoreTestCase):
ModuleStoreTestCase):
""" """
Base class for instrumenting SQL queries and Mongo reads for field override Base class for instrumenting SQL queries and Mongo reads for field override
providers. providers.
...@@ -51,8 +51,6 @@ class FieldOverridePerformanceTestCase(ProceduralCourseTestMixin, ...@@ -51,8 +51,6 @@ class FieldOverridePerformanceTestCase(ProceduralCourseTestMixin,
# TEST_DATA must be overridden by subclasses # TEST_DATA must be overridden by subclasses
TEST_DATA = None TEST_DATA = None
ENABLED_CACHES = ['default', 'mongo_metadata_inheritance', 'loc_cache']
def setUp(self): def setUp(self):
""" """
Create a test client, course, and user. Create a test client, course, and user.
...@@ -172,7 +170,7 @@ class FieldOverridePerformanceTestCase(ProceduralCourseTestMixin, ...@@ -172,7 +170,7 @@ class FieldOverridePerformanceTestCase(ProceduralCourseTestMixin,
caches[cache].clear() caches[cache].clear()
# Refill the metadata inheritance cache # Refill the metadata inheritance cache
modulestore().get_course(self.course.id, depth=None) get_course_in_cache(self.course.id)
# We clear the request cache to simulate a new request in the LMS. # We clear the request cache to simulate a new request in the LMS.
RequestCache.clear_request_cache() RequestCache.clear_request_cache()
...@@ -190,7 +188,8 @@ class FieldOverridePerformanceTestCase(ProceduralCourseTestMixin, ...@@ -190,7 +188,8 @@ class FieldOverridePerformanceTestCase(ProceduralCourseTestMixin,
@ddt.data(*itertools.product(('no_overrides', 'ccx'), range(1, 4), (True, False), (True, False))) @ddt.data(*itertools.product(('no_overrides', 'ccx'), range(1, 4), (True, False), (True, False)))
@ddt.unpack @ddt.unpack
@override_settings( @override_settings(
FIELD_OVERRIDE_PROVIDERS=(), XBLOCK_FIELD_DATA_WRAPPERS=[],
MODULESTORE_FIELD_OVERRIDE_PROVIDERS=[],
) )
def test_field_overrides(self, overrides, course_width, enable_ccx, view_as_ccx): def test_field_overrides(self, overrides, course_width, enable_ccx, view_as_ccx):
""" """
...@@ -209,7 +208,10 @@ class FieldOverridePerformanceTestCase(ProceduralCourseTestMixin, ...@@ -209,7 +208,10 @@ class FieldOverridePerformanceTestCase(ProceduralCourseTestMixin,
if self.MODULESTORE == TEST_DATA_MONGO_MODULESTORE and view_as_ccx: if self.MODULESTORE == TEST_DATA_MONGO_MODULESTORE and view_as_ccx:
raise SkipTest("Can't use a MongoModulestore test as a CCX course") raise SkipTest("Can't use a MongoModulestore test as a CCX course")
with self.settings(FIELD_OVERRIDE_PROVIDERS=providers[overrides]): with self.settings(
XBLOCK_FIELD_DATA_WRAPPERS=['lms.djangoapps.courseware.field_overrides:OverrideModulestoreFieldData.wrap'],
MODULESTORE_FIELD_OVERRIDE_PROVIDERS=providers[overrides],
):
default_queries, history_queries, reads, xblocks = self.TEST_DATA[ default_queries, history_queries, reads, xblocks = self.TEST_DATA[
(overrides, course_width, enable_ccx, view_as_ccx) (overrides, course_width, enable_ccx, view_as_ccx)
] ]
...@@ -232,24 +234,24 @@ class TestFieldOverrideMongoPerformance(FieldOverridePerformanceTestCase): ...@@ -232,24 +234,24 @@ class TestFieldOverrideMongoPerformance(FieldOverridePerformanceTestCase):
# # of mongo queries, # # of mongo queries,
# # of xblocks # # of xblocks
# ) # )
('no_overrides', 1, True, False): (47, 1, 6, 13), ('no_overrides', 1, True, False): (34, 0, 6, 1),
('no_overrides', 2, True, False): (119, 16, 6, 84), ('no_overrides', 2, True, False): (40, 0, 6, 1),
('no_overrides', 3, True, False): (399, 81, 6, 335), ('no_overrides', 3, True, False): (50, 0, 6, 1),
('ccx', 1, True, False): (47, 1, 6, 13), ('ccx', 1, True, False): (34, 0, 6, 1),
('ccx', 2, True, False): (119, 16, 6, 84), ('ccx', 2, True, False): (40, 0, 6, 1),
('ccx', 3, True, False): (399, 81, 6, 335), ('ccx', 3, True, False): (50, 0, 6, 1),
('ccx', 1, True, True): (47, 1, 6, 13), ('ccx', 1, True, True): (47, 0, 6, 1),
('ccx', 2, True, True): (119, 16, 6, 84), ('ccx', 2, True, True): (40, 0, 6, 1),
('ccx', 3, True, True): (399, 81, 6, 335), ('ccx', 3, True, True): (50, 0, 6, 1),
('no_overrides', 1, False, False): (47, 1, 6, 13), ('no_overrides', 1, False, False): (34, 0, 6, 1),
('no_overrides', 2, False, False): (119, 16, 6, 84), ('no_overrides', 2, False, False): (40, 0, 6, 1),
('no_overrides', 3, False, False): (399, 81, 6, 335), ('no_overrides', 3, False, False): (50, 0, 6, 1),
('ccx', 1, False, False): (47, 1, 6, 13), ('ccx', 1, False, False): (34, 0, 6, 1),
('ccx', 2, False, False): (119, 16, 6, 84), ('ccx', 2, False, False): (40, 0, 6, 1),
('ccx', 3, False, False): (399, 81, 6, 335), ('ccx', 3, False, False): (50, 0, 6, 1),
('ccx', 1, False, True): (47, 1, 6, 13), ('ccx', 1, False, True): (47, 0, 6, 1),
('ccx', 2, False, True): (119, 16, 6, 84), ('ccx', 2, False, True): (40, 0, 6, 1),
('ccx', 3, False, True): (399, 81, 6, 335), ('ccx', 3, False, True): (50, 0, 6, 1),
} }
...@@ -261,22 +263,22 @@ class TestFieldOverrideSplitPerformance(FieldOverridePerformanceTestCase): ...@@ -261,22 +263,22 @@ class TestFieldOverrideSplitPerformance(FieldOverridePerformanceTestCase):
__test__ = True __test__ = True
TEST_DATA = { TEST_DATA = {
('no_overrides', 1, True, False): (47, 1, 4, 9), ('no_overrides', 1, True, False): (34, 0, 4, 1),
('no_overrides', 2, True, False): (119, 16, 19, 54), ('no_overrides', 2, True, False): (40, 0, 19, 1),
('no_overrides', 3, True, False): (399, 81, 84, 215), ('no_overrides', 3, True, False): (50, 0, 84, 1),
('ccx', 1, True, False): (47, 1, 4, 9), ('ccx', 1, True, False): (34, 0, 4, 1),
('ccx', 2, True, False): (119, 16, 19, 54), ('ccx', 2, True, False): (40, 0, 19, 1),
('ccx', 3, True, False): (399, 81, 84, 215), ('ccx', 3, True, False): (50, 0, 84, 1),
('ccx', 1, True, True): (49, 1, 4, 13), ('ccx', 1, True, True): (35, 0, 5, 6),
('ccx', 2, True, True): (121, 16, 19, 84), ('ccx', 2, True, True): (41, 0, 20, 47),
('ccx', 3, True, True): (401, 81, 84, 335), ('ccx', 3, True, True): (51, 0, 85, 202),
('no_overrides', 1, False, False): (47, 1, 4, 9), ('no_overrides', 1, False, False): (34, 0, 4, 1),
('no_overrides', 2, False, False): (119, 16, 19, 54), ('no_overrides', 2, False, False): (40, 0, 19, 1),
('no_overrides', 3, False, False): (399, 81, 84, 215), ('no_overrides', 3, False, False): (50, 0, 84, 1),
('ccx', 1, False, False): (47, 1, 4, 9), ('ccx', 1, False, False): (34, 0, 4, 1),
('ccx', 2, False, False): (119, 16, 19, 54), ('ccx', 2, False, False): (40, 0, 19, 1),
('ccx', 3, False, False): (399, 81, 84, 215), ('ccx', 3, False, False): (50, 0, 84, 1),
('ccx', 1, False, True): (47, 1, 4, 9), ('ccx', 1, False, True): (46, 0, 4, 1),
('ccx', 2, False, True): (119, 16, 19, 54), ('ccx', 2, False, True): (118, 0, 19, 1),
('ccx', 3, False, True): (399, 81, 84, 215), ('ccx', 3, False, True): (398, 0, 84, 1),
} }
...@@ -7,7 +7,10 @@ import mock ...@@ -7,7 +7,10 @@ import mock
import pytz import pytz
from nose.plugins.attrib import attr from nose.plugins.attrib import attr
from ccx_keys.locator import CCXLocator
from courseware.courses import get_course_by_id
from courseware.field_overrides import OverrideFieldData from courseware.field_overrides import OverrideFieldData
from courseware.testutils import FieldOverrideTestMixin
from django.test.utils import override_settings from django.test.utils import override_settings
from lms.djangoapps.courseware.tests.test_field_overrides import inject_field_overrides from lms.djangoapps.courseware.tests.test_field_overrides import inject_field_overrides
from request_cache.middleware import RequestCache from request_cache.middleware import RequestCache
...@@ -24,9 +27,11 @@ from lms.djangoapps.ccx.tests.utils import flatten, iter_blocks ...@@ -24,9 +27,11 @@ from lms.djangoapps.ccx.tests.utils import flatten, iter_blocks
@attr('shard_1') @attr('shard_1')
@override_settings(FIELD_OVERRIDE_PROVIDERS=( @override_settings(
'ccx.overrides.CustomCoursesForEdxOverrideProvider',)) XBLOCK_FIELD_DATA_WRAPPERS=['lms.djangoapps.courseware.field_overrides:OverrideModulestoreFieldData.wrap'],
class TestFieldOverrides(SharedModuleStoreTestCase): MODULESTORE_FIELD_OVERRIDE_PROVIDERS=['ccx.overrides.CustomCoursesForEdxOverrideProvider'],
)
class TestFieldOverrides(FieldOverrideTestMixin, SharedModuleStoreTestCase):
""" """
Make sure field overrides behave in the expected manner. Make sure field overrides behave in the expected manner.
""" """
...@@ -77,6 +82,9 @@ class TestFieldOverrides(SharedModuleStoreTestCase): ...@@ -77,6 +82,9 @@ class TestFieldOverrides(SharedModuleStoreTestCase):
inject_field_overrides(iter_blocks(ccx.course), self.course, AdminFactory.create()) inject_field_overrides(iter_blocks(ccx.course), self.course, AdminFactory.create())
self.ccx_key = CCXLocator.from_course_locator(self.course.id, ccx.id)
self.ccx_course = get_course_by_id(self.ccx_key, depth=None)
def cleanup_provider_classes(): def cleanup_provider_classes():
""" """
After everything is done, clean up by un-doing the change to the After everything is done, clean up by un-doing the change to the
...@@ -90,7 +98,7 @@ class TestFieldOverrides(SharedModuleStoreTestCase): ...@@ -90,7 +98,7 @@ class TestFieldOverrides(SharedModuleStoreTestCase):
Test that overriding start date on a chapter works. Test that overriding start date on a chapter works.
""" """
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]
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)
...@@ -99,7 +107,7 @@ class TestFieldOverrides(SharedModuleStoreTestCase): ...@@ -99,7 +107,7 @@ class TestFieldOverrides(SharedModuleStoreTestCase):
Test that for creating new field executed only create query 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]
# One outer SAVEPOINT/RELEASE SAVEPOINT pair around everything caused by the # One outer SAVEPOINT/RELEASE SAVEPOINT pair around everything caused by the
# transaction.atomic decorator wrapping override_field_for_ccx. # transaction.atomic decorator wrapping override_field_for_ccx.
# One SELECT and one INSERT. # One SELECT and one INSERT.
...@@ -114,7 +122,7 @@ class TestFieldOverrides(SharedModuleStoreTestCase): ...@@ -114,7 +122,7 @@ class TestFieldOverrides(SharedModuleStoreTestCase):
""" """
ccx_start = datetime.datetime(2014, 12, 25, 00, 00, tzinfo=pytz.UTC) 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) new_ccx_start = datetime.datetime(2015, 12, 25, 00, 00, tzinfo=pytz.UTC)
chapter = self.ccx.course.get_children()[0] chapter = self.ccx_course.get_children()[0]
override_field_for_ccx(self.ccx, chapter, 'start', ccx_start) override_field_for_ccx(self.ccx, chapter, 'start', ccx_start)
with self.assertNumQueries(3): with self.assertNumQueries(3):
override_field_for_ccx(self.ccx, chapter, 'start', new_ccx_start) override_field_for_ccx(self.ccx, chapter, 'start', new_ccx_start)
...@@ -124,7 +132,7 @@ class TestFieldOverrides(SharedModuleStoreTestCase): ...@@ -124,7 +132,7 @@ class TestFieldOverrides(SharedModuleStoreTestCase):
Test that if value of field does not changed no query execute. Test that if value of field does not changed no query execute.
""" """
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]
override_field_for_ccx(self.ccx, chapter, 'start', ccx_start) override_field_for_ccx(self.ccx, chapter, 'start', ccx_start)
with self.assertNumQueries(2): # 2 savepoints with self.assertNumQueries(2): # 2 savepoints
override_field_for_ccx(self.ccx, chapter, 'start', ccx_start) override_field_for_ccx(self.ccx, chapter, 'start', ccx_start)
...@@ -134,7 +142,7 @@ class TestFieldOverrides(SharedModuleStoreTestCase): ...@@ -134,7 +142,7 @@ class TestFieldOverrides(SharedModuleStoreTestCase):
Test no extra queries when accessing an overriden field more than once. Test no extra queries when accessing an overriden field more than once.
""" """
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]
# One outer SAVEPOINT/RELEASE SAVEPOINT pair around everything caused by the # One outer SAVEPOINT/RELEASE SAVEPOINT pair around everything caused by the
# transaction.atomic decorator wrapping override_field_for_ccx. # transaction.atomic decorator wrapping override_field_for_ccx.
# One SELECT and one INSERT. # One SELECT and one INSERT.
...@@ -148,7 +156,7 @@ class TestFieldOverrides(SharedModuleStoreTestCase): ...@@ -148,7 +156,7 @@ class TestFieldOverrides(SharedModuleStoreTestCase):
Test that sequentials inherit overridden start date from chapter. Test that sequentials inherit overridden start date from chapter.
""" """
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]
override_field_for_ccx(self.ccx, chapter, 'start', ccx_start) override_field_for_ccx(self.ccx, chapter, 'start', ccx_start)
self.assertEquals(chapter.get_children()[0].start, ccx_start) self.assertEquals(chapter.get_children()[0].start, ccx_start)
self.assertEquals(chapter.get_children()[1].start, ccx_start) self.assertEquals(chapter.get_children()[1].start, ccx_start)
...@@ -160,7 +168,7 @@ class TestFieldOverrides(SharedModuleStoreTestCase): ...@@ -160,7 +168,7 @@ class TestFieldOverrides(SharedModuleStoreTestCase):
the mooc. the mooc.
""" """
ccx_due = datetime.datetime(2015, 1, 1, 00, 00, tzinfo=pytz.UTC) ccx_due = datetime.datetime(2015, 1, 1, 00, 00, tzinfo=pytz.UTC)
chapter = self.ccx.course.get_children()[0] chapter = self.ccx_course.get_children()[0]
chapter.display_name = 'itsme!' chapter.display_name = 'itsme!'
override_field_for_ccx(self.ccx, chapter, 'due', ccx_due) override_field_for_ccx(self.ccx, chapter, 'due', ccx_due)
vertical = chapter.get_children()[0].get_children()[0] vertical = chapter.get_children()[0].get_children()[0]
......
...@@ -15,6 +15,7 @@ from courseware.courses import get_course_by_id ...@@ -15,6 +15,7 @@ from courseware.courses import get_course_by_id
from courseware.tests.factories import StudentModuleFactory from courseware.tests.factories import StudentModuleFactory
from courseware.tests.helpers import LoginEnrollmentTestCase from courseware.tests.helpers import LoginEnrollmentTestCase
from courseware.tabs import get_course_tab_list from courseware.tabs import get_course_tab_list
from courseware.testutils import FieldOverrideTestMixin
from instructor.access import ( from instructor.access import (
allow_access, allow_access,
list_with_level, list_with_level,
...@@ -921,10 +922,12 @@ def patched_get_children(self, usage_key_filter=None): ...@@ -921,10 +922,12 @@ def patched_get_children(self, usage_key_filter=None):
@attr('shard_1') @attr('shard_1')
@override_settings(FIELD_OVERRIDE_PROVIDERS=( @override_settings(
'ccx.overrides.CustomCoursesForEdxOverrideProvider',)) XBLOCK_FIELD_DATA_WRAPPERS=['lms.djangoapps.courseware.field_overrides:OverrideModulestoreFieldData.wrap'],
MODULESTORE_FIELD_OVERRIDE_PROVIDERS=['ccx.overrides.CustomCoursesForEdxOverrideProvider'],
)
@patch('xmodule.x_module.XModuleMixin.get_children', patched_get_children, spec=True) @patch('xmodule.x_module.XModuleMixin.get_children', patched_get_children, spec=True)
class TestCCXGrades(SharedModuleStoreTestCase, LoginEnrollmentTestCase): class TestCCXGrades(FieldOverrideTestMixin, SharedModuleStoreTestCase, LoginEnrollmentTestCase):
""" """
Tests for Custom Courses views. Tests for Custom Courses views.
""" """
......
...@@ -282,12 +282,6 @@ def ccx_students_enrolling_center(action, identifiers, email_students, course_ke ...@@ -282,12 +282,6 @@ def ccx_students_enrolling_center(action, identifiers, email_students, course_ke
def prep_course_for_grading(course, request): def prep_course_for_grading(course, request):
"""Set up course module for overrides to function properly""" """Set up course module for overrides to function properly"""
field_data_cache = FieldDataCache.cache_for_descriptor_descendents(
course.id, request.user, course, depth=2)
course = get_module_for_descriptor(
request.user, request, course, field_data_cache, course.id, course=course
)
course._field_data_cache = {} # pylint: disable=protected-access course._field_data_cache = {} # pylint: disable=protected-access
course.set_grading_policy(course.grading_policy) course.set_grading_policy(course.grading_policy)
......
...@@ -17,6 +17,7 @@ from ..field_overrides import ( ...@@ -17,6 +17,7 @@ from ..field_overrides import (
OverrideFieldData, OverrideFieldData,
OverrideModulestoreFieldData, OverrideModulestoreFieldData,
) )
from ..testutils import FieldOverrideTestMixin
TESTUSER = "testuser" TESTUSER = "testuser"
...@@ -128,15 +129,7 @@ class OverrideFieldDataTests(SharedModuleStoreTestCase): ...@@ -128,15 +129,7 @@ class OverrideFieldDataTests(SharedModuleStoreTestCase):
@override_settings( @override_settings(
MODULESTORE_FIELD_OVERRIDE_PROVIDERS=['courseware.tests.test_field_overrides.TestOverrideProvider'] MODULESTORE_FIELD_OVERRIDE_PROVIDERS=['courseware.tests.test_field_overrides.TestOverrideProvider']
) )
class OverrideModulestoreFieldDataTests(OverrideFieldDataTests): class OverrideModulestoreFieldDataTests(FieldOverrideTestMixin, OverrideFieldDataTests):
def setUp(self):
super(OverrideModulestoreFieldDataTests, self).setUp()
OverrideModulestoreFieldData.provider_classes = None
def tearDown(self):
super(OverrideModulestoreFieldDataTests, self).tearDown()
OverrideModulestoreFieldData.provider_classes = None
def make_one(self): def make_one(self):
return OverrideModulestoreFieldData.wrap(self.course, DictFieldData({ return OverrideModulestoreFieldData.wrap(self.course, DictFieldData({
'foo': 'bar', 'foo': 'bar',
......
...@@ -339,16 +339,16 @@ class ModuleRenderTestCase(SharedModuleStoreTestCase, LoginEnrollmentTestCase): ...@@ -339,16 +339,16 @@ class ModuleRenderTestCase(SharedModuleStoreTestCase, LoginEnrollmentTestCase):
) )
@override_settings(FIELD_OVERRIDE_PROVIDERS=( @override_settings(FIELD_OVERRIDE_PROVIDERS=(
'ccx.overrides.CustomCoursesForEdxOverrideProvider', 'courseware.student_field_overrides.IndividualStudentOverrideProvider',
)) ))
def test_rebind_different_users_ccx(self): def test_rebind_different_users(self):
""" """
This tests the rebinding a descriptor to a student does not result This tests the rebinding a descriptor to a student does not result
in overly nested _field_data when CCX is enabled. in overly nested _field_data.
""" """
request = self.request_factory.get('') request = self.request_factory.get('')
request.user = self.mock_user request.user = self.mock_user
course = CourseFactory.create(enable_ccx=True) course = CourseFactory.create()
descriptor = ItemFactory(category='html', parent=course) descriptor = ItemFactory(category='html', parent=course)
field_data_cache = FieldDataCache( field_data_cache = FieldDataCache(
......
...@@ -8,6 +8,7 @@ import ddt ...@@ -8,6 +8,7 @@ import ddt
from mock import patch from mock import patch
from urllib import urlencode from urllib import urlencode
from lms.djangoapps.courseware.field_overrides import OverrideModulestoreFieldData
from lms.djangoapps.courseware.url_helpers import get_redirect_url from lms.djangoapps.courseware.url_helpers import get_redirect_url
from student.tests.factories import AdminFactory, UserFactory, CourseEnrollmentFactory from student.tests.factories import AdminFactory, UserFactory, CourseEnrollmentFactory
from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore import ModuleStoreEnum
...@@ -197,3 +198,16 @@ class RenderXBlockTestMixin(object): ...@@ -197,3 +198,16 @@ class RenderXBlockTestMixin(object):
self.setup_course() self.setup_course()
self.setup_user(admin=False, enroll=True, login=True) self.setup_user(admin=False, enroll=True, login=True)
self.verify_response(url_params={'view': 'author_view'}, expected_response_code=400) self.verify_response(url_params={'view': 'author_view'}, expected_response_code=400)
class FieldOverrideTestMixin(object):
"""
A Mixin helper class for classes that test Field Overrides.
"""
def setUp(self):
super(FieldOverrideTestMixin, self).setUp()
OverrideModulestoreFieldData.provider_classes = None
def tearDown(self):
super(FieldOverrideTestMixin, self).tearDown()
OverrideModulestoreFieldData.provider_classes = None
...@@ -731,7 +731,7 @@ COURSE_CATALOG_API_URL = ENV_TOKENS.get('COURSE_CATALOG_API_URL', COURSE_CATALOG ...@@ -731,7 +731,7 @@ COURSE_CATALOG_API_URL = ENV_TOKENS.get('COURSE_CATALOG_API_URL', COURSE_CATALOG
##### Custom Courses for EdX ##### ##### Custom Courses for EdX #####
if FEATURES.get('CUSTOM_COURSES_EDX'): if FEATURES.get('CUSTOM_COURSES_EDX'):
INSTALLED_APPS += ('lms.djangoapps.ccx', 'openedx.core.djangoapps.ccxcon') INSTALLED_APPS += ('lms.djangoapps.ccx', 'openedx.core.djangoapps.ccxcon')
FIELD_OVERRIDE_PROVIDERS += ( MODULESTORE_FIELD_OVERRIDE_PROVIDERS += (
'lms.djangoapps.ccx.overrides.CustomCoursesForEdxOverrideProvider', 'lms.djangoapps.ccx.overrides.CustomCoursesForEdxOverrideProvider',
) )
CCX_MAX_STUDENTS_ALLOWED = ENV_TOKENS.get('CCX_MAX_STUDENTS_ALLOWED', CCX_MAX_STUDENTS_ALLOWED) CCX_MAX_STUDENTS_ALLOWED = ENV_TOKENS.get('CCX_MAX_STUDENTS_ALLOWED', CCX_MAX_STUDENTS_ALLOWED)
......
...@@ -299,7 +299,7 @@ GRADES_DOWNLOAD_ROUTING_KEY = HIGH_MEM_QUEUE ...@@ -299,7 +299,7 @@ GRADES_DOWNLOAD_ROUTING_KEY = HIGH_MEM_QUEUE
##### Custom Courses for EdX ##### ##### Custom Courses for EdX #####
if FEATURES.get('CUSTOM_COURSES_EDX'): if FEATURES.get('CUSTOM_COURSES_EDX'):
INSTALLED_APPS += ('lms.djangoapps.ccx', 'openedx.core.djangoapps.ccxcon') INSTALLED_APPS += ('lms.djangoapps.ccx', 'openedx.core.djangoapps.ccxcon')
FIELD_OVERRIDE_PROVIDERS += ( MODULESTORE_FIELD_OVERRIDE_PROVIDERS += (
'lms.djangoapps.ccx.overrides.CustomCoursesForEdxOverrideProvider', 'lms.djangoapps.ccx.overrides.CustomCoursesForEdxOverrideProvider',
) )
......
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