CCX: Use Modulestore Field Overrides instead of User-specific Overrides
This commit is contained in:
@@ -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
|
|
||||||
|
|
||||||
protect against missing attributes
|
|
||||||
"""
|
"""
|
||||||
return getattr(course, 'enable_ccx', False)
|
CCX field overrides are enabled for CCX blocks.
|
||||||
|
"""
|
||||||
|
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):
|
|||||||
"""
|
"""
|
||||||
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):
|
|||||||
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):
|
|||||||
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):
|
|||||||
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):
|
|||||||
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
|
|||||||
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
|
|||||||
|
|
||||||
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
|
|||||||
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
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
@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,
|
|||||||
# 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,
|
|||||||
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,
|
|||||||
@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,
|
|||||||
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):
|
|||||||
# # 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):
|
|||||||
__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
|
|||||||
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
|
|||||||
|
|
||||||
|
|
||||||
@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):
|
|||||||
|
|
||||||
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):
|
|||||||
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):
|
|||||||
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):
|
|||||||
"""
|
"""
|
||||||
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):
|
|||||||
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):
|
|||||||
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):
|
|||||||
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):
|
|||||||
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
|
|||||||
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):
|
|||||||
|
|
||||||
|
|
||||||
@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
|
|||||||
|
|
||||||
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 (
|
|||||||
OverrideFieldData,
|
OverrideFieldData,
|
||||||
OverrideModulestoreFieldData,
|
OverrideModulestoreFieldData,
|
||||||
)
|
)
|
||||||
|
from ..testutils import FieldOverrideTestMixin
|
||||||
|
|
||||||
|
|
||||||
TESTUSER = "testuser"
|
TESTUSER = "testuser"
|
||||||
@@ -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):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@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
|
|||||||
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):
|
|||||||
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
|
|||||||
##### 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
|
|||||||
##### 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',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user