Merge pull request #20198 from edx/revert-20190-revert_both
Undo revert of switching course api default behavior to new version
This commit is contained in:
@@ -7,6 +7,7 @@ from lms.djangoapps.course_blocks.transformers.hidden_content import HiddenConte
|
||||
from lms.djangoapps.course_blocks.transformers.hide_empty import HideEmptyTransformer
|
||||
from lms.djangoapps.course_blocks.transformers.access_denied_filter import AccessDeniedMessageFilterTransformer
|
||||
from openedx.core.djangoapps.content.block_structure.transformers import BlockStructureTransformers
|
||||
from openedx.core.djangoapps.waffle_utils import WaffleFlag, WaffleFlagNamespace
|
||||
from openedx.core.lib.mobile_utils import is_request_from_mobile_app
|
||||
|
||||
from .serializers import BlockDictSerializer, BlockSerializer
|
||||
@@ -26,7 +27,7 @@ def get_blocks(
|
||||
student_view_data=None,
|
||||
return_type='dict',
|
||||
block_types_filter=None,
|
||||
hide_access_denials=True,
|
||||
hide_access_denials=False,
|
||||
):
|
||||
"""
|
||||
Return a serialized representation of the course blocks.
|
||||
@@ -57,6 +58,16 @@ def get_blocks(
|
||||
denied access to the user, even if they have access denial messages
|
||||
attached.
|
||||
"""
|
||||
|
||||
course_blocks_namespace = WaffleFlagNamespace(name=u'course_blocks_api')
|
||||
hide_access_denials_flag = WaffleFlag(
|
||||
waffle_namespace=course_blocks_namespace,
|
||||
flag_name=u'hide_access_denials',
|
||||
flag_undefined_default=False
|
||||
)
|
||||
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:
|
||||
|
||||
@@ -209,7 +209,7 @@ class TestGetBlocksQueryCounts(TestGetBlocksQueryCountsBase):
|
||||
self._get_blocks(
|
||||
course,
|
||||
expected_mongo_queries=0,
|
||||
expected_sql_queries=9 if with_storage_backing else 8,
|
||||
expected_sql_queries=10 if with_storage_backing else 9,
|
||||
)
|
||||
|
||||
@ddt.data(
|
||||
@@ -226,9 +226,9 @@ class TestGetBlocksQueryCounts(TestGetBlocksQueryCountsBase):
|
||||
clear_course_from_cache(course.id)
|
||||
|
||||
if with_storage_backing:
|
||||
num_sql_queries = 19
|
||||
num_sql_queries = 20
|
||||
else:
|
||||
num_sql_queries = 9
|
||||
num_sql_queries = 10
|
||||
|
||||
self._get_blocks(
|
||||
course,
|
||||
@@ -257,7 +257,7 @@ class TestQueryCountsWithIndividualOverrideProvider(TestGetBlocksQueryCountsBase
|
||||
self._get_blocks(
|
||||
course,
|
||||
expected_mongo_queries=0,
|
||||
expected_sql_queries=10 if with_storage_backing else 9,
|
||||
expected_sql_queries=11 if with_storage_backing else 10,
|
||||
)
|
||||
|
||||
@ddt.data(
|
||||
@@ -274,9 +274,9 @@ class TestQueryCountsWithIndividualOverrideProvider(TestGetBlocksQueryCountsBase
|
||||
clear_course_from_cache(course.id)
|
||||
|
||||
if with_storage_backing:
|
||||
num_sql_queries = 20
|
||||
num_sql_queries = 21
|
||||
else:
|
||||
num_sql_queries = 10
|
||||
num_sql_queries = 11
|
||||
|
||||
self._get_blocks(
|
||||
course,
|
||||
|
||||
@@ -11,6 +11,7 @@ from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
from mock import patch
|
||||
|
||||
from course_api.blocks.api import get_blocks
|
||||
from django_comment_common.models import (
|
||||
FORUM_ROLE_ADMINISTRATOR,
|
||||
FORUM_ROLE_MODERATOR,
|
||||
@@ -61,6 +62,13 @@ from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase, SharedMo
|
||||
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
|
||||
|
||||
|
||||
METADATA = {
|
||||
'group_access': {
|
||||
CONTENT_GATING_PARTITION_ID: [CONTENT_TYPE_GATE_GROUP_IDS['full_access']]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@patch("crum.get_current_request")
|
||||
def _get_fragment_from_block(block, user_id, course, request_factory, mock_get_current_request):
|
||||
"""
|
||||
@@ -85,24 +93,23 @@ def _get_fragment_from_block(block, user_id, course, request_factory, mock_get_c
|
||||
|
||||
# This method of fetching the block from the descriptor bypassess access checks
|
||||
problem_block = runtime.get_module(block)
|
||||
|
||||
# Attempt to render the block, this should return different fragments if the content is gated or not.
|
||||
frag = runtime.render(problem_block, 'student_view')
|
||||
return frag
|
||||
|
||||
|
||||
def _assert_block_is_gated(block, is_gated, user_id, course, request_factory, has_upgrade_link=True):
|
||||
def _assert_block_is_gated(block, is_gated, user, course, request_factory, has_upgrade_link=True):
|
||||
"""
|
||||
Asserts that a block in a specific course is gated for a specific user
|
||||
Arguments:
|
||||
block: some sort of xblock descriptor, must implement .scope_ids.usage_id
|
||||
is_gated (bool): if True, this user is expected to be gated from this block
|
||||
user_id (int): id of user
|
||||
user (int): user
|
||||
course_id (CourseLocator): id of course
|
||||
"""
|
||||
checkout_link = '#' if has_upgrade_link else None
|
||||
with patch.object(ContentTypeGatingPartition, '_get_checkout_link', return_value=checkout_link):
|
||||
frag = _get_fragment_from_block(block, user_id, course, request_factory)
|
||||
frag = _get_fragment_from_block(block, user.id, course, request_factory)
|
||||
if is_gated:
|
||||
assert 'content-paywall' in frag.content
|
||||
if has_upgrade_link:
|
||||
@@ -112,6 +119,15 @@ def _assert_block_is_gated(block, is_gated, user_id, course, request_factory, ha
|
||||
else:
|
||||
assert 'content-paywall' not in frag.content
|
||||
|
||||
fake_request = request_factory.get('')
|
||||
with patch('lms.djangoapps.course_api.blocks.api.is_request_from_mobile_app', return_value=True):
|
||||
blocks = get_blocks(fake_request, course.location, user=user)
|
||||
course_api_block = blocks['blocks'][str(block.location)]
|
||||
if is_gated:
|
||||
assert 'authorization_denial_reason' in course_api_block
|
||||
else:
|
||||
assert 'authorization_denial_reason' not in course_api_block
|
||||
|
||||
|
||||
def _assert_block_is_empty(block, user_id, course, request_factory):
|
||||
"""
|
||||
@@ -183,6 +199,7 @@ class TestProblemTypeAccess(SharedModuleStoreTestCase):
|
||||
display_name=case_name,
|
||||
graded=graded,
|
||||
weight=weight,
|
||||
metadata=METADATA if (graded and has_score and weight) else {},
|
||||
)
|
||||
cls.graded_score_weight_blocks[(graded, has_score, weight)] = block
|
||||
|
||||
@@ -193,6 +210,7 @@ class TestProblemTypeAccess(SharedModuleStoreTestCase):
|
||||
display_name='lti_consumer',
|
||||
has_score=True,
|
||||
graded=True,
|
||||
metadata=METADATA,
|
||||
)
|
||||
cls.blocks_dict['lti_block_not_scored'] = ItemFactory.create(
|
||||
parent=cls.blocks_dict['vertical'],
|
||||
@@ -207,6 +225,7 @@ class TestProblemTypeAccess(SharedModuleStoreTestCase):
|
||||
category='problem',
|
||||
display_name='graded_problem',
|
||||
graded=True,
|
||||
metadata=METADATA,
|
||||
)
|
||||
cls.blocks_dict['ungraded_problem'] = ItemFactory.create(
|
||||
parent=cls.blocks_dict['vertical'],
|
||||
@@ -322,22 +341,22 @@ class TestProblemTypeAccess(SharedModuleStoreTestCase):
|
||||
blocks_dict = {}
|
||||
chapter = ItemFactory.create(
|
||||
parent=course,
|
||||
display_name='Overview'
|
||||
display_name='Overview',
|
||||
)
|
||||
blocks_dict['chapter'] = ItemFactory.create(
|
||||
parent=course,
|
||||
category='chapter',
|
||||
display_name='Week 1'
|
||||
display_name='Week 1',
|
||||
)
|
||||
blocks_dict['sequential'] = ItemFactory.create(
|
||||
parent=chapter,
|
||||
category='sequential',
|
||||
display_name='Lesson 1'
|
||||
display_name='Lesson 1',
|
||||
)
|
||||
blocks_dict['vertical'] = ItemFactory.create(
|
||||
parent=blocks_dict['sequential'],
|
||||
category='vertical',
|
||||
display_name='Lesson 1 Vertical - Unit 1'
|
||||
display_name='Lesson 1 Vertical - Unit 1',
|
||||
)
|
||||
|
||||
for component_type in component_types:
|
||||
@@ -346,6 +365,7 @@ class TestProblemTypeAccess(SharedModuleStoreTestCase):
|
||||
category=component_type,
|
||||
display_name=component_type,
|
||||
graded=True,
|
||||
metadata={} if component_type == 'html' else METADATA
|
||||
)
|
||||
blocks_dict[component_type] = block
|
||||
|
||||
@@ -369,14 +389,14 @@ class TestProblemTypeAccess(SharedModuleStoreTestCase):
|
||||
def test_access_to_problems(self, prob_type, is_gated):
|
||||
_assert_block_is_gated(
|
||||
block=self.blocks_dict[prob_type],
|
||||
user_id=self.users['audit'].id,
|
||||
user=self.users['audit'],
|
||||
course=self.course,
|
||||
is_gated=is_gated,
|
||||
request_factory=self.factory,
|
||||
)
|
||||
_assert_block_is_gated(
|
||||
block=self.blocks_dict[prob_type],
|
||||
user_id=self.users['verified'].id,
|
||||
user=self.users['verified'],
|
||||
course=self.course,
|
||||
is_gated=False,
|
||||
request_factory=self.factory,
|
||||
@@ -391,7 +411,7 @@ class TestProblemTypeAccess(SharedModuleStoreTestCase):
|
||||
block = self.graded_score_weight_blocks[(graded, has_score, weight)]
|
||||
_assert_block_is_gated(
|
||||
block=block,
|
||||
user_id=self.audit_user.id,
|
||||
user=self.audit_user,
|
||||
course=self.course,
|
||||
is_gated=is_gated,
|
||||
request_factory=self.factory,
|
||||
@@ -426,7 +446,7 @@ class TestProblemTypeAccess(SharedModuleStoreTestCase):
|
||||
"""
|
||||
_assert_block_is_gated(
|
||||
block=self.courses[course]['blocks'][component_type],
|
||||
user_id=self.users[user_track].id,
|
||||
user=self.users[user_track],
|
||||
course=self.courses[course]['course'],
|
||||
is_gated=is_gated,
|
||||
request_factory=self.factory,
|
||||
@@ -439,7 +459,7 @@ class TestProblemTypeAccess(SharedModuleStoreTestCase):
|
||||
"""
|
||||
_assert_block_is_gated(
|
||||
block=self.courses['default']['blocks']['problem'],
|
||||
user_id=self.users['audit'].id,
|
||||
user=self.users['audit'],
|
||||
course=self.courses['default']['course'],
|
||||
is_gated=True,
|
||||
request_factory=self.factory,
|
||||
@@ -496,7 +516,7 @@ class TestProblemTypeAccess(SharedModuleStoreTestCase):
|
||||
# assert that course team members have access to graded content
|
||||
_assert_block_is_gated(
|
||||
block=self.blocks_dict['problem'],
|
||||
user_id=user.id,
|
||||
user=user,
|
||||
course=self.course,
|
||||
is_gated=False,
|
||||
request_factory=self.factory,
|
||||
@@ -518,7 +538,7 @@ class TestProblemTypeAccess(SharedModuleStoreTestCase):
|
||||
|
||||
_assert_block_is_gated(
|
||||
block=self.blocks_dict['problem'],
|
||||
user_id=user.id,
|
||||
user=user,
|
||||
course=self.course,
|
||||
is_gated=False,
|
||||
request_factory=self.factory,
|
||||
@@ -548,7 +568,7 @@ class TestProblemTypeAccess(SharedModuleStoreTestCase):
|
||||
block = self.graded_score_weight_blocks[(graded, has_score, weight)]
|
||||
_assert_block_is_gated(
|
||||
block=block,
|
||||
user_id=user.id,
|
||||
user=user,
|
||||
course=self.course,
|
||||
is_gated=is_gated,
|
||||
request_factory=self.factory,
|
||||
@@ -671,7 +691,7 @@ class TestProblemTypeAccess(SharedModuleStoreTestCase):
|
||||
|
||||
_assert_block_is_gated(
|
||||
block=self.blocks_dict['problem'],
|
||||
user_id=user.id,
|
||||
user=user,
|
||||
course=self.course,
|
||||
is_gated=False,
|
||||
request_factory=self.factory,
|
||||
@@ -714,24 +734,26 @@ class TestConditionalContentAccess(TestConditionalContent):
|
||||
user=self.student_verified_a,
|
||||
course_id=self.course.id,
|
||||
key='xblock.partition_service.partition_{0}'.format(self.partition.id),
|
||||
value=str('user_course_tag_a'),
|
||||
value=str('0'),
|
||||
)
|
||||
UserCourseTagFactory(
|
||||
user=self.student_verified_b,
|
||||
course_id=self.course.id,
|
||||
key='xblock.partition_service.partition_{0}'.format(self.partition.id),
|
||||
value=str('user_course_tag_b'),
|
||||
value=str('1'),
|
||||
)
|
||||
# Create blocks to go into the verticals
|
||||
self.block_a = ItemFactory.create(
|
||||
category='problem',
|
||||
parent=self.vertical_a,
|
||||
display_name='problem_a',
|
||||
metadata=METADATA,
|
||||
)
|
||||
self.block_b = ItemFactory.create(
|
||||
category='problem',
|
||||
parent=self.vertical_b,
|
||||
display_name='problem_b',
|
||||
metadata=METADATA,
|
||||
)
|
||||
|
||||
def test_access_based_on_conditional_content(self):
|
||||
@@ -743,14 +765,14 @@ class TestConditionalContentAccess(TestConditionalContent):
|
||||
# Make sure that all audit enrollments are gated regardless of if they see vertical a or vertical b
|
||||
_assert_block_is_gated(
|
||||
block=self.block_a,
|
||||
user_id=self.student_audit_a.id,
|
||||
user=self.student_audit_a,
|
||||
course=self.course,
|
||||
is_gated=True,
|
||||
request_factory=self.factory,
|
||||
)
|
||||
_assert_block_is_gated(
|
||||
block=self.block_b,
|
||||
user_id=self.student_audit_b.id,
|
||||
user=self.student_audit_b,
|
||||
course=self.course,
|
||||
is_gated=True,
|
||||
request_factory=self.factory,
|
||||
@@ -759,14 +781,14 @@ class TestConditionalContentAccess(TestConditionalContent):
|
||||
# Make sure that all verified enrollments are not gated regardless of if they see vertical a or vertical b
|
||||
_assert_block_is_gated(
|
||||
block=self.block_a,
|
||||
user_id=self.student_verified_a.id,
|
||||
user=self.student_verified_a,
|
||||
course=self.course,
|
||||
is_gated=False,
|
||||
request_factory=self.factory,
|
||||
)
|
||||
_assert_block_is_gated(
|
||||
block=self.block_b,
|
||||
user_id=self.student_verified_b.id,
|
||||
user=self.student_verified_b,
|
||||
course=self.course,
|
||||
is_gated=False,
|
||||
request_factory=self.factory,
|
||||
@@ -836,10 +858,11 @@ class TestMessageDeduplication(ModuleStoreTestCase):
|
||||
category='problem',
|
||||
display_name='graded_problem',
|
||||
graded=True,
|
||||
metadata=METADATA,
|
||||
)
|
||||
_assert_block_is_gated(
|
||||
block=blocks_dict['graded_1'],
|
||||
user_id=self.user.id,
|
||||
user=self.user,
|
||||
course=course['course'],
|
||||
is_gated=True,
|
||||
request_factory=self.request_factory,
|
||||
@@ -854,12 +877,14 @@ class TestMessageDeduplication(ModuleStoreTestCase):
|
||||
category='problem',
|
||||
display_name='graded_problem',
|
||||
graded=True,
|
||||
metadata=METADATA,
|
||||
)
|
||||
blocks_dict['graded_2'] = ItemFactory.create(
|
||||
parent=blocks_dict['vertical'],
|
||||
category='problem',
|
||||
display_name='graded_problem',
|
||||
graded=True,
|
||||
metadata=METADATA,
|
||||
)
|
||||
CourseEnrollmentFactory.create(
|
||||
user=self.user,
|
||||
@@ -868,7 +893,7 @@ class TestMessageDeduplication(ModuleStoreTestCase):
|
||||
)
|
||||
_assert_block_is_gated(
|
||||
block=blocks_dict['graded_1'],
|
||||
user_id=self.user.id,
|
||||
user=self.user,
|
||||
course=course['course'],
|
||||
is_gated=True,
|
||||
request_factory=self.request_factory,
|
||||
@@ -889,24 +914,28 @@ class TestMessageDeduplication(ModuleStoreTestCase):
|
||||
category='problem',
|
||||
display_name='graded_problem',
|
||||
graded=True,
|
||||
metadata=METADATA,
|
||||
)
|
||||
blocks_dict['graded_2'] = ItemFactory.create(
|
||||
parent=blocks_dict['vertical'],
|
||||
category='problem',
|
||||
display_name='graded_problem',
|
||||
graded=True,
|
||||
metadata=METADATA,
|
||||
)
|
||||
blocks_dict['graded_3'] = ItemFactory.create(
|
||||
parent=blocks_dict['vertical'],
|
||||
category='problem',
|
||||
display_name='graded_problem',
|
||||
graded=True,
|
||||
metadata=METADATA,
|
||||
)
|
||||
blocks_dict['graded_4'] = ItemFactory.create(
|
||||
parent=blocks_dict['vertical'],
|
||||
category='problem',
|
||||
display_name='graded_problem',
|
||||
graded=True,
|
||||
metadata=METADATA,
|
||||
)
|
||||
CourseEnrollmentFactory.create(
|
||||
user=self.user,
|
||||
@@ -915,7 +944,7 @@ class TestMessageDeduplication(ModuleStoreTestCase):
|
||||
)
|
||||
_assert_block_is_gated(
|
||||
block=blocks_dict['graded_1'],
|
||||
user_id=self.user.id,
|
||||
user=self.user,
|
||||
course=course['course'],
|
||||
is_gated=True,
|
||||
request_factory=self.request_factory,
|
||||
@@ -948,6 +977,7 @@ class TestMessageDeduplication(ModuleStoreTestCase):
|
||||
category='problem',
|
||||
display_name='graded_problem',
|
||||
graded=True,
|
||||
metadata=METADATA,
|
||||
)
|
||||
blocks_dict['ungraded_2'] = ItemFactory.create(
|
||||
parent=blocks_dict['vertical'],
|
||||
@@ -960,6 +990,7 @@ class TestMessageDeduplication(ModuleStoreTestCase):
|
||||
category='problem',
|
||||
display_name='graded_problem',
|
||||
graded=True,
|
||||
metadata=METADATA,
|
||||
)
|
||||
CourseEnrollmentFactory.create(
|
||||
user=self.user,
|
||||
@@ -968,21 +999,21 @@ class TestMessageDeduplication(ModuleStoreTestCase):
|
||||
)
|
||||
_assert_block_is_gated(
|
||||
block=blocks_dict['graded_1'],
|
||||
user_id=self.user.id,
|
||||
user=self.user,
|
||||
course=course['course'],
|
||||
is_gated=True,
|
||||
request_factory=self.request_factory,
|
||||
)
|
||||
_assert_block_is_gated(
|
||||
block=blocks_dict['ungraded_2'],
|
||||
user_id=self.user.id,
|
||||
user=self.user,
|
||||
course=course['course'],
|
||||
is_gated=False,
|
||||
request_factory=self.request_factory,
|
||||
)
|
||||
_assert_block_is_gated(
|
||||
block=blocks_dict['graded_3'],
|
||||
user_id=self.user.id,
|
||||
user=self.user,
|
||||
course=course['course'],
|
||||
is_gated=True,
|
||||
request_factory=self.request_factory,
|
||||
|
||||
@@ -151,6 +151,11 @@ course_sections = blocks.get('children')
|
||||
aria-labelledby="${ subsection['id'] }"
|
||||
>
|
||||
% for vertical in subsection.get('children', []):
|
||||
<%
|
||||
vertical_is_access_denied = vertical.get('authorization_denial_reason')
|
||||
if vertical_is_access_denied:
|
||||
continue
|
||||
%>
|
||||
<li class="vertical outline-item focusable">
|
||||
<a class="outline-item focusable"
|
||||
% if enable_links:
|
||||
|
||||
@@ -90,13 +90,12 @@ def get_course_outline_block_tree(request, course_id, user=None):
|
||||
latest_completion,
|
||||
block=block['children'][idx]
|
||||
)
|
||||
if block['children'][idx]['resume_block'] is True:
|
||||
if block['children'][idx].get('resume_block') is True:
|
||||
block['resume_block'] = True
|
||||
|
||||
completable_blocks = [child for child in block['children']
|
||||
if child['type'] != 'discussion']
|
||||
if len([child['complete'] for child in block['children']
|
||||
if child['complete']]) == len(completable_blocks):
|
||||
if child.get('type') != 'discussion']
|
||||
if all(child.get('complete') for child in completable_blocks):
|
||||
block['complete'] = True
|
||||
|
||||
def mark_last_accessed(user, course_key, block):
|
||||
@@ -173,7 +172,7 @@ def get_resume_block(block):
|
||||
Gets the deepest block marked as 'resume_block'.
|
||||
|
||||
"""
|
||||
if not block['resume_block']:
|
||||
if block.get('authorization_denial_reason') or not block['resume_block']:
|
||||
return None
|
||||
if not block.get('children'):
|
||||
return block
|
||||
|
||||
@@ -76,12 +76,13 @@ class CourseOutlineFragmentView(EdxFragmentView):
|
||||
if xblock_display_names is None:
|
||||
xblock_display_names = {}
|
||||
|
||||
if course_block_tree.get('id'):
|
||||
xblock_display_names[course_block_tree['id']] = course_block_tree['display_name']
|
||||
if not course_block_tree.get('authorization_denial_reason'):
|
||||
if course_block_tree.get('id'):
|
||||
xblock_display_names[course_block_tree['id']] = course_block_tree['display_name']
|
||||
|
||||
if course_block_tree.get('children'):
|
||||
for child in course_block_tree['children']:
|
||||
self.create_xblock_id_and_name_dict(child, xblock_display_names)
|
||||
if course_block_tree.get('children'):
|
||||
for child in course_block_tree['children']:
|
||||
self.create_xblock_id_and_name_dict(child, xblock_display_names)
|
||||
|
||||
return xblock_display_names
|
||||
|
||||
|
||||
Reference in New Issue
Block a user