Files
edx-platform/lms/djangoapps/course_api/blocks/api.py
Dillon Dumesnil 6f773d1eca feat: AA-802: Update the BlockCompletionTransformer
We discovered a subsection that contained a unit without any content
inside, but because of our logic requiring children, it would never be
marked complete (meaning the subsection, section, and course could thus
never be marked complete). This fixes that by removing the children
check from setting completion, but first gating that code path on the
xblock being an aggregator (to prevent leaves from marking as true
simply because there are no children).

Test fixes include adding a test for the empty aggregator case as
well as some changes to not have an entire course marked complete
because they are all empty aggregators.
2021-05-14 11:22:07 -04:00

151 lines
6.5 KiB
Python

"""
API function for retrieving course blocks data
"""
import lms.djangoapps.course_blocks.api as course_blocks_api
from lms.djangoapps.course_blocks.transformers.access_denied_filter import AccessDeniedMessageFilterTransformer
from lms.djangoapps.course_blocks.transformers.hidden_content import HiddenContentTransformer
from lms.djangoapps.course_blocks.transformers.hide_empty import HideEmptyTransformer
from openedx.core.djangoapps.content.block_structure.transformers import BlockStructureTransformers
from openedx.core.lib.mobile_utils import is_request_from_mobile_app
from openedx.features.effort_estimation.api import EffortEstimationTransformer
from .serializers import BlockDictSerializer, BlockSerializer
from .toggles import HIDE_ACCESS_DENIALS_FLAG
from .transformers.blocks_api import BlocksAPITransformer
from .transformers.milestones import MilestonesAndSpecialExamsTransformer
def get_blocks(
request,
usage_key,
user=None,
depth=None,
nav_depth=None,
requested_fields=None,
block_counts=None,
student_view_data=None,
return_type='dict',
block_types_filter=None,
hide_access_denials=False,
allow_start_dates_in_future=False,
):
"""
Return a serialized representation of the course blocks.
Arguments:
request (HTTPRequest): Used for calling django reverse.
usage_key (UsageKey): Identifies the starting block of interest.
user (User): Optional user object for whom the blocks are being
retrieved. If None, blocks are returned regardless of access checks.
depth (integer or None): Identifies the depth of the tree to return
starting at the root block. If None, the entire tree starting at
the root is returned.
nav_depth (integer): Optional parameter that indicates how far deep to
traverse into the block hierarchy before bundling all the
descendants for navigation.
requested_fields (list): Optional list of names of additional fields
to return for each block. Supported fields are listed in
transformers.SUPPORTED_FIELDS.
block_counts (list): Optional list of names of block types for which to
return an aggregate count of blocks.
student_view_data (list): Optional list of names of block types for
which blocks to return their student_view_data.
return_type (string): Possible values are 'dict' or 'list'. Indicates
the format for returning the blocks.
block_types_filter (list): Optional list of block type names used to filter
the final result of returned blocks.
hide_access_denials (bool): When True, filter out any blocks that were
denied access to the user, even if they have access denial messages
attached.
allow_start_dates_in_future (bool): When True, will allow blocks to be
returned that can bypass the StartDateTransformer's filter to show
blocks with start dates in the future.
"""
if HIDE_ACCESS_DENIALS_FLAG.is_enabled():
hide_access_denials = True
# create ordered list of transformers, adding BlocksAPITransformer at end.
transformers = BlockStructureTransformers()
if requested_fields is None:
requested_fields = []
include_completion = 'completion' in requested_fields
include_effort_estimation = (EffortEstimationTransformer.EFFORT_TIME in requested_fields or
EffortEstimationTransformer.EFFORT_ACTIVITIES in requested_fields)
include_gated_sections = 'show_gated_sections' in requested_fields
include_has_scheduled_content = 'has_scheduled_content' in requested_fields
include_special_exams = 'special_exam_info' in requested_fields
if user is not None:
transformers += course_blocks_api.get_course_block_access_transformers(user)
transformers += [
MilestonesAndSpecialExamsTransformer(
include_special_exams=include_special_exams,
include_gated_sections=include_gated_sections
),
HiddenContentTransformer()
]
# Note: A change to the BlockCompletionTransformer (https://github.com/edx/edx-platform/pull/27622/)
# will be introducing a bug if hide_access_denials is True. I'm accepting this risk because in
# the AccessDeniedMessageFilterTransformer, there is note about deleting it and I believe it is
# technically deprecated functionality. The only use case where hide_access_denials is True
# (outside of explicitly setting the temporary waffle flag) is in lms/djangoapps/course_api/blocks/urls.py
# for a v1 api that I also believe should have been deprecated and removed. When this code is removed,
# please also remove this comment. Thanks!
if hide_access_denials:
transformers += [AccessDeniedMessageFilterTransformer()]
# TODO: Remove this after REVE-52 lands and old-mobile-app traffic falls to < 5% of mobile traffic
if is_request_from_mobile_app(request):
transformers += [HideEmptyTransformer()]
if include_effort_estimation:
transformers += [EffortEstimationTransformer()]
transformers += [
BlocksAPITransformer(
block_counts,
student_view_data,
depth,
nav_depth
)
]
# transform
blocks = course_blocks_api.get_course_blocks(
user,
usage_key,
transformers,
allow_start_dates_in_future=allow_start_dates_in_future,
include_completion=include_completion,
include_has_scheduled_content=include_has_scheduled_content
)
# filter blocks by types
if block_types_filter:
block_keys_to_remove = []
for block_key in blocks:
block_type = blocks.get_xblock_field(block_key, 'category')
if block_type not in block_types_filter:
block_keys_to_remove.append(block_key)
for block_key in block_keys_to_remove:
blocks.remove_block(block_key, keep_descendants=True)
# serialize
serializer_context = {
'request': request,
'block_structure': blocks,
'requested_fields': requested_fields or [],
}
if return_type == 'dict':
serializer = BlockDictSerializer(blocks, context=serializer_context, many=False)
else:
serializer = BlockSerializer(blocks, context=serializer_context, many=True)
# return serialized data
return serializer.data