We heard about a bug where learners granted extensions would still lose access to content if it was marked as "hidden after due date". This was caused by the HiddenContentTransformer using the due date from collection time (publish time) rather than the user date returned from the edx-when DateOverrideTransformer. A small subtletly of this PR is that Transformers with the FilteringTransformerMixin are executed before those without it so part of the fix was to make the HiddenContentTransformer not use the FilteringTransformerMixin to ensure the DateOverrideTransformer had run first. Part 3/3 - Removing old collect code with merged due date
95 lines
3.5 KiB
Python
95 lines
3.5 KiB
Python
"""
|
|
Visibility Transformer implementation.
|
|
"""
|
|
|
|
|
|
from datetime import datetime
|
|
|
|
from pytz import utc
|
|
|
|
from openedx.core.djangoapps.content.block_structure.transformer import BlockStructureTransformer
|
|
from xmodule.seq_module import SequenceBlock
|
|
|
|
from .utils import collect_merged_boolean_field
|
|
|
|
MAXIMUM_DATE = utc.localize(datetime.max)
|
|
|
|
|
|
class HiddenContentTransformer(BlockStructureTransformer):
|
|
"""
|
|
A transformer that enforces the hide_after_due field on
|
|
blocks by removing children blocks from the block structure for
|
|
which the user does not have access. The hide_after_due
|
|
field on a block is percolated down to its descendants, so that
|
|
all blocks enforce the hidden content settings from their ancestors.
|
|
|
|
For a block with multiple parents, access is denied only if
|
|
access is denied from all its parents.
|
|
|
|
Staff users are exempted from hidden content rules.
|
|
|
|
IMPORTANT: Must be run _after_ the DateOverrideTransformer from edx-when
|
|
in case the 'due' date on a block has been shifted for a user.
|
|
"""
|
|
WRITE_VERSION = 3
|
|
READ_VERSION = 3
|
|
MERGED_HIDE_AFTER_DUE = 'merged_hide_after_due'
|
|
|
|
@classmethod
|
|
def name(cls):
|
|
"""
|
|
Unique identifier for the transformer's class;
|
|
same identifier used in setup.py.
|
|
"""
|
|
return "hidden_content"
|
|
|
|
@classmethod
|
|
def _get_merged_hide_after_due(cls, block_structure, block_key):
|
|
"""
|
|
Returns whether the block with the given block_key in the
|
|
given block_structure should be hidden after due date per
|
|
computed value from ancestry chain.
|
|
"""
|
|
return block_structure.get_transformer_block_field(
|
|
block_key, cls, cls.MERGED_HIDE_AFTER_DUE, False
|
|
)
|
|
|
|
@classmethod
|
|
def collect(cls, block_structure):
|
|
"""
|
|
Collects any information that's necessary to execute this
|
|
transformer's transform method.
|
|
"""
|
|
collect_merged_boolean_field(
|
|
block_structure,
|
|
transformer=cls,
|
|
xblock_field_name='hide_after_due',
|
|
merged_field_name=cls.MERGED_HIDE_AFTER_DUE,
|
|
)
|
|
|
|
block_structure.request_xblock_fields('self_paced', 'end', 'due')
|
|
|
|
def transform(self, usage_info, block_structure):
|
|
# Users with staff access bypass the Visibility check.
|
|
if usage_info.has_staff_access:
|
|
return [block_structure.create_universal_filter()]
|
|
|
|
block_structure.remove_block_traversal(lambda block_key: self._is_block_hidden(block_structure, block_key))
|
|
|
|
def _is_block_hidden(self, block_structure, block_key):
|
|
"""
|
|
Returns whether the block with the given block_key should
|
|
be hidden, given the current time.
|
|
"""
|
|
hide_after_due = self._get_merged_hide_after_due(block_structure, block_key)
|
|
self_paced = block_structure[block_structure.root_block_usage_key].self_paced
|
|
if self_paced:
|
|
hidden_date = block_structure[block_structure.root_block_usage_key].end
|
|
else:
|
|
# Important Note:
|
|
# A small subtlety of grabbing the due date here is that this transformer relies on the
|
|
# DateOverrideTransformer (located in edx-when repo) to first set any overrides (one
|
|
# example is a user receiving an extension on an assignment).
|
|
hidden_date = block_structure.get_xblock_field(block_key, 'due', None) or MAXIMUM_DATE
|
|
return not SequenceBlock.verify_current_content_visibility(hidden_date, hide_after_due)
|