This adds support for displaying completion on the course outline page, to remove the discrepancies between this view and the learning sequence. It also simplifies course outline page by reusing existing APIs for determining completion state and finding the "Resume block"'s target.
208 lines
8.1 KiB
Python
208 lines
8.1 KiB
Python
"""
|
|
Serializers for Course Blocks related return objects.
|
|
"""
|
|
|
|
|
|
from django.conf import settings
|
|
from rest_framework import serializers
|
|
from rest_framework.reverse import reverse
|
|
|
|
from lms.djangoapps.course_blocks.transformers.visibility import VisibilityTransformer
|
|
|
|
from .transformers.block_completion import BlockCompletionTransformer
|
|
from .transformers.block_counts import BlockCountsTransformer
|
|
from .transformers.extra_fields import ExtraFieldsTransformer
|
|
from .transformers.milestones import MilestonesAndSpecialExamsTransformer
|
|
from .transformers.navigation import BlockNavigationTransformer
|
|
from .transformers.student_view import StudentViewTransformer
|
|
|
|
|
|
class SupportedFieldType:
|
|
"""
|
|
Metadata about fields supported by different transformers
|
|
"""
|
|
def __init__(
|
|
self,
|
|
block_field_name,
|
|
transformer=None,
|
|
requested_field_name=None,
|
|
serializer_field_name=None,
|
|
default_value=None
|
|
):
|
|
self.transformer = transformer
|
|
self.block_field_name = block_field_name
|
|
self.requested_field_name = requested_field_name or block_field_name
|
|
self.serializer_field_name = serializer_field_name or self.requested_field_name
|
|
self.default_value = default_value
|
|
|
|
|
|
# A list of metadata for additional requested fields to be used by the
|
|
# BlockSerializer` class. Each entry provides information on how that field can
|
|
# be requested (`requested_field_name`), can be found (`transformer` and
|
|
# `block_field_name`), and should be serialized (`serializer_field_name` and
|
|
# `default_value`).
|
|
|
|
SUPPORTED_FIELDS = [
|
|
SupportedFieldType('category', requested_field_name='type'),
|
|
SupportedFieldType('display_name', default_value=''),
|
|
SupportedFieldType('effort_activities'),
|
|
SupportedFieldType('effort_time'),
|
|
SupportedFieldType('graded'),
|
|
SupportedFieldType('format'),
|
|
SupportedFieldType('start'),
|
|
SupportedFieldType('due'),
|
|
SupportedFieldType('contains_gated_content'),
|
|
SupportedFieldType('has_score'),
|
|
SupportedFieldType('has_scheduled_content'),
|
|
SupportedFieldType('weight'),
|
|
SupportedFieldType('show_correctness'),
|
|
# 'student_view_data'
|
|
SupportedFieldType(StudentViewTransformer.STUDENT_VIEW_DATA, StudentViewTransformer),
|
|
# 'student_view_multi_device'
|
|
SupportedFieldType(StudentViewTransformer.STUDENT_VIEW_MULTI_DEVICE, StudentViewTransformer),
|
|
|
|
SupportedFieldType('special_exam_info', MilestonesAndSpecialExamsTransformer),
|
|
|
|
# set the block_field_name to None so the entire data for the transformer is serialized
|
|
SupportedFieldType(None, BlockCountsTransformer, BlockCountsTransformer.BLOCK_COUNTS),
|
|
|
|
SupportedFieldType(
|
|
BlockNavigationTransformer.BLOCK_NAVIGATION,
|
|
BlockNavigationTransformer,
|
|
requested_field_name='nav_depth',
|
|
serializer_field_name='descendants',
|
|
),
|
|
|
|
# Provide the staff visibility info stored when VisibilityTransformer ran previously
|
|
SupportedFieldType(
|
|
'merged_visible_to_staff_only',
|
|
VisibilityTransformer,
|
|
requested_field_name='visible_to_staff_only',
|
|
),
|
|
SupportedFieldType(BlockCompletionTransformer.COMPLETION, BlockCompletionTransformer),
|
|
SupportedFieldType(BlockCompletionTransformer.COMPLETE),
|
|
SupportedFieldType(BlockCompletionTransformer.RESUME_BLOCK),
|
|
|
|
*[SupportedFieldType(field_name) for field_name in ExtraFieldsTransformer.get_requested_extra_fields()],
|
|
]
|
|
|
|
# This lists the names of all fields that are allowed
|
|
# to be show to users who do not have access to a particular piece
|
|
# of content
|
|
FIELDS_ALLOWED_IN_AUTH_DENIED_CONTENT = [
|
|
"display_name",
|
|
"block_id",
|
|
"student_view_url",
|
|
"student_view_multi_device",
|
|
"lms_web_url",
|
|
"type",
|
|
"id",
|
|
"block_counts",
|
|
"graded",
|
|
"descendants",
|
|
"authorization_denial_reason",
|
|
"authorization_denial_message",
|
|
]
|
|
|
|
|
|
class BlockSerializer(serializers.Serializer): # pylint: disable=abstract-method
|
|
"""
|
|
Serializer for single course block
|
|
"""
|
|
def _get_field(self, block_key, transformer, field_name, default):
|
|
"""
|
|
Get the field value requested. The field may be an XBlock field, a
|
|
transformer block field, or an entire tranformer block data dict.
|
|
"""
|
|
value = None
|
|
if transformer is None:
|
|
value = self.context['block_structure'].get_xblock_field(block_key, field_name)
|
|
elif field_name is None:
|
|
try:
|
|
value = self.context['block_structure'].get_transformer_block_data(block_key, transformer).fields
|
|
except KeyError:
|
|
pass
|
|
else:
|
|
value = self.context['block_structure'].get_transformer_block_field(block_key, transformer, field_name)
|
|
|
|
return value if (value is not None) else default
|
|
|
|
def to_representation(self, block_key): # lint-amnesty, pylint: disable=arguments-differ
|
|
"""
|
|
Return a serializable representation of the requested block
|
|
"""
|
|
# create response data dict for basic fields
|
|
|
|
block_structure = self.context['block_structure']
|
|
authorization_denial_reason = block_structure.get_xblock_field(block_key, 'authorization_denial_reason')
|
|
authorization_denial_message = block_structure.get_xblock_field(block_key, 'authorization_denial_message')
|
|
|
|
data = {
|
|
'id': str(block_key),
|
|
'block_id': str(block_key.block_id),
|
|
'lms_web_url': reverse(
|
|
'jump_to',
|
|
kwargs={'course_id': str(block_key.course_key), 'location': str(block_key)},
|
|
request=self.context['request'],
|
|
),
|
|
'student_view_url': reverse(
|
|
'render_xblock',
|
|
kwargs={'usage_key_string': str(block_key)},
|
|
request=self.context['request'],
|
|
),
|
|
}
|
|
|
|
if settings.FEATURES.get("ENABLE_LTI_PROVIDER") and 'lti_url' in self.context['requested_fields']:
|
|
data['lti_url'] = reverse(
|
|
'lti_provider_launch',
|
|
kwargs={'course_id': str(block_key.course_key), 'usage_id': str(block_key)},
|
|
request=self.context['request'],
|
|
)
|
|
|
|
# add additional requested fields that are supported by the various transformers
|
|
for supported_field in SUPPORTED_FIELDS:
|
|
if supported_field.requested_field_name in self.context['requested_fields']:
|
|
field_value = self._get_field(
|
|
block_key,
|
|
supported_field.transformer,
|
|
supported_field.block_field_name,
|
|
supported_field.default_value,
|
|
)
|
|
if field_value is not None:
|
|
# only return fields that have data
|
|
data[supported_field.serializer_field_name] = field_value
|
|
|
|
if 'children' in self.context['requested_fields']:
|
|
children = block_structure.get_children(block_key)
|
|
if children:
|
|
data['children'] = [str(child) for child in children]
|
|
|
|
if authorization_denial_reason and authorization_denial_message:
|
|
data['authorization_denial_reason'] = authorization_denial_reason
|
|
data['authorization_denial_message'] = authorization_denial_message
|
|
cleaned_data = data.copy()
|
|
for field in data.keys(): # pylint: disable=consider-iterating-dictionary
|
|
if field not in FIELDS_ALLOWED_IN_AUTH_DENIED_CONTENT:
|
|
del cleaned_data[field]
|
|
data = cleaned_data
|
|
|
|
return data
|
|
|
|
|
|
class BlockDictSerializer(serializers.Serializer): # pylint: disable=abstract-method
|
|
"""
|
|
Serializer that formats a BlockStructure object to a dictionary, rather
|
|
than a list, of blocks
|
|
"""
|
|
root = serializers.CharField(source='root_block_usage_key')
|
|
blocks = serializers.SerializerMethodField()
|
|
|
|
def get_blocks(self, structure):
|
|
"""
|
|
Serialize to a dictionary of blocks keyed by the block's usage_key.
|
|
"""
|
|
return {
|
|
str(block_key): BlockSerializer(block_key, context=self.context).data
|
|
for block_key in structure
|
|
}
|