From 3f6baa992d59da1c157fd502aa32d61299c65298 Mon Sep 17 00:00:00 2001 From: Nimisha Asthagiri Date: Wed, 28 Oct 2015 19:06:32 -0400 Subject: [PATCH] Transformer: VisibilityTransformer --- .../transformers/tests/__init__.py | 0 .../transformers/tests/test_visibility.py | 43 ++++++++++ .../course_blocks/transformers/visibility.py | 80 +++++++++++++++++++ 3 files changed, 123 insertions(+) create mode 100644 lms/djangoapps/course_blocks/transformers/tests/__init__.py create mode 100644 lms/djangoapps/course_blocks/transformers/tests/test_visibility.py create mode 100644 lms/djangoapps/course_blocks/transformers/visibility.py diff --git a/lms/djangoapps/course_blocks/transformers/tests/__init__.py b/lms/djangoapps/course_blocks/transformers/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lms/djangoapps/course_blocks/transformers/tests/test_visibility.py b/lms/djangoapps/course_blocks/transformers/tests/test_visibility.py new file mode 100644 index 0000000000..c6841e707a --- /dev/null +++ b/lms/djangoapps/course_blocks/transformers/tests/test_visibility.py @@ -0,0 +1,43 @@ +""" +Tests for VisibilityTransformer. +""" +import ddt + +from ..visibility import VisibilityTransformer +from .test_helpers import BlockParentsMapTestCase, update_block + + +@ddt.ddt +class VisibilityTransformerTestCase(BlockParentsMapTestCase): + """ + VisibilityTransformer Test + """ + # Following test cases are based on BlockParentsMapTestCase.parents_map + @ddt.data( + ({}, {0, 1, 2, 3, 4, 5, 6}, {}), + ({0}, {}, {1, 2, 3, 4, 5, 6}), + ({1}, {0, 2, 5, 6}, {3, 4}), + ({2}, {0, 1, 3, 4, 6}, {5}), + ({3}, {0, 1, 2, 4, 5, 6}, {}), + ({4}, {0, 1, 2, 3, 5, 6}, {}), + ({5}, {0, 1, 2, 3, 4, 6}, {}), + ({6}, {0, 1, 2, 3, 4, 5}, {}), + ({1, 2}, {0}, {3, 4, 5, 6}), + ({2, 4}, {0, 1, 3}, {5, 6}), + ({1, 2, 3, 4, 5, 6}, {0}, {}), + ) + @ddt.unpack + def test_block_visibility( + self, staff_only_blocks, expected_visible_blocks, blocks_with_differing_access + ): + for idx, _ in enumerate(self.parents_map): + block = self.get_block(idx) + block.visible_to_staff_only = (idx in staff_only_blocks) + update_block(block) + + self.assert_transform_results( + self.student, + expected_visible_blocks, + blocks_with_differing_access, + [VisibilityTransformer()], + ) diff --git a/lms/djangoapps/course_blocks/transformers/visibility.py b/lms/djangoapps/course_blocks/transformers/visibility.py new file mode 100644 index 0000000000..cde91c1ad3 --- /dev/null +++ b/lms/djangoapps/course_blocks/transformers/visibility.py @@ -0,0 +1,80 @@ +""" +Visibility Transformer implementation. +""" +from openedx.core.lib.block_cache.transformer import BlockStructureTransformer + + +class VisibilityTransformer(BlockStructureTransformer): + """ + A transformer that enforces the visible_to_staff_only field on + blocks by removing blocks from the block structure for which the + user does not have access. The visible_to_staff_only field on a + block is percolated down to its descendants, so that all blocks + enforce the visibility settings from their ancestors. + + For a block with multiple parents, access is denied only if + visibility is denied for all its parents. + + Staff users are exempted from visibility rules. + """ + VERSION = 1 + + MERGED_VISIBLE_TO_STAFF_ONLY = 'merged_visible_to_staff_only' + + @classmethod + def name(cls): + """ + Unique identifier for the transformer's class; + same identifier used in setup.py. + """ + return "visibility" + + @classmethod + def get_visible_to_staff_only(cls, block_structure, block_key): + """ + Returns whether the block with the given block_key in the + given block_structure should be visible to staff only per + computed value from ancestry chain. + """ + return block_structure.get_transformer_block_field( + block_key, cls, cls.MERGED_VISIBLE_TO_STAFF_ONLY, False + ) + + @classmethod + def collect(cls, block_structure): + """ + Collects any information that's necessary to execute this + transformer's transform method. + """ + for block_key in block_structure.topological_traversal(): + + # compute merged value of visible_to_staff_only from all parents + parents = block_structure.get_parents(block_key) + all_parents_visible_to_staff_only = all( # pylint: disable=invalid-name + cls.get_visible_to_staff_only(block_structure, parent_key) + for parent_key in parents + ) if parents else False + + # set the merged value for this block + block_structure.set_transformer_block_field( + block_key, + cls, + cls.MERGED_VISIBLE_TO_STAFF_ONLY, + # merge visible_to_staff_only from all parents and this block + ( + all_parents_visible_to_staff_only or + block_structure.get_xblock(block_key).visible_to_staff_only + ) + ) + + def transform(self, usage_info, block_structure): + """ + Mutates block_structure based on the given usage_info. + """ + # Users with staff access bypass the Visibility check. + if usage_info.has_staff_access: + return + + block_structure.remove_block_if( + lambda block_key: self.get_visible_to_staff_only(block_structure, block_key) + )