Return content type gate for staff users when masquerading as the Learner in Audit or Learner in Limited Access Roles

This is necessary to display the content type gate in the UI
AA-613
This commit is contained in:
Matthew Piatetsky
2021-02-01 22:15:42 -05:00
parent 71b15df66c
commit ae7d0a1ed8
6 changed files with 117 additions and 91 deletions

View File

@@ -27,7 +27,6 @@ from xblock.fields import Boolean, Integer, List, Scope, String
from edx_toggles.toggles import LegacyWaffleFlag
from edx_toggles.toggles import WaffleFlag
from lms.djangoapps.courseware.toggles import COURSEWARE_PROCTORING_IMPROVEMENTS
from openedx.core.lib.graph_traversals import get_children, leaf_filter, traverse_pre_order
from .exceptions import NotFoundError
from .fields import Date
@@ -283,37 +282,6 @@ class SequenceModule(SequenceFields, ProctoringFields, XModule):
datetime.now(UTC) < date
)
def _get_user(self):
"""
Return the current runtime Django user.
"""
return get_user_model().objects.get(id=self.runtime.user_id)
def _check_children_for_content_type_gating_paywall(self, item):
"""
If:
This xblock contains problems which this user cannot load due to content type gating
Then:
Return the first content type gating paywall (Fragment)
Else:
Return None
"""
try:
user = self._get_user()
course_id = self.runtime.course_id
content_type_gating_service = self.runtime.service(self, 'content_type_gating')
if not (content_type_gating_service and
content_type_gating_service.enabled_for_enrollment(user=user, course_key=course_id)):
return None
for block in traverse_pre_order(item, get_children, leaf_filter):
gate_fragment = content_type_gating_service.content_type_gate_for_block(user, block, course_id)
if gate_fragment is not None:
return gate_fragment.content
except get_user_model().DoesNotExist:
pass
return None
def gate_entire_sequence_if_it_is_a_timed_exam_and_contains_content_type_gated_problems(self):
"""
Problem:
@@ -344,7 +312,11 @@ class SequenceModule(SequenceFields, ProctoringFields, XModule):
Note that this will break compatability with using sequences outside of edx-platform
but we are ok with this for now
"""
self.gated_sequence_paywall = self._check_children_for_content_type_gating_paywall(self)
content_type_gating_service = self.runtime.service(self, 'content_type_gating')
if content_type_gating_service:
self.gated_sequence_paywall = content_type_gating_service.check_children_for_content_type_gating_paywall(
self, self.course_id
)
def student_view(self, context):
_ = self.runtime.service(self, "i18n").ugettext
@@ -669,7 +641,12 @@ class SequenceModule(SequenceFields, ProctoringFields, XModule):
else:
content = ''
contains_content_type_gated_content = self._check_children_for_content_type_gating_paywall(item) is not None
content_type_gating_service = self.runtime.service(self, 'content_type_gating')
contains_content_type_gated_content = False
if content_type_gating_service:
contains_content_type_gated_content = content_type_gating_service.check_children_for_content_type_gating_paywall( # pylint:disable=line-too-long
item, self.course_id
) is not None
iteminfo = {
'content': content,
'page_title': getattr(item, 'tooltip_title', ''),

View File

@@ -144,13 +144,12 @@ class SequenceBlockTestCase(XModuleXmlImportTest):
seq_module = SequenceModule(runtime=Mock(position=2), descriptor=Mock(), scope_ids=Mock())
self.assertEqual(seq_module.position, 2) # matches position set in the runtime
@patch('xmodule.seq_module.SequenceModule._get_user', return_value=UserFactory.build())
@ddt.unpack
@ddt.data(
{'view': STUDENT_VIEW},
{'view': PUBLIC_VIEW},
)
def test_render_student_view(self, mocked_user, view): # pylint: disable=unused-argument
def test_render_student_view(self, view):
html = self._get_rendered_view(
self.sequence_3_1,
extra_context=dict(next_url='NextSequential', prev_url='PrevSequential'),
@@ -164,9 +163,8 @@ class SequenceBlockTestCase(XModuleXmlImportTest):
self.assertNotIn("fa fa-check-circle check-circle is-hidden", html)
# pylint: disable=line-too-long
@patch('xmodule.seq_module.SequenceModule._get_user', return_value=UserFactory.build())
@patch('xmodule.seq_module.SequenceModule.gate_entire_sequence_if_it_is_a_timed_exam_and_contains_content_type_gated_problems')
def test_timed_exam_gating_waffle_flag(self, mocked_function, mocked_user): # pylint: disable=unused-argument
def test_timed_exam_gating_waffle_flag(self, mocked_function): # pylint: disable=unused-argument
"""
Verify the code inside the waffle flag is not executed with the flag off
Verify the code inside the waffle flag is executed with the flag on
@@ -190,11 +188,9 @@ class SequenceBlockTestCase(XModuleXmlImportTest):
mocked_function.assert_called_once()
@override_waffle_flag(TIMED_EXAM_GATING_WAFFLE_FLAG, active=True)
@patch('xmodule.seq_module.SequenceModule._get_user', return_value=UserFactory.build())
def test_that_timed_sequence_gating_respects_access_configurations(self, mocked_user): # pylint: disable=unused-argument
def test_that_timed_sequence_gating_respects_access_configurations(self):
"""
Verify that if a time limited sequence contains content type gated problems, we gate the sequence
Verify that if a time limited sequence does not contain content type gated problems, we do not gate the sequence
"""
# the one problem in this sequence needs to have graded set to true in order to test content type gating
self.sequence_5_1.get_children()[0].get_children()[0].graded = True
@@ -202,8 +198,7 @@ class SequenceBlockTestCase(XModuleXmlImportTest):
# When a time limited sequence contains content type gated problems, the sequence itself is gated
self.sequence_5_1.runtime._services['content_type_gating'] = Mock(return_value=Mock( # pylint: disable=protected-access
enabled_for_enrollment=Mock(return_value=True),
content_type_gate_for_block=Mock(return_value=gated_fragment)
check_children_for_content_type_gating_paywall=Mock(return_value=gated_fragment.content),
))
view = self._get_rendered_view(
self.sequence_5_1,
@@ -216,62 +211,27 @@ class SequenceBlockTestCase(XModuleXmlImportTest):
self.assertIn('NextSequential', view)
self.assertIn('PrevSequential', view)
# When enabled_for_enrollment is false, the sequence itself is not gated
self.sequence_5_1.runtime._services['content_type_gating'] = Mock(return_value=Mock( # pylint: disable=protected-access
enabled_for_enrollment=Mock(return_value=False),
content_type_gate_for_block=Mock(return_value=gated_fragment)
))
view = self._get_rendered_view(
self.sequence_5_1,
extra_context=dict(next_url='NextSequential', prev_url='PrevSequential'),
view=STUDENT_VIEW
)
self.assertNotIn('i_am_gated', view)
# check a few elements to ensure the correct page was loaded
self.assertIn("seq_module.html", view)
self.assertIn('NextSequential', view)
self.assertIn('PrevSequential', view)
# When content_type_gate_for_block returns None, the sequence itself is not gated
self.sequence_5_1.runtime._services['content_type_gating'] = Mock(return_value=Mock( # pylint: disable=protected-access
enabled_for_enrollment=Mock(return_value=True),
content_type_gate_for_block=Mock(return_value=None)
))
view = self._get_rendered_view(
self.sequence_5_1,
extra_context=dict(next_url='NextSequential', prev_url='PrevSequential'),
view=STUDENT_VIEW
)
self.assertNotIn('i_am_gated', view)
# check a few elements to ensure the correct page was loaded
self.assertIn("seq_module.html", view)
self.assertIn('NextSequential', view)
self.assertIn('PrevSequential', view)
@patch('xmodule.seq_module.SequenceModule._get_user', return_value=UserFactory.build())
@ddt.unpack
@ddt.data(
{'view': STUDENT_VIEW},
{'view': PUBLIC_VIEW},
)
def test_student_view_first_child(self, mocked_user, view): # pylint: disable=unused-argument
def test_student_view_first_child(self, view):
html = self._get_rendered_view(
self.sequence_3_1, requested_child='first', view=view
)
self._assert_view_at_position(html, expected_position=1)
@patch('xmodule.seq_module.SequenceModule._get_user', return_value=UserFactory.build())
@ddt.unpack
@ddt.data(
{'view': STUDENT_VIEW},
{'view': PUBLIC_VIEW},
)
def test_student_view_last_child(self, mocked_user, view): # pylint: disable=unused-argument
def test_student_view_last_child(self, view):
html = self._get_rendered_view(self.sequence_3_1, requested_child='last', view=view)
self._assert_view_at_position(html, expected_position=3)
@patch('xmodule.seq_module.SequenceModule._get_user', return_value=UserFactory.build())
def test_tooltip(self, mocked_user): # pylint: disable=unused-argument
def test_tooltip(self):
html = self._get_rendered_view(self.sequence_3_1, requested_child=None)
for child in self.sequence_3_1.children:
self.assertIn("'page_title': '{}'".format(child.block_id), html)
@@ -445,8 +405,7 @@ class SequenceBlockTestCase(XModuleXmlImportTest):
)
self.assertIs(completion_return, None)
@patch('xmodule.seq_module.SequenceModule._get_user', return_value=UserFactory.build())
def test_handle_ajax_metadata(self, mocked_user): # pylint: disable=unused-argument
def test_handle_ajax_metadata(self):
"""
Test that the sequence metadata is returned from the
metadata ajax handler.
@@ -459,11 +418,10 @@ class SequenceBlockTestCase(XModuleXmlImportTest):
self.assertEqual(metadata['tag'], 'sequential')
self.assertEqual(metadata['display_name'], self.sequence_3_1.display_name_with_default)
@patch('xmodule.seq_module.SequenceModule._get_user', return_value=UserFactory.build())
@override_settings(FIELD_OVERRIDE_PROVIDERS=(
'openedx.features.content_type_gating.field_override.ContentTypeGatingFieldOverride',
))
def test_handle_ajax_metadata_content_type_gated_content(self, mocked_user): # pylint: disable=unused-argument
def test_handle_ajax_metadata_content_type_gated_content(self):
"""
The contains_content_type_gated_content field should reflect
whether the given item contains content type gated content