From de142b2b51b0eb38ac73616102f5131886536e57 Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Wed, 17 Jun 2015 18:33:02 -0400 Subject: [PATCH 1/4] Set a minimum number of Xblock constructions in field override tests --- lms/djangoapps/ccx/tests/test_field_override_performance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lms/djangoapps/ccx/tests/test_field_override_performance.py b/lms/djangoapps/ccx/tests/test_field_override_performance.py index 8d4eff1ac5..170f20fd0c 100644 --- a/lms/djangoapps/ccx/tests/test_field_override_performance.py +++ b/lms/djangoapps/ccx/tests/test_field_override_performance.py @@ -143,7 +143,7 @@ class FieldOverridePerformanceTestCase(ProceduralCourseTestMixin, with self.assertNumQueries(queries): with check_mongo_calls(reads): - with check_sum_of_calls(XBlock, ['__init__'], xblocks): + with check_sum_of_calls(XBlock, ['__init__'], xblocks, xblocks): self.grade_course(self.course) @ddt.data(*itertools.product(('no_overrides', 'ccx'), range(1, 4), (True, False))) From 324d4785c13965214c9410e1b61a0f276d7d370e Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Fri, 19 Jun 2015 12:04:45 -0400 Subject: [PATCH 2/4] Include stack traces when counting calls in unit tests --- .../xmodule/modulestore/tests/factories.py | 165 +++++++++++++++--- .../tests/test_field_override_performance.py | 2 +- 2 files changed, 140 insertions(+), 27 deletions(-) diff --git a/common/lib/xmodule/xmodule/modulestore/tests/factories.py b/common/lib/xmodule/xmodule/modulestore/tests/factories.py index cea8c2ebc4..ba5c1a9598 100644 --- a/common/lib/xmodule/xmodule/modulestore/tests/factories.py +++ b/common/lib/xmodule/xmodule/modulestore/tests/factories.py @@ -2,12 +2,15 @@ Factories for use in tests of XBlocks. """ +import functools import inspect import pprint -import threading -from uuid import uuid4 -from decorator import contextmanager import pymongo.message +import threading +import traceback +from collections import defaultdict +from decorator import contextmanager +from uuid import uuid4 from factory import Factory, Sequence, lazy_attribute_sequence, lazy_attribute from factory.containers import CyclicDefinitionError @@ -320,47 +323,157 @@ def check_number_of_calls(object_with_method, method_name, maximum_calls, minimu return check_sum_of_calls(object_with_method, [method_name], maximum_calls, minimum_calls) +class StackTraceCounter(object): + """ + A class that counts unique stack traces underneath a particular stack frame. + """ + def __init__(self, stack_depth, include_arguments=True): + """ + Arguments: + stack_depth (int): The number of stack frames above this constructor to capture. + include_arguments (bool): Whether to store the arguments that are passed + when capturing a stack trace. + """ + self.include_arguments = include_arguments + self._top_of_stack = traceback.extract_stack(limit=stack_depth)[0] + + if self.include_arguments: + self._stacks = defaultdict(lambda: defaultdict(int)) + else: + self._stacks = defaultdict(int) + + def capture_stack(self, args, kwargs): + """ + Record the stack frames starting at the caller of this method, and + ending at the top of the stack as defined by the ``stack_depth``. + + Arguments: + args: The positional arguments to capture at this stack frame + kwargs: The keyword arguments to capture at this stack frame + """ + stack = traceback.extract_stack()[:-2] + + if self._top_of_stack in stack: + stack = stack[stack.index(self._top_of_stack):] + + if self.include_arguments: + safe_args = [] + for arg in args: + try: + safe_args.append(repr(arg)) + except Exception as exc: + safe_args.append(' Date: Wed, 17 Jun 2015 18:33:40 -0400 Subject: [PATCH 3/4] Pass XBlock parents down to their children when constructing them, for caching --- common/lib/xmodule/xmodule/error_module.py | 15 +++- .../xmodule/xmodule/modulestore/mongo/base.py | 32 ++++--- .../xmodule/modulestore/mongo/draft.py | 6 +- .../split_mongo/caching_descriptor_system.py | 1 + .../xmodule/modulestore/tests/factories.py | 3 + common/lib/xmodule/xmodule/modulestore/xml.py | 4 +- .../xmodule/xmodule/tests/test_conditional.py | 12 ++- .../xmodule/tests/test_error_module.py | 22 ++--- .../lib/xmodule/xmodule/tests/xml/__init__.py | 2 +- common/lib/xmodule/xmodule/x_module.py | 86 +++++++++++-------- .../tests/test_field_override_performance.py | 24 +++--- .../course_structure_api/v0/views.py | 7 +- .../courseware/tests/test_module_render.py | 15 ++-- requirements/edx/github.txt | 2 +- 14 files changed, 145 insertions(+), 86 deletions(-) diff --git a/common/lib/xmodule/xmodule/error_module.py b/common/lib/xmodule/xmodule/error_module.py index 066a1ad38b..11c0c28720 100644 --- a/common/lib/xmodule/xmodule/error_module.py +++ b/common/lib/xmodule/xmodule/error_module.py @@ -80,7 +80,18 @@ class ErrorDescriptor(ErrorFields, XModuleDescriptor): return u'' @classmethod - def _construct(cls, system, contents, error_msg, location): + def _construct(cls, system, contents, error_msg, location, for_parent=None): + """ + Build a new ErrorDescriptor. using ``system``. + + Arguments: + system (:class:`DescriptorSystem`): The :class:`DescriptorSystem` used + to construct the XBlock that had an error. + contents (unicode): An encoding of the content of the xblock that had an error. + error_msg (unicode): A message describing the error. + location (:class:`UsageKey`): The usage key of the XBlock that had an error. + for_parent (:class:`XBlock`): Optional. The parent of this error block. + """ if error_msg is None: # this string is not marked for translation because we don't have @@ -110,6 +121,7 @@ class ErrorDescriptor(ErrorFields, XModuleDescriptor): # real scope keys ScopeIds(None, 'error', location, location), field_data, + for_parent=for_parent, ) def get_context(self): @@ -139,6 +151,7 @@ class ErrorDescriptor(ErrorFields, XModuleDescriptor): str(descriptor), error_msg, location=descriptor.location, + for_parent=descriptor.get_parent() if descriptor.has_cached_parent else None ) @classmethod diff --git a/common/lib/xmodule/xmodule/modulestore/mongo/base.py b/common/lib/xmodule/xmodule/modulestore/mongo/base.py index 9a4ba7c6f1..d7ef5d542b 100644 --- a/common/lib/xmodule/xmodule/modulestore/mongo/base.py +++ b/common/lib/xmodule/xmodule/modulestore/mongo/base.py @@ -223,7 +223,7 @@ class CachingDescriptorSystem(MakoDescriptorSystem, EditInfoRuntimeMixin): self.course_id = course_key self.cached_metadata = cached_metadata - def load_item(self, location): + def load_item(self, location, for_parent=None): # pylint: disable=method-hidden """ Return an XModule instance for the specified location """ @@ -292,7 +292,7 @@ class CachingDescriptorSystem(MakoDescriptorSystem, EditInfoRuntimeMixin): field_data = KvsFieldData(kvs) scope_ids = ScopeIds(None, category, location, location) - module = self.construct_xblock_from_class(class_, scope_ids, field_data) + module = self.construct_xblock_from_class(class_, scope_ids, field_data, for_parent=for_parent) if self.cached_metadata is not None: # parent container pointers don't differentiate between draft and non-draft # so when we do the lookup, we should do so with a non-draft location @@ -883,7 +883,8 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase, Mongo apply_cached_metadata=bool, using_descriptor_system="None|CachingDescriptorSystem" ) - def _load_item(self, course_key, item, data_cache, apply_cached_metadata=True, using_descriptor_system=None): + def _load_item(self, course_key, item, data_cache, + apply_cached_metadata=True, using_descriptor_system=None, for_parent=None): """ Load an XModuleDescriptor from item, using the children stored in data_cache @@ -898,6 +899,7 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase, Mongo purposes. using_descriptor_system (CachingDescriptorSystem): The existing CachingDescriptorSystem to add data to, and to load the XBlocks from. + for_parent (:class:`XBlock`): The parent of the XBlock being loaded. """ course_key = self.fill_in_run(course_key) location = Location._from_deprecated_son(item['location'], course_key.run) @@ -942,9 +944,9 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase, Mongo system.module_data.update(data_cache) system.cached_metadata.update(cached_metadata) - return system.load_item(location) + return system.load_item(location, for_parent=for_parent) - def _load_items(self, course_key, items, depth=0, using_descriptor_system=None): + def _load_items(self, course_key, items, depth=0, using_descriptor_system=None, for_parent=None): """ Load a list of xmodules from the data in items, with children cached up to specified depth @@ -960,7 +962,8 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase, Mongo item, data_cache, using_descriptor_system=using_descriptor_system, - apply_cached_metadata=self._should_apply_cached_metadata(item, depth) + apply_cached_metadata=self._should_apply_cached_metadata(item, depth), + for_parent=for_parent, ) for item in items ] @@ -1078,7 +1081,7 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase, Mongo except ItemNotFoundError: return False - def get_item(self, usage_key, depth=0, using_descriptor_system=None): + def get_item(self, usage_key, depth=0, using_descriptor_system=None, for_parent=None, **kwargs): """ Returns an XModuleDescriptor instance for the item at location. @@ -1101,7 +1104,8 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase, Mongo usage_key.course_key, [item], depth, - using_descriptor_system=using_descriptor_system + using_descriptor_system=using_descriptor_system, + for_parent=for_parent, )[0] return module @@ -1293,6 +1297,7 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase, Mongo # so we use the location for both. ScopeIds(None, block_type, location, location), dbmodel, + for_parent=kwargs.get('for_parent'), ) if fields is not None: for key, value in fields.iteritems(): @@ -1341,11 +1346,16 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase, Mongo block_id: a unique identifier for the new item. If not supplied, a new identifier will be generated """ - xblock = self.create_item(user_id, parent_usage_key.course_key, block_type, block_id=block_id, **kwargs) # attach to parent if given - if 'detached' not in xblock._class_tags: - parent = self.get_item(parent_usage_key) + parent = None + if parent_usage_key is not None: + parent = self.get_item(parent_usage_key) + kwargs.setdefault('for_parent', parent) + + xblock = self.create_item(user_id, parent_usage_key.course_key, block_type, block_id=block_id, **kwargs) + + if parent is not None and 'detached' not in xblock._class_tags: # Originally added to support entrance exams (settings.FEATURES.get('ENTRANCE_EXAMS')) if kwargs.get('position') is None: parent.children.append(xblock.location) diff --git a/common/lib/xmodule/xmodule/modulestore/mongo/draft.py b/common/lib/xmodule/xmodule/modulestore/mongo/draft.py index 814058d145..8c181b2639 100644 --- a/common/lib/xmodule/xmodule/modulestore/mongo/draft.py +++ b/common/lib/xmodule/xmodule/modulestore/mongo/draft.py @@ -82,12 +82,14 @@ class DraftModuleStore(MongoModuleStore): """ def get_published(): return wrap_draft(super(DraftModuleStore, self).get_item( - usage_key, depth=depth, using_descriptor_system=using_descriptor_system + usage_key, depth=depth, using_descriptor_system=using_descriptor_system, + for_parent=kwargs.get('for_parent'), )) def get_draft(): return wrap_draft(super(DraftModuleStore, self).get_item( - as_draft(usage_key), depth=depth, using_descriptor_system=using_descriptor_system + as_draft(usage_key), depth=depth, using_descriptor_system=using_descriptor_system, + for_parent=kwargs.get('for_parent') )) # return the published version if ModuleStoreEnum.RevisionOption.published_only is requested diff --git a/common/lib/xmodule/xmodule/modulestore/split_mongo/caching_descriptor_system.py b/common/lib/xmodule/xmodule/modulestore/split_mongo/caching_descriptor_system.py index dff8b0d853..f8f1dbe54e 100644 --- a/common/lib/xmodule/xmodule/modulestore/split_mongo/caching_descriptor_system.py +++ b/common/lib/xmodule/xmodule/modulestore/split_mongo/caching_descriptor_system.py @@ -227,6 +227,7 @@ class CachingDescriptorSystem(MakoDescriptorSystem, EditInfoRuntimeMixin): class_, ScopeIds(None, block_key.type, definition_id, block_locator), field_data, + for_parent=kwargs.get('for_parent') ) except Exception: # pylint: disable=broad-except log.warning("Failed to load descriptor", exc_info=True) diff --git a/common/lib/xmodule/xmodule/modulestore/tests/factories.py b/common/lib/xmodule/xmodule/modulestore/tests/factories.py index ba5c1a9598..abfc1f9664 100644 --- a/common/lib/xmodule/xmodule/modulestore/tests/factories.py +++ b/common/lib/xmodule/xmodule/modulestore/tests/factories.py @@ -351,6 +351,8 @@ class StackTraceCounter(object): args: The positional arguments to capture at this stack frame kwargs: The keyword arguments to capture at this stack frame """ + # pylint: disable=broad-except + stack = traceback.extract_stack()[:-2] if self._top_of_stack in stack: @@ -419,6 +421,7 @@ class StackTraceCounter(object): """ stacks = StackTraceCounter(stack_depth, include_arguments) + # pylint: disable=missing-docstring @functools.wraps(func) def capture(*args, **kwargs): stacks.capture_stack(args, kwargs) diff --git a/common/lib/xmodule/xmodule/modulestore/xml.py b/common/lib/xmodule/xmodule/modulestore/xml.py index 78ef1043d3..8950380615 100644 --- a/common/lib/xmodule/xmodule/modulestore/xml.py +++ b/common/lib/xmodule/xmodule/modulestore/xml.py @@ -250,9 +250,9 @@ class ImportSystem(XMLParsingSystem, MakoDescriptorSystem): # TODO (vshnayder): we are somewhat architecturally confused in the loading code: # load_item should actually be get_instance, because it expects the course-specific # policy to be loaded. For now, just add the course_id here... - def load_item(usage_key): + def load_item(usage_key, for_parent=None): """Return the XBlock for the specified location""" - return xmlstore.get_item(usage_key) + return xmlstore.get_item(usage_key, for_parent=for_parent) resources_fs = OSFS(xmlstore.data_dir / course_dir) diff --git a/common/lib/xmodule/xmodule/tests/test_conditional.py b/common/lib/xmodule/xmodule/tests/test_conditional.py index 4dc087fd5e..d392de1829 100644 --- a/common/lib/xmodule/xmodule/tests/test_conditional.py +++ b/common/lib/xmodule/xmodule/tests/test_conditional.py @@ -79,10 +79,14 @@ class ConditionalFactory(object): child_descriptor.render = lambda view, context=None: descriptor_system.render(child_descriptor, view, context) child_descriptor.location = source_location.replace(category='html', name='child') - descriptor_system.load_item = { - child_descriptor.location: child_descriptor, - source_location: source_descriptor - }.get + def load_item(usage_id, for_parent=None): # pylint: disable=unused-argument + """Test-only implementation of load_item that simply returns static xblocks.""" + return { + child_descriptor.location: child_descriptor, + source_location: source_descriptor + }.get(usage_id) + + descriptor_system.load_item = load_item system.descriptor_runtime = descriptor_system diff --git a/common/lib/xmodule/xmodule/tests/test_error_module.py b/common/lib/xmodule/xmodule/tests/test_error_module.py index 1b12017fbd..a59cc56452 100644 --- a/common/lib/xmodule/xmodule/tests/test_error_module.py +++ b/common/lib/xmodule/xmodule/tests/test_error_module.py @@ -9,7 +9,7 @@ from opaque_keys.edx.locations import SlashSeparatedCourseKey, Location from xmodule.x_module import XModuleDescriptor, XModule, STUDENT_VIEW from mock import MagicMock, Mock, patch from xblock.runtime import Runtime, IdReader -from xblock.field_data import FieldData +from xblock.field_data import DictFieldData from xblock.fields import ScopeIds from xblock.test.tools import unabc @@ -43,10 +43,11 @@ class TestErrorModule(SetupTestErrorModules): self.assertIn(repr(self.valid_xml), context_repr) def test_error_module_from_descriptor(self): - descriptor = MagicMock([XModuleDescriptor], - runtime=self.system, - location=self.location, - _field_data=self.valid_xml) + descriptor = MagicMock( + spec=XModuleDescriptor, + runtime=self.system, + location=self.location, + ) error_descriptor = ErrorDescriptor.from_descriptor( descriptor, self.error_msg) @@ -81,10 +82,11 @@ class TestNonStaffErrorModule(SetupTestErrorModules): self.assertNotIn(repr(self.valid_xml), context_repr) def test_error_module_from_descriptor(self): - descriptor = MagicMock([XModuleDescriptor], - runtime=self.system, - location=self.location, - _field_data=self.valid_xml) + descriptor = MagicMock( + spec=XModuleDescriptor, + runtime=self.system, + location=self.location, + ) error_descriptor = NonStaffErrorDescriptor.from_descriptor( descriptor, self.error_msg) @@ -122,7 +124,7 @@ class TestErrorModuleConstruction(unittest.TestCase): def setUp(self): # pylint: disable=abstract-class-instantiated super(TestErrorModuleConstruction, self).setUp() - field_data = Mock(spec=FieldData) + field_data = DictFieldData({}) self.descriptor = BrokenDescriptor( TestRuntime(Mock(spec=IdReader), field_data), field_data, diff --git a/common/lib/xmodule/xmodule/tests/xml/__init__.py b/common/lib/xmodule/xmodule/tests/xml/__init__.py index c6ec34785c..f4342b45b6 100644 --- a/common/lib/xmodule/xmodule/tests/xml/__init__.py +++ b/common/lib/xmodule/xmodule/tests/xml/__init__.py @@ -49,7 +49,7 @@ class InMemorySystem(XMLParsingSystem, MakoDescriptorSystem): # pylint: disable self._descriptors[descriptor.location.to_deprecated_string()] = descriptor return descriptor - def load_item(self, location): # pylint: disable=method-hidden + def load_item(self, location, for_parent=None): # pylint: disable=method-hidden, unused-argument """Return the descriptor loaded for `location`""" return self._descriptors[location.to_deprecated_string()] diff --git a/common/lib/xmodule/xmodule/x_module.py b/common/lib/xmodule/xmodule/x_module.py index 9c809b1fad..da90b47d7a 100644 --- a/common/lib/xmodule/xmodule/x_module.py +++ b/common/lib/xmodule/xmodule/x_module.py @@ -295,7 +295,6 @@ class XModuleMixin(XModuleFields, XBlockMixin): def __init__(self, *args, **kwargs): self.xmodule_runtime = None - self._child_instances = None super(XModuleMixin, self).__init__(*args, **kwargs) @@ -424,39 +423,45 @@ class XModuleMixin(XModuleFields, XBlockMixin): else: return [self.display_name_with_default] - def get_children(self, usage_key_filter=None): + def get_children(self, usage_id_filter=None, usage_key_filter=None): # pylint: disable=arguments-differ """Returns a list of XBlock instances for the children of this module""" - if not self.has_children: - return [] + # Be backwards compatible with callers using usage_key_filter + if usage_id_filter is None and usage_key_filter is not None: + usage_id_filter = usage_key_filter - if self._child_instances is None: - self._child_instances = [] # pylint: disable=attribute-defined-outside-init - for child_loc in self.children: - # Skip if it doesn't satisfy the filter function - if usage_key_filter and not usage_key_filter(child_loc): - continue - try: - child = self.runtime.get_block(child_loc) - if child is None: - continue + return [ + child + for child + in super(XModuleMixin, self).get_children(usage_id_filter) + if child is not None + ] - child.runtime.export_fs = self.runtime.export_fs - except ItemNotFoundError: - log.warning(u'Unable to load item {loc}, skipping'.format(loc=child_loc)) - dog_stats_api.increment( - "xmodule.item_not_found_error", - tags=[ - u"course_id:{}".format(child_loc.course_key), - u"block_type:{}".format(child_loc.block_type), - u"parent_block_type:{}".format(self.location.block_type), - ] - ) - continue - self._child_instances.append(child) + def get_child(self, usage_id): + """ + Return the child XBlock identified by ``usage_id``, or ``None`` if there + is an error while retrieving the block. + """ + try: + child = super(XModuleMixin, self).get_child(usage_id) + except ItemNotFoundError: + log.warning(u'Unable to load item %s, skipping', usage_id) + dog_stats_api.increment( + "xmodule.item_not_found_error", + tags=[ + u"course_id:{}".format(usage_id.course_key), + u"block_type:{}".format(usage_id.block_type), + u"parent_block_type:{}".format(self.location.block_type), + ] + ) + return None - return self._child_instances + if child is None: + return None + + child.runtime.export_fs = self.runtime.export_fs + return child def get_required_module_descriptors(self): """Returns a list of XModuleDescriptor instances upon which this module depends, but are @@ -573,7 +578,7 @@ class XModuleMixin(XModuleFields, XBlockMixin): self.scope_ids = self.scope_ids._replace(user_id=user_id) # Clear out any cached instantiated children. - self._child_instances = None + self.clear_child_cache() # Clear out any cached field data scoped to the old user. for field in self.fields.values(): @@ -767,7 +772,6 @@ class XModule(XModuleMixin, HTMLSnippet, XBlock): # pylint: disable=abstract-me # Set the descriptor first so that we can proxy to it self.descriptor = descriptor - self._loaded_children = None self._runtime = None super(XModule, self).__init__(*args, **kwargs) self.runtime.xmodule_instance = self @@ -820,6 +824,19 @@ class XModule(XModuleMixin, HTMLSnippet, XBlock): # pylint: disable=abstract-me response_data = self.handle_ajax(suffix, request_post) return Response(response_data, content_type='application/json') + def get_child(self, usage_id): + if usage_id in self._child_cache: + return self._child_cache[usage_id] + + # Take advantage of the children cache that the descriptor might have + child_descriptor = self.descriptor.get_child(usage_id) + child_block = None + if child_descriptor is not None: + child_block = self.system.get_module(child_descriptor) + + self._child_cache[usage_id] = child_block + return child_block + def get_child_descriptors(self): """ Returns the descriptors of the child modules @@ -1103,6 +1120,7 @@ class XModuleDescriptor(XModuleMixin, HTMLSnippet, ResourceTemplates, XBlock): descriptor=self, scope_ids=self.scope_ids, field_data=self._field_data, + for_parent=self.get_parent() if self.has_cached_parent else None ) self.xmodule_runtime.xmodule_instance.save() except Exception: # pylint: disable=broad-except @@ -1340,9 +1358,9 @@ class DescriptorSystem(MetricsMixin, ConfigurableFragmentWrapper, Runtime): # p else: self.get_policy = lambda u: {} - def get_block(self, usage_id): + def get_block(self, usage_id, for_parent=None): """See documentation for `xblock.runtime:Runtime.get_block`""" - return self.load_item(usage_id) + return self.load_item(usage_id, for_parent=for_parent) def get_field_provenance(self, xblock, field): """ @@ -1681,8 +1699,8 @@ class ModuleSystem(MetricsMixin, ConfigurableFragmentWrapper, Runtime): # pylin assert self.xmodule_instance is not None return self.handler_url(self.xmodule_instance, 'xmodule_handler', '', '').rstrip('/?') - def get_block(self, block_id): - return self.get_module(self.descriptor_runtime.get_block(block_id)) + def get_block(self, block_id, for_parent=None): + return self.get_module(self.descriptor_runtime.get_block(block_id, for_parent=for_parent)) def resource_url(self, resource): raise NotImplementedError("edX Platform doesn't currently implement XBlock resource urls") diff --git a/lms/djangoapps/ccx/tests/test_field_override_performance.py b/lms/djangoapps/ccx/tests/test_field_override_performance.py index ad14b6908c..3b98b3b46b 100644 --- a/lms/djangoapps/ccx/tests/test_field_override_performance.py +++ b/lms/djangoapps/ccx/tests/test_field_override_performance.py @@ -173,18 +173,18 @@ class TestFieldOverrideMongoPerformance(FieldOverridePerformanceTestCase): TEST_DATA = { # (providers, course_width, enable_ccx): # of sql queries, # of mongo queries, # of xblocks - ('no_overrides', 1, True): (26, 7, 19), - ('no_overrides', 2, True): (134, 7, 131), - ('no_overrides', 3, True): (594, 7, 537), - ('ccx', 1, True): (26, 7, 47), - ('ccx', 2, True): (134, 7, 455), - ('ccx', 3, True): (594, 7, 2037), - ('no_overrides', 1, False): (26, 7, 19), - ('no_overrides', 2, False): (134, 7, 131), - ('no_overrides', 3, False): (594, 7, 537), - ('ccx', 1, False): (26, 7, 19), - ('ccx', 2, False): (134, 7, 131), - ('ccx', 3, False): (594, 7, 537), + ('no_overrides', 1, True): (26, 7, 14), + ('no_overrides', 2, True): (134, 7, 85), + ('no_overrides', 3, True): (594, 7, 336), + ('ccx', 1, True): (26, 7, 14), + ('ccx', 2, True): (134, 7, 85), + ('ccx', 3, True): (594, 7, 336), + ('no_overrides', 1, False): (26, 7, 14), + ('no_overrides', 2, False): (134, 7, 85), + ('no_overrides', 3, False): (594, 7, 336), + ('ccx', 1, False): (26, 7, 14), + ('ccx', 2, False): (134, 7, 85), + ('ccx', 3, False): (594, 7, 336), } diff --git a/lms/djangoapps/course_structure_api/v0/views.py b/lms/djangoapps/course_structure_api/v0/views.py index 2d31995751..08f8f56df9 100644 --- a/lms/djangoapps/course_structure_api/v0/views.py +++ b/lms/djangoapps/course_structure_api/v0/views.py @@ -560,7 +560,12 @@ class CourseBlocksAndNavigation(ListAPIView): ) # verify the user has access to this block - if not has_access(request_info.request.user, 'load', block_info.block, course_key=request_info.course.id): + if (block_info.block is None or not has_access( + request_info.request.user, + 'load', + block_info.block, + course_key=request_info.course.id + )): return # add the block's value to the result diff --git a/lms/djangoapps/courseware/tests/test_module_render.py b/lms/djangoapps/courseware/tests/test_module_render.py index 408f763178..3459f4eb46 100644 --- a/lms/djangoapps/courseware/tests/test_module_render.py +++ b/lms/djangoapps/courseware/tests/test_module_render.py @@ -610,11 +610,11 @@ class TestTOC(ModuleStoreTestCase): # Split makes 6 queries to load the course to depth 2: # - load the structure # - load 5 definitions - # Split makes 6 queries to render the toc: + # Split makes 5 queries to render the toc: # - it loads the active version at the start of the bulk operation - # - it loads 5 definitions, because it instantiates the a CourseModule and 4 VideoModules + # - it loads 4 definitions, because it instantiates 4 VideoModules # each of which access a Scope.content field in __init__ - @ddt.data((ModuleStoreEnum.Type.mongo, 3, 0, 0), (ModuleStoreEnum.Type.split, 6, 0, 6)) + @ddt.data((ModuleStoreEnum.Type.mongo, 3, 0, 0), (ModuleStoreEnum.Type.split, 6, 0, 5)) @ddt.unpack def test_toc_toy_from_chapter(self, default_ms, setup_finds, setup_sends, toc_finds): with self.store.default_store(default_ms): @@ -634,9 +634,10 @@ class TestTOC(ModuleStoreTestCase): 'format': '', 'due': None, 'active': False}], 'url_name': 'secret:magic', 'display_name': 'secret:magic'}]) + course = self.store.get_course(self.toy_course.id, depth=2) with check_mongo_calls(toc_finds): actual = render.toc_for_course( - self.request, self.toy_course, self.chapter, None, self.field_data_cache + self.request, course, self.chapter, None, self.field_data_cache ) for toc_section in expected: self.assertIn(toc_section, actual) @@ -648,11 +649,11 @@ class TestTOC(ModuleStoreTestCase): # Split makes 6 queries to load the course to depth 2: # - load the structure # - load 5 definitions - # Split makes 2 queries to render the toc: + # Split makes 5 queries to render the toc: # - it loads the active version at the start of the bulk operation - # - it loads 5 definitions, because it instantiates the a CourseModule and 4 VideoModules + # - it loads 4 definitions, because it instantiates 4 VideoModules # each of which access a Scope.content field in __init__ - @ddt.data((ModuleStoreEnum.Type.mongo, 3, 0, 0), (ModuleStoreEnum.Type.split, 6, 0, 6)) + @ddt.data((ModuleStoreEnum.Type.mongo, 3, 0, 0), (ModuleStoreEnum.Type.split, 6, 0, 5)) @ddt.unpack def test_toc_toy_from_section(self, default_ms, setup_finds, setup_sends, toc_finds): with self.store.default_store(default_ms): diff --git a/requirements/edx/github.txt b/requirements/edx/github.txt index d7fb1bbc04..3db27e48d0 100644 --- a/requirements/edx/github.txt +++ b/requirements/edx/github.txt @@ -32,7 +32,7 @@ git+https://github.com/hmarr/django-debug-toolbar-mongo.git@b0686a76f1ce3532088c -e git+https://github.com/jazkarta/ccx-keys.git@e6b03704b1bb97c1d2f31301ecb4e3a687c536ea#egg=ccx-keys # Our libraries: --e git+https://github.com/edx/XBlock.git@e1831fa86bff778ffe1308e00d8ed51b26f7c047#egg=XBlock +-e git+https://github.com/cpennington/XBlock.git@64f6bdaf6c987cdea4798d8b1a1fa8e471c20a21#egg=XBlock -e git+https://github.com/edx/codejail.git@6b17c33a89bef0ac510926b1d7fea2748b73aadd#egg=codejail -e git+https://github.com/edx/js-test-tool.git@v0.1.6#egg=js_test_tool -e git+https://github.com/edx/event-tracking.git@0.2.0#egg=event-tracking From a25f91da691c8ce37eb2854cc6ebee73d848cdd3 Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Thu, 25 Jun 2015 14:00:27 -0400 Subject: [PATCH 4/4] Fix XBlock inheritance ordering. XBlockMixin and derivatives should always come after XBlock and derivatives --- common/lib/xmodule/xmodule/course_module.py | 2 +- .../lib/xmodule/xmodule/modulestore/tests/test_mongo.py | 2 +- .../xmodule/modulestore/tests/test_xml_importer.py | 2 +- common/lib/xmodule/xmodule/video_module/video_module.py | 4 ++-- common/lib/xmodule/xmodule/video_module/video_xfields.py | 2 +- common/lib/xmodule/xmodule/x_module.py | 8 ++++---- requirements/edx/github.txt | 2 +- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py index 832791ba59..5a5066c344 100644 --- a/common/lib/xmodule/xmodule/course_module.py +++ b/common/lib/xmodule/xmodule/course_module.py @@ -919,7 +919,7 @@ class CourseModule(CourseFields, SequenceModule): # pylint: disable=abstract-me """ -class CourseDescriptor(CourseFields, LicenseMixin, SequenceDescriptor): +class CourseDescriptor(CourseFields, SequenceDescriptor, LicenseMixin): """ The descriptor for the course XModule """ diff --git a/common/lib/xmodule/xmodule/modulestore/tests/test_mongo.py b/common/lib/xmodule/xmodule/modulestore/tests/test_mongo.py index 4996449c4f..8d8f28b0d8 100644 --- a/common/lib/xmodule/xmodule/modulestore/tests/test_mongo.py +++ b/common/lib/xmodule/xmodule/modulestore/tests/test_mongo.py @@ -60,7 +60,7 @@ DEFAULT_CLASS = 'xmodule.raw_module.RawDescriptor' RENDER_TEMPLATE = lambda t_n, d, ctx=None, nsp='main': '' -class ReferenceTestXBlock(XBlock, XModuleMixin): +class ReferenceTestXBlock(XModuleMixin): """ Test xblock type to test the reference field types """ diff --git a/common/lib/xmodule/xmodule/modulestore/tests/test_xml_importer.py b/common/lib/xmodule/xmodule/modulestore/tests/test_xml_importer.py index b2b9558cf7..c39bbf4534 100644 --- a/common/lib/xmodule/xmodule/modulestore/tests/test_xml_importer.py +++ b/common/lib/xmodule/xmodule/modulestore/tests/test_xml_importer.py @@ -101,7 +101,7 @@ def render_to_template_mock(*args): pass -class StubXBlock(XBlock, XModuleMixin, InheritanceMixin): +class StubXBlock(XModuleMixin, InheritanceMixin): """ Stub XBlock used for testing. """ diff --git a/common/lib/xmodule/xmodule/video_module/video_module.py b/common/lib/xmodule/xmodule/video_module/video_module.py index 72c8c82aba..e6c79e51b4 100644 --- a/common/lib/xmodule/xmodule/video_module/video_module.py +++ b/common/lib/xmodule/xmodule/video_module/video_module.py @@ -86,7 +86,7 @@ log = logging.getLogger(__name__) _ = lambda text: text -class VideoModule(VideoFields, VideoTranscriptsMixin, VideoStudentViewHandlers, XModule): +class VideoModule(VideoFields, VideoTranscriptsMixin, VideoStudentViewHandlers, XModule, LicenseMixin): """ XML source example: