Commit 529672ac by Daniel Friedman Committed by cahrens

Reduce calls to expensive methods in create_xblock_info

Special-case the outline page requests.

STUD-1997
parent ce06e7f7
...@@ -176,7 +176,7 @@ def container_handler(request, usage_key_string): ...@@ -176,7 +176,7 @@ def container_handler(request, usage_key_string):
# Fetch the XBlock info for use by the container page. Note that it includes information # Fetch the XBlock info for use by the container page. Note that it includes information
# about the block's ancestors and siblings for use by the Unit Outline. # about the block's ancestors and siblings for use by the Unit Outline.
xblock_info = create_xblock_info(xblock, include_ancestor_info=is_unit_page, include_edited_by=True, include_published_by=True) xblock_info = create_xblock_info(xblock, include_ancestor_info=is_unit_page)
# Create the link for preview. # Create the link for preview.
preview_lms_base = settings.FEATURES.get('PREVIEW_LMS_BASE') preview_lms_base = settings.FEATURES.get('PREVIEW_LMS_BASE')
......
...@@ -239,6 +239,7 @@ def _course_outline_json(request, course_key): ...@@ -239,6 +239,7 @@ def _course_outline_json(request, course_key):
return create_xblock_info( return create_xblock_info(
course_module, course_module,
include_child_info=True, include_child_info=True,
course_outline=True,
include_children_predicate=lambda xblock: not xblock.category == 'vertical' include_children_predicate=lambda xblock: not xblock.category == 'vertical'
) )
......
...@@ -286,6 +286,7 @@ def xblock_outline_handler(request, usage_key_string): ...@@ -286,6 +286,7 @@ def xblock_outline_handler(request, usage_key_string):
return JsonResponse(create_xblock_info( return JsonResponse(create_xblock_info(
root_xblock, root_xblock,
include_child_info=True, include_child_info=True,
course_outline=True,
include_children_predicate=lambda xblock: not xblock.category == 'vertical' include_children_predicate=lambda xblock: not xblock.category == 'vertical'
)) ))
else: else:
...@@ -587,16 +588,18 @@ def _get_module_info(xblock, rewrite_static_links=True): ...@@ -587,16 +588,18 @@ def _get_module_info(xblock, rewrite_static_links=True):
def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=False, include_child_info=False, def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=False, include_child_info=False,
include_edited_by=False, include_published_by=False, include_children_predicate=NEVER): course_outline=False, include_children_predicate=NEVER):
""" """
Creates the information needed for client-side XBlockInfo. Creates the information needed for client-side XBlockInfo.
If data or metadata are not specified, their information will not be added If data or metadata are not specified, their information will not be added
(regardless of whether or not the xblock actually has data or metadata). (regardless of whether or not the xblock actually has data or metadata).
There are two optional boolean parameters: There are three optional boolean parameters:
include_ancestor_info - if true, ancestor info is added to the response include_ancestor_info - if true, ancestor info is added to the response
include_child_info - if true, direct child info is included in the response include_child_info - if true, direct child info is included in the response
course_outline - if true, the xblock is being rendered on behalf of the course outline.
There are certain expensive computations that do not need to be included in this case.
In addition, an optional include_children_predicate argument can be provided to define whether or In addition, an optional include_children_predicate argument can be provided to define whether or
not a particular xblock should have its children included. not a particular xblock should have its children included.
...@@ -622,7 +625,9 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F ...@@ -622,7 +625,9 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F
# Compute the child info first so it can be included in aggregate information for the parent # Compute the child info first so it can be included in aggregate information for the parent
if include_child_info and xblock.has_children: if include_child_info and xblock.has_children:
child_info = _create_xblock_child_info( child_info = _create_xblock_child_info(
xblock, include_children_predicate=include_children_predicate xblock,
course_outline,
include_children_predicate=include_children_predicate
) )
else: else:
child_info = None child_info = None
...@@ -630,7 +635,6 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F ...@@ -630,7 +635,6 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F
# Treat DEFAULT_START_DATE as a magic number that means the release date has not been set # Treat DEFAULT_START_DATE as a magic number that means the release date has not been set
release_date = get_default_time_display(xblock.start) if xblock.start != DEFAULT_START_DATE else None release_date = get_default_time_display(xblock.start) if xblock.start != DEFAULT_START_DATE else None
published = modulestore().has_item(xblock.location, revision=ModuleStoreEnum.RevisionOption.published_only) published = modulestore().has_item(xblock.location, revision=ModuleStoreEnum.RevisionOption.published_only)
currently_visible_to_students = is_currently_visible_to_students(xblock)
is_xblock_unit = is_unit(xblock) is_xblock_unit = is_unit(xblock)
is_unit_with_changes = is_xblock_unit and modulestore().has_changes(xblock.location) is_unit_with_changes = is_xblock_unit and modulestore().has_changes(xblock.location)
...@@ -645,8 +649,6 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F ...@@ -645,8 +649,6 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F
'studio_url': xblock_studio_url(xblock), 'studio_url': xblock_studio_url(xblock),
"released_to_students": datetime.now(UTC) > xblock.start, "released_to_students": datetime.now(UTC) > xblock.start,
"release_date": release_date, "release_date": release_date,
"release_date_from": _get_release_date_from(xblock) if release_date else None,
"currently_visible_to_students": currently_visible_to_students,
"visibility_state": _compute_visibility_state(xblock, child_info, is_unit_with_changes) if not xblock.category == 'course' else None "visibility_state": _compute_visibility_state(xblock, child_info, is_unit_with_changes) if not xblock.category == 'course' else None
} }
if data is not None: if data is not None:
...@@ -654,19 +656,19 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F ...@@ -654,19 +656,19 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F
if metadata is not None: if metadata is not None:
xblock_info["metadata"] = metadata xblock_info["metadata"] = metadata
if include_ancestor_info: if include_ancestor_info:
xblock_info['ancestor_info'] = _create_xblock_ancestor_info(xblock) xblock_info['ancestor_info'] = _create_xblock_ancestor_info(xblock, course_outline)
if child_info: if child_info:
xblock_info['child_info'] = child_info xblock_info['child_info'] = child_info
# Currently, 'edited_by' and 'published_by' are only used by the container page. Only compute them when asked to do # Currently, 'edited_by', 'published_by', and 'release_date_from', and 'has_changes' are only used by the
# so, since safe_get_username() is expensive. # container page when rendering a unit. Since they are expensive to compute, only include them for units
if include_edited_by: # that are not being rendered on the course outline.
xblock_info['edited_by'] = safe_get_username(xblock.subtree_edited_by) if is_xblock_unit and not course_outline:
if include_published_by: xblock_info["edited_by"] = safe_get_username(xblock.subtree_edited_by)
xblock_info['published_by'] = safe_get_username(xblock.published_by) xblock_info["published_by"] = safe_get_username(xblock.published_by)
# On the unit page only, add 'has_changes' to indicate when there are changes that can be discarded. xblock_info["currently_visible_to_students"] = is_currently_visible_to_students(xblock)
# We don't add it in general because it is an expensive operation.
if is_xblock_unit:
xblock_info['has_changes'] = is_unit_with_changes xblock_info['has_changes'] = is_unit_with_changes
if release_date:
xblock_info["release_date_from"] = _get_release_date_from(xblock)
return xblock_info return xblock_info
...@@ -740,7 +742,7 @@ def _compute_visibility_state(xblock, child_info, is_unit_with_changes): ...@@ -740,7 +742,7 @@ def _compute_visibility_state(xblock, child_info, is_unit_with_changes):
return VisibilityState.ready return VisibilityState.ready
def _create_xblock_ancestor_info(xblock): def _create_xblock_ancestor_info(xblock, course_outline):
""" """
Returns information about the ancestors of an xblock. Note that the direct parent will also return Returns information about the ancestors of an xblock. Note that the direct parent will also return
information about all of its children. information about all of its children.
...@@ -756,6 +758,7 @@ def _create_xblock_ancestor_info(xblock): ...@@ -756,6 +758,7 @@ def _create_xblock_ancestor_info(xblock):
ancestors.append(create_xblock_info( ancestors.append(create_xblock_info(
ancestor, ancestor,
include_child_info=include_child_info, include_child_info=include_child_info,
course_outline=course_outline,
include_children_predicate=direct_children_only include_children_predicate=direct_children_only
)) ))
collect_ancestor_info(get_parent_xblock(ancestor)) collect_ancestor_info(get_parent_xblock(ancestor))
...@@ -765,7 +768,7 @@ def _create_xblock_ancestor_info(xblock): ...@@ -765,7 +768,7 @@ def _create_xblock_ancestor_info(xblock):
} }
def _create_xblock_child_info(xblock, include_children_predicate=NEVER): def _create_xblock_child_info(xblock, course_outline, include_children_predicate=NEVER):
""" """
Returns information about the children of an xblock, as well as about the primary category Returns information about the children of an xblock, as well as about the primary category
of xblock expected as children. of xblock expected as children.
...@@ -780,7 +783,8 @@ def _create_xblock_child_info(xblock, include_children_predicate=NEVER): ...@@ -780,7 +783,8 @@ def _create_xblock_child_info(xblock, include_children_predicate=NEVER):
if xblock.has_children and include_children_predicate(xblock): if xblock.has_children and include_children_predicate(xblock):
child_info['children'] = [ child_info['children'] = [
create_xblock_info( create_xblock_info(
child, include_child_info=True, include_children_predicate=include_children_predicate child, include_child_info=True, course_outline=course_outline,
include_children_predicate=include_children_predicate
) for child in xblock.get_children() ) for child in xblock.get_children()
] ]
return child_info return child_info
......
...@@ -1108,7 +1108,7 @@ class TestXBlockInfo(ItemTest): ...@@ -1108,7 +1108,7 @@ class TestXBlockInfo(ItemTest):
outline_url = reverse_usage_url('xblock_outline_handler', self.usage_key) outline_url = reverse_usage_url('xblock_outline_handler', self.usage_key)
resp = self.client.get(outline_url, HTTP_ACCEPT='application/json') resp = self.client.get(outline_url, HTTP_ACCEPT='application/json')
json_response = json.loads(resp.content) json_response = json.loads(resp.content)
self.validate_course_xblock_info(json_response) self.validate_course_xblock_info(json_response, course_outline=True)
def test_chapter_xblock_info(self): def test_chapter_xblock_info(self):
chapter = modulestore().get_item(self.chapter.location) chapter = modulestore().get_item(self.chapter.location)
...@@ -1133,7 +1133,6 @@ class TestXBlockInfo(ItemTest): ...@@ -1133,7 +1133,6 @@ class TestXBlockInfo(ItemTest):
xblock_info = create_xblock_info( xblock_info = create_xblock_info(
vertical, vertical,
include_child_info=True, include_child_info=True,
include_edited_by=True,
include_children_predicate=ALWAYS, include_children_predicate=ALWAYS,
include_ancestor_info=True include_ancestor_info=True
) )
...@@ -1148,7 +1147,7 @@ class TestXBlockInfo(ItemTest): ...@@ -1148,7 +1147,7 @@ class TestXBlockInfo(ItemTest):
) )
self.validate_component_xblock_info(xblock_info) self.validate_component_xblock_info(xblock_info)
def validate_course_xblock_info(self, xblock_info, has_child_info=True): def validate_course_xblock_info(self, xblock_info, has_child_info=True, course_outline=False):
""" """
Validate that the xblock info is correct for the test course. Validate that the xblock info is correct for the test course.
""" """
...@@ -1158,7 +1157,7 @@ class TestXBlockInfo(ItemTest): ...@@ -1158,7 +1157,7 @@ class TestXBlockInfo(ItemTest):
self.assertTrue(xblock_info['published']) self.assertTrue(xblock_info['published'])
# Finally, validate the entire response for consistency # Finally, validate the entire response for consistency
self.validate_xblock_info_consistency(xblock_info, has_child_info=has_child_info) self.validate_xblock_info_consistency(xblock_info, has_child_info=has_child_info, course_outline=course_outline)
def validate_chapter_xblock_info(self, xblock_info, has_child_info=True): def validate_chapter_xblock_info(self, xblock_info, has_child_info=True):
""" """
...@@ -1168,18 +1167,20 @@ class TestXBlockInfo(ItemTest): ...@@ -1168,18 +1167,20 @@ class TestXBlockInfo(ItemTest):
self.assertEqual(xblock_info['id'], 'i4x://MITx/999/chapter/Week_1') self.assertEqual(xblock_info['id'], 'i4x://MITx/999/chapter/Week_1')
self.assertEqual(xblock_info['display_name'], 'Week 1') self.assertEqual(xblock_info['display_name'], 'Week 1')
self.assertTrue(xblock_info['published']) self.assertTrue(xblock_info['published'])
self.assertIsNone(xblock_info.get('edited_by', None))
# Finally, validate the entire response for consistency # Finally, validate the entire response for consistency
self.validate_xblock_info_consistency(xblock_info, has_child_info=has_child_info) self.validate_xblock_info_consistency(xblock_info, has_child_info=has_child_info)
def validate_sequential_xblock_info(self, xblock_info, has_child_info=True): def validate_sequential_xblock_info(self, xblock_info, has_child_info=True):
""" """
Validate that the xblock info is correct for the test chapter. Validate that the xblock info is correct for the test sequential.
""" """
self.assertEqual(xblock_info['category'], 'sequential') self.assertEqual(xblock_info['category'], 'sequential')
self.assertEqual(xblock_info['id'], 'i4x://MITx/999/sequential/Lesson_1') self.assertEqual(xblock_info['id'], 'i4x://MITx/999/sequential/Lesson_1')
self.assertEqual(xblock_info['display_name'], 'Lesson 1') self.assertEqual(xblock_info['display_name'], 'Lesson 1')
self.assertTrue(xblock_info['published']) self.assertTrue(xblock_info['published'])
self.assertIsNone(xblock_info.get('edited_by', None))
# Finally, validate the entire response for consistency # Finally, validate the entire response for consistency
self.validate_xblock_info_consistency(xblock_info, has_child_info=has_child_info) self.validate_xblock_info_consistency(xblock_info, has_child_info=has_child_info)
...@@ -1204,7 +1205,7 @@ class TestXBlockInfo(ItemTest): ...@@ -1204,7 +1205,7 @@ class TestXBlockInfo(ItemTest):
self.validate_course_xblock_info(ancestors[2], has_child_info=False) self.validate_course_xblock_info(ancestors[2], has_child_info=False)
# Finally, validate the entire response for consistency # Finally, validate the entire response for consistency
self.validate_xblock_info_consistency(xblock_info, has_child_info=True, has_ancestor_info=True, has_edited_by=True) self.validate_xblock_info_consistency(xblock_info, has_child_info=True, has_ancestor_info=True)
def validate_component_xblock_info(self, xblock_info): def validate_component_xblock_info(self, xblock_info):
""" """
...@@ -1214,11 +1215,13 @@ class TestXBlockInfo(ItemTest): ...@@ -1214,11 +1215,13 @@ class TestXBlockInfo(ItemTest):
self.assertEqual(xblock_info['id'], 'i4x://MITx/999/video/My_Video') self.assertEqual(xblock_info['id'], 'i4x://MITx/999/video/My_Video')
self.assertEqual(xblock_info['display_name'], 'My Video') self.assertEqual(xblock_info['display_name'], 'My Video')
self.assertTrue(xblock_info['published']) self.assertTrue(xblock_info['published'])
self.assertIsNone(xblock_info.get('edited_by', None))
# Finally, validate the entire response for consistency # Finally, validate the entire response for consistency
self.validate_xblock_info_consistency(xblock_info) self.validate_xblock_info_consistency(xblock_info)
def validate_xblock_info_consistency(self, xblock_info, has_ancestor_info=False, has_child_info=False, has_edited_by=False): def validate_xblock_info_consistency(self, xblock_info, has_ancestor_info=False, has_child_info=False,
course_outline=False):
""" """
Validate that the xblock info is internally consistent. Validate that the xblock info is internally consistent.
""" """
...@@ -1232,7 +1235,8 @@ class TestXBlockInfo(ItemTest): ...@@ -1232,7 +1235,8 @@ class TestXBlockInfo(ItemTest):
for ancestor in xblock_info['ancestor_info']['ancestors']: for ancestor in xblock_info['ancestor_info']['ancestors']:
self.validate_xblock_info_consistency( self.validate_xblock_info_consistency(
ancestor, ancestor,
has_child_info=(ancestor == ancestors[0]) # Only the direct ancestor includes children has_child_info=(ancestor == ancestors[0]), # Only the direct ancestor includes children
course_outline=course_outline
) )
else: else:
self.assertIsNone(xblock_info.get('ancestor_info', None)) self.assertIsNone(xblock_info.get('ancestor_info', None))
...@@ -1242,11 +1246,12 @@ class TestXBlockInfo(ItemTest): ...@@ -1242,11 +1246,12 @@ class TestXBlockInfo(ItemTest):
for child_response in xblock_info['child_info']['children']: for child_response in xblock_info['child_info']['children']:
self.validate_xblock_info_consistency( self.validate_xblock_info_consistency(
child_response, child_response,
has_child_info=(not child_response.get('child_info', None) is None) has_child_info=(not child_response.get('child_info', None) is None),
course_outline=course_outline
) )
else: else:
self.assertIsNone(xblock_info.get('child_info', None)) self.assertIsNone(xblock_info.get('child_info', None))
if has_edited_by: if xblock_info['category'] == 'vertical' and not course_outline:
self.assertEqual(xblock_info['edited_by'], 'testuser') self.assertEqual(xblock_info['edited_by'], 'testuser')
else: else:
self.assertIsNone(xblock_info.get('edited_by', None)) self.assertIsNone(xblock_info.get('edited_by', None))
......
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