diff --git a/lms/djangoapps/course_api/blocks/tests/test_api.py b/lms/djangoapps/course_api/blocks/tests/test_api.py index a3b6080ca024..2c0979c11b5d 100644 --- a/lms/djangoapps/course_api/blocks/tests/test_api.py +++ b/lms/djangoapps/course_api/blocks/tests/test_api.py @@ -222,12 +222,12 @@ def test_query_counts_cached(self, store_type, with_storage_backing): self._get_blocks( course, expected_mongo_queries=0, - expected_sql_queries=22 if with_storage_backing else 21, + expected_sql_queries=21 if with_storage_backing else 20, ) @ddt.data( - (ModuleStoreEnum.Type.split, 2, True, 31), - (ModuleStoreEnum.Type.split, 2, False, 21), + (ModuleStoreEnum.Type.split, 2, True, 30), + (ModuleStoreEnum.Type.split, 2, False, 20), ) @ddt.unpack def test_query_counts_uncached(self, store_type, expected_mongo_queries, with_storage_backing, num_sql_queries): diff --git a/lms/djangoapps/course_api/blocks/transformers/tests/test_milestones.py b/lms/djangoapps/course_api/blocks/transformers/tests/test_milestones.py index e87e0e755685..bfde20ca1f2b 100644 --- a/lms/djangoapps/course_api/blocks/transformers/tests/test_milestones.py +++ b/lms/djangoapps/course_api/blocks/transformers/tests/test_milestones.py @@ -166,7 +166,7 @@ def test_gated(self, gated_block_ref, gating_block_ref, expected_blocks_before_c self.course.enable_subsection_gating = True self.setup_gated_section(self.blocks[gated_block_ref], self.blocks[gating_block_ref]) - with self.assertNumQueries(5): + with self.assertNumQueries(6): self.get_blocks_and_check_against_expected(self.user, expected_blocks_before_completion) # clear the request cache to simulate a new request @@ -179,7 +179,7 @@ def test_gated(self, gated_block_ref, gating_block_ref, expected_blocks_before_c self.user, ) - with self.assertNumQueries(6): + with self.assertNumQueries(7): self.get_blocks_and_check_against_expected(self.user, self.ALL_BLOCKS_EXCEPT_SPECIAL) def test_staff_access(self): diff --git a/lms/djangoapps/courseware/access.py b/lms/djangoapps/courseware/access.py index 74f1d74f837f..5ec50af4d519 100644 --- a/lms/djangoapps/courseware/access.py +++ b/lms/djangoapps/courseware/access.py @@ -42,6 +42,7 @@ from lms.djangoapps.mobile_api.models import IgnoreMobileAvailableFlagConfig from lms.djangoapps.courseware.toggles import course_is_invitation_only from openedx.core.djangoapps.content.course_overviews.models import CourseOverview +from openedx.core.djangoapps.course_roles.data import CourseRolesPermission from openedx.features.course_duration_limits.access import check_course_expired from common.djangoapps.student import auth from common.djangoapps.student.models import CourseEnrollmentAllowed @@ -83,7 +84,8 @@ def has_ccx_coach_role(user, course_key): ccx_id = course_key.ccx role = CourseCcxCoachRole(course_key) - if role.has_user(user): + # TODO: remove role check once course_roles is fully impelented and data is migrated + if role.has_user(user) or user.has_perm(CourseRolesPermission.MANAGE_STUDENTS.perm_name, course_key): list_ccx = CustomCourseForEdX.objects.filter( course_id=course_key.to_course_locator(), coach=user @@ -174,7 +176,12 @@ def has_staff_access_to_preview_mode(user, course_key): """ has_admin_access_to_course = any(administrative_accesses_to_course_for_user(user, course_key)) - return has_admin_access_to_course or is_masquerading_as_student(user, course_key) + # TODO: remove role check once course_roles is fully impelented and data is migrated + return ( + has_admin_access_to_course or + is_masquerading_as_student(user, course_key) or + user.has_perm(CourseRolesPermission.VIEW_ALL_CONTENT.perm_name, course_key) + ) def _can_view_courseware_with_prerequisites(user, course): @@ -721,12 +728,12 @@ def _has_access_to_course(user, access_level, course_key): return ACCESS_DENIED global_staff, staff_access, instructor_access = administrative_accesses_to_course_for_user(user, course_key) - + permissions_access = user.has_perm(CourseRolesPermission.VIEW_ALL_CONTENT.perm_name, course_key) if global_staff: debug("Allow: user.is_staff") return ACCESS_GRANTED - if access_level not in ('staff', 'instructor'): + if access_level not in ('staff', 'instructor') and not permissions_access: log.debug("Error in access._has_access_to_course access_level=%s unknown", access_level) debug("Deny: unknown access level") return ACCESS_DENIED @@ -739,6 +746,10 @@ def _has_access_to_course(user, access_level, course_key): debug("Allow: user has course instructor access") return ACCESS_GRANTED + if permissions_access: + debug("Allow: user has view all content permission") + return ACCESS_GRANTED + debug("Deny: user did not have correct access") return ACCESS_DENIED @@ -875,11 +886,21 @@ def is_mobile_available_for_user(user, block): Checks: mobile_available flag on the course Beta User and staff access overrides the mobile_available flag + Permission to view_all_published_content or + view_only_published_content overrides mobile_available flag Arguments: block (CourseBlock|CourseOverview): course or overview of course in question """ + permissions_access = ( + user.has_perms([ + CourseRolesPermission.VIEW_LIVE_PUBLISHED_CONTENT.perm_name, + CourseRolesPermission.VIEW_ALL_PUBLISHED_CONTENT.perm_name + ], block.id) + ) + # TODO: remove role check once course_roles is fully impelented and data is migrated return ( auth.user_has_role(user, CourseBetaTesterRole(block.id)) + or permissions_access or _has_staff_access_to_block(user, block, block.id) or _is_block_mobile_available(block) ) diff --git a/lms/djangoapps/courseware/access_utils.py b/lms/djangoapps/courseware/access_utils.py index dd2f8d8f6dfe..4cb1bfcd3b85 100644 --- a/lms/djangoapps/courseware/access_utils.py +++ b/lms/djangoapps/courseware/access_utils.py @@ -25,6 +25,7 @@ StartDateError ) from lms.djangoapps.courseware.masquerade import get_course_masquerade, is_masquerading_as_student +from openedx.core.djangoapps.course_roles.data import CourseRolesPermission from openedx.features.course_experience import COURSE_ENABLE_UNENROLLED_ACCESS_FLAG, COURSE_PRE_START_ACCESS_FLAG from xmodule.course_block import COURSE_VISIBILITY_PUBLIC # lint-amnesty, pylint: disable=wrong-import-order from xmodule.util.xmodule_django import get_current_request_hostname # lint-amnesty, pylint: disable=wrong-import-order @@ -57,7 +58,11 @@ def adjust_start_date(user, days_early_for_beta, start, course_key): # bail early if no beta testing is set up return start - if CourseBetaTesterRole(course_key).has_user(user): + # TODO: remove role check once course_roles is fully impelented and data is migrated + if ( + CourseBetaTesterRole(course_key).has_user(user) or + user.has_perm(CourseRolesPermission.VIEW_LIVE_PUBLISHED_CONTENT.perm_name, course_key) + ): debug("Adjust start time: user in beta role for %s", course_key) delta = timedelta(days_early_for_beta) effective = start - delta diff --git a/lms/djangoapps/courseware/block_render.py b/lms/djangoapps/courseware/block_render.py index fc02e2662e67..95d283af51d2 100644 --- a/lms/djangoapps/courseware/block_render.py +++ b/lms/djangoapps/courseware/block_render.py @@ -68,6 +68,7 @@ from lms.djangoapps.lms_xblock.runtime import UserTagsService, lms_wrappers_aside, lms_applicable_aside_types from lms.djangoapps.verify_student.services import XBlockVerificationService from openedx.core.djangoapps.bookmarks.api import BookmarksService +from openedx.core.djangoapps.course_roles.data import CourseRolesPermission from openedx.core.djangoapps.crawlers.models import CrawlersConfig from openedx.core.djangoapps.credit.services import CreditService from openedx.core.djangoapps.util.user_utils import SystemUser @@ -572,7 +573,17 @@ def inner_get_block(block: XBlock) -> XBlock | None: block_wrappers.append(partial(course_expiration_wrapper, user)) block_wrappers.append(partial(offer_banner_wrapper, user)) - user_is_staff = bool(has_access(user, 'staff', course_id)) + has_permission = ( + user.has_perms([ + CourseRolesPermission.VIEW_ALL_CONTENT.perm_name, + CourseRolesPermission.SPECIFIC_MASQUERADING.perm_name + ], course_id) + ) + # TODO: remove role check once course_roles is fully impelented and data is migrated + user_is_staff = ( + bool(has_access(user, 'staff', course_id)) or + has_permission + ) if settings.FEATURES.get('DISPLAY_DEBUG_INFO_TO_STAFF'): if user_is_staff or is_masquerading_as_specific_student(user, course_id): @@ -582,6 +593,9 @@ def inner_get_block(block: XBlock) -> XBlock | None: block_wrappers.append(partial(add_staff_markup, user, disable_staff_debug_info)) store = modulestore() + # The user_is_staff, user_is_beta_test, and user_role cannot be removed until + # all x-blocks have been updated to use permissions, once they have been updated + # remove role check in favor of permissions checks services = { 'fs': FSService(), @@ -596,6 +610,16 @@ def inner_get_block(block: XBlock) -> XBlock | None: # See the docstring of `DjangoXBlockUserService`. deprecated_anonymous_user_id=anonymous_id_for_user(user, None), request_country_code=user_location, + user_has_manage_content=user.has_perm(CourseRolesPermission.MANAGE_CONTENT.perm_name, course_id), + user_has_manage_grades=user.has_perm(CourseRolesPermission.MANAGE_GRADES.perm_name, course_id), + user_has_access_data_downloads=user.has_perm( + CourseRolesPermission.ACCESS_DATA_DOWNLOADS.perm_name, + course_id + ), + user_has_view_all_content=user.has_perm( + CourseRolesPermission.VIEW_ALL_CONTENT.perm_name, + course_id + ) ), 'verification': XBlockVerificationService(), 'proctoring': ProctoringService(), diff --git a/lms/djangoapps/courseware/masquerade.py b/lms/djangoapps/courseware/masquerade.py index 5ce266c463d6..495f77dc3005 100644 --- a/lms/djangoapps/courseware/masquerade.py +++ b/lms/djangoapps/courseware/masquerade.py @@ -20,6 +20,7 @@ from xblock.runtime import KeyValueStore from common.djangoapps.course_modes.models import CourseMode +from openedx.core.djangoapps.course_roles.data import CourseRolesPermission from openedx.core.djangoapps.util.user_messages import PageLevelMessages from openedx.core.djangolib.markup import HTML from openedx.features.content_type_gating.helpers import CONTENT_GATING_PARTITION_ID @@ -98,8 +99,13 @@ def get(self, request, course_key_string): Retrieve data on the active and available masquerade options """ course_key = CourseKey.from_string(course_key_string) + # TODO: remove role check once course_roles is fully impelented and data is migrated is_staff = has_staff_roles(request.user, course_key) - if not is_staff: + has_permissions = ( + request.user.has_perm(CourseRolesPermission.GENERAL_MASQUERADING.perm_name, course_key) or + request.user.has_perm(CourseRolesPermission.SPECIFIC_MASQUERADING.perm_name, course_key) + ) + if not is_staff and not has_permissions: return JsonResponse({ 'success': False, }) @@ -165,8 +171,13 @@ def post(self, request, course_key_string): to CourseMasquerade objects. """ course_key = CourseKey.from_string(course_key_string) + # TODO: remove role check once course_roles is fully impelented and data is migrated is_staff = has_staff_roles(request.user, course_key) - if not is_staff: + has_permissions = ( + request.user.has_perm(CourseRolesPermission.GENERAL_MASQUERADING.perm_name, course_key) or + request.user.has_perm(CourseRolesPermission.SPECIFIC_MASQUERADING.perm_name, course_key) + ) + if not is_staff and not has_permissions: return JsonResponse({ 'success': False, }) @@ -395,8 +406,22 @@ def check_content_start_date_for_masquerade_user(course_key, user, request, cour if now < most_future_date and _is_masquerading: group_masquerade = is_masquerading_as_student(user, course_key) specific_student_masquerade = is_masquerading_as_specific_student(user, course_key) + # TODO: remove role check once course_roles is fully impelented and data is migrated is_staff = has_staff_roles(user, course_key) - if group_masquerade or (specific_student_masquerade and not is_staff): + has_permissions = ( + user.has_perm(CourseRolesPermission.SPECIFIC_MASQUERADING.perm_name, course_key) and + ( + user.has_perm(CourseRolesPermission.MANAGE_CONTENT.perm_name, course_key) or + user.has_perm(CourseRolesPermission.VIEW_ALL_CONTENT.perm_name, course_key) or + user.has_perm(CourseRolesPermission.VIEW_LIVE_AND_PUBLISHED_CONTENT.perm_name, course_key) or + user.has_perm(CourseRolesPermission.VIEW_ALL_PUBLISHED_CONTENT.perm_name, course_key) + ) + ) + if group_masquerade or ( + specific_student_masquerade and ( + not is_staff and not has_permissions + ) + ): PageLevelMessages.register_warning_message( request, HTML(_('This user does not have access to this content because \ diff --git a/lms/djangoapps/courseware/tests/test_discussion_xblock.py b/lms/djangoapps/courseware/tests/test_discussion_xblock.py index 599f125a7e38..931ff9633f82 100644 --- a/lms/djangoapps/courseware/tests/test_discussion_xblock.py +++ b/lms/djangoapps/courseware/tests/test_discussion_xblock.py @@ -446,7 +446,7 @@ def test_permissions_query_load(self): # * django_comment_client_role # * DiscussionsConfiguration - num_queries = 6 + num_queries = 9 for discussion in discussions: discussion_xblock = get_block_for_descriptor( @@ -466,7 +466,7 @@ def test_permissions_query_load(self): # query to check for provider_type # query to check waffle flag discussions.enable_new_structure_discussions - num_queries = 2 + num_queries = 5 html = fragment.content assert 'data-user-create-comment="false"' in html diff --git a/lms/djangoapps/grades/tests/test_tasks.py b/lms/djangoapps/grades/tests/test_tasks.py index b5c0d5b6ff17..f02370274645 100644 --- a/lms/djangoapps/grades/tests/test_tasks.py +++ b/lms/djangoapps/grades/tests/test_tasks.py @@ -153,8 +153,8 @@ def test_block_structure_created_only_once(self): assert mock_block_structure_create.call_count == 1 @ddt.data( - (ModuleStoreEnum.Type.split, 2, 49, True), - (ModuleStoreEnum.Type.split, 2, 49, False), + (ModuleStoreEnum.Type.split, 2, 48, True), + (ModuleStoreEnum.Type.split, 2, 48, False), ) @ddt.unpack def test_query_counts(self, default_store, num_mongo_calls, num_sql_calls, create_multiple_subsections): @@ -165,7 +165,7 @@ def test_query_counts(self, default_store, num_mongo_calls, num_sql_calls, creat self._apply_recalculate_subsection_grade() @ddt.data( - (ModuleStoreEnum.Type.split, 2, 49), + (ModuleStoreEnum.Type.split, 2, 48), ) @ddt.unpack def test_query_counts_dont_change_with_more_content(self, default_store, num_mongo_calls, num_sql_calls): @@ -210,7 +210,7 @@ def test_other_inaccessible_subsection(self, mock_subsection_signal): ) @ddt.data( - (ModuleStoreEnum.Type.split, 2, 49), + (ModuleStoreEnum.Type.split, 2, 48), ) @ddt.unpack def test_persistent_grades_on_course(self, default_store, num_mongo_queries, num_sql_queries): diff --git a/lms/djangoapps/learner_home/views.py b/lms/djangoapps/learner_home/views.py index 1fa115be5890..4f97fef9fa9b 100644 --- a/lms/djangoapps/learner_home/views.py +++ b/lms/djangoapps/learner_home/views.py @@ -53,6 +53,7 @@ get_masquerade_user, ) from openedx.core.djangoapps.content.course_overviews.models import CourseOverview +from openedx.core.djangoapps.course_roles.data import CourseRolesPermission from openedx.core.djangoapps.programs.utils import ProgramProgressMeter from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers from openedx.core.lib.api.authentication import BearerAuthenticationAllowInactiveUser @@ -327,10 +328,12 @@ def check_course_access(user, course_enrollments): "is_too_early_to_view": not check_course_open_for_learner( user, course_enrollment.course ), + # TODO: remove role checks once course_roles is fully impelented and data is migrated "user_has_staff_access": any( administrative_accesses_to_course_for_user( user, course_enrollment.course_id - ) + ) or + user.has_perm(CourseRolesPermission.VIEW_ALL_CONTENT.perm_name, course_enrollment.course_id) ), } diff --git a/lms/djangoapps/teams/api.py b/lms/djangoapps/teams/api.py index 4051a9ba54a2..1a929c3bfcd4 100644 --- a/lms/djangoapps/teams/api.py +++ b/lms/djangoapps/teams/api.py @@ -16,6 +16,7 @@ from lms.djangoapps.courseware.courses import has_access from lms.djangoapps.discussion.django_comment_client.utils import has_discussion_privileges from lms.djangoapps.teams.models import CourseTeam, CourseTeamMembership +from openedx.core.djangoapps.course_roles.data import CourseRolesPermission from openedx.core.lib.teams_config import TeamsetType from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order @@ -144,6 +145,9 @@ def has_course_staff_privileges(user, course_key): return True if CourseInstructorRole(course_key).has_user(user): return True + # TODO: remove role checks once course_roles is fully impelented and data is migrated + if user.has_perm(CourseRolesPermission.MANAGE_STUDENTS.perm_name, course_key): + return True return False @@ -176,7 +180,11 @@ def user_organization_protection_status(user, course_key): If the user is a staff of the course, we return the protection_exempt status else, we return the unprotected status """ - if has_course_staff_privileges(user, course_key): + # TODO: remove role checks once course_roles is fully impelented and data is migrated + if ( + has_course_staff_privileges(user, course_key) or + user.has_perm(CourseRolesPermission.MANAGE_STUDENTS.perm_name, course_key) + ): return OrganizationProtectionStatus.protection_exempt enrollment = CourseEnrollment.get_enrollment(user, course_key) if enrollment and enrollment.is_active: @@ -200,8 +208,13 @@ def has_specific_team_access(user, team): - be in the correct bubble - be in the team if it is private """ - return has_course_staff_privileges(user, team.course_id) or ( - user_protection_status_matches_team(user, team) and user_on_team_or_team_is_public(user, team) + # TODO: remove role checks once course_roles is fully impelented and data is migrated + user_has_manage_student_permission = user.has_perm(CourseRolesPermission.MANAGE_STUDENTS.perm_name, team.course_id) + return ( + has_course_staff_privileges(user, team.course_id) or + user_has_manage_student_permission or ( + user_protection_status_matches_team(user, team) and user_on_team_or_team_is_public(user, team) + ) ) @@ -211,8 +224,13 @@ def has_specific_teamset_access(user, course_block, teamset_id): All non-staff users have access to open and public_managed teamsets. Non-staff users only have access to a private_managed teamset if they are in a team in that teamset """ - return has_course_staff_privileges(user, course_block.id) or \ + # TODO: remove role checks once course_roles is fully impelented and data is migrated + user_has_manage_student_permission = user.has_perm(CourseRolesPermission.MANAGE_STUDENTS.perm_name, course_block.id) + return ( + has_course_staff_privileges(user, course_block.id) or + user_has_manage_student_permission or teamset_is_public_or_user_is_on_team_in_teamset(user, course_block, teamset_id) + ) def teamset_is_public_or_user_is_on_team_in_teamset(user, course_block, teamset_id): @@ -322,9 +340,11 @@ def can_user_modify_team(user, team): Assumes that user is enrolled in course run. """ + # TODO: remove role checks once course_roles is fully impelented and data is migrated return ( (not is_instructor_managed_team(team)) or - has_course_staff_privileges(user, team.course_id) + has_course_staff_privileges(user, team.course_id) or + user.has_perm(CourseRolesPermission.MANAGE_STUDENTS.perm_name, team.course_id) ) @@ -334,9 +354,11 @@ def can_user_create_team_in_topic(user, course_id, topic_id): Assumes that user is enrolled in course run. """ + # TODO: remove role checks once course_roles is fully impelented and data is migrated return ( (not is_instructor_managed_topic(course_id, topic_id)) or - has_course_staff_privileges(user, course_id) + has_course_staff_privileges(user, course_id) or + user.has_perm(CourseRolesPermission.MANAGE_STUDENTS.perm_name, course_id) ) @@ -401,7 +423,11 @@ def anonymous_user_ids_for_team(user, team): if not user or not team: raise Exception("User and team must be provided for ID lookup") - if not has_course_staff_privileges(user, team.course_id) and not user_is_a_team_member(user, team): + # TODO: remove role checks once course_roles is fully impelented and data is migrated + if ( + not has_course_staff_privileges(user, team.course_id) and not + user.has_perm(CourseRolesPermission.MANAGE_STUDENTS.perm_name, team.course_id) + ) and not user_is_a_team_member(user, team): raise Exception("User {user} is not permitted to access team info for {team}".format( user=user.username, team=team.team_id diff --git a/lms/djangoapps/teams/tests/test_serializers.py b/lms/djangoapps/teams/tests/test_serializers.py index 384039bd661c..af7e5fe68951 100644 --- a/lms/djangoapps/teams/tests/test_serializers.py +++ b/lms/djangoapps/teams/tests/test_serializers.py @@ -251,7 +251,8 @@ def assert_serializer_output(self, topics, num_teams_per_topic, num_queries): request = RequestFactory().get('/api/team/v0/topics') request.user = self.user - with self.assertNumQueries(num_queries + 2): # num_queries on teams tables, plus 2 split modulestore queries + with self.assertNumQueries(num_queries + 3): + # num_queries on teams tables, plus 2 split modulestore queries, plus a perms query serializer = self.serializer( topics, context={ diff --git a/lms/djangoapps/teams/views.py b/lms/djangoapps/teams/views.py index a5370ffa937f..bd1970702d10 100644 --- a/lms/djangoapps/teams/views.py +++ b/lms/djangoapps/teams/views.py @@ -33,6 +33,7 @@ from lms.djangoapps.courseware.courses import get_course_with_access, has_access from lms.djangoapps.discussion.django_comment_client.utils import has_discussion_privileges from lms.djangoapps.teams.models import CourseTeam, CourseTeamMembership +from openedx.core.djangoapps.course_roles.data import CourseRolesPermission from openedx.core.lib.api.authentication import BearerAuthenticationAllowInactiveUser, BearerAuthentication from openedx.core.lib.api.parsers import MergePatchParser from openedx.core.lib.api.permissions import IsCourseStaffInstructor, IsStaffOrReadOnly @@ -1070,7 +1071,11 @@ def _filter_hidden_private_teamsets(user, teamsets, course_block): Return a filtered list of teamsets, removing any private teamsets that a user doesn't have access to. Follows the same logic as `has_specific_teamset_access` but in bulk rather than for one teamset at a time """ - if has_course_staff_privileges(user, course_block.id): + # TODO: remove role checks once course_roles is fully impelented and data is migrated + if ( + has_course_staff_privileges(user, course_block.id) or + user.has_perm(CourseRolesPermission.MANAGE_STUDENTS.perm_name, course_block.id) + ): return teamsets private_teamset_ids = [teamset.teamset_id for teamset in course_block.teamsets if teamset.is_private_managed] teamset_ids_user_has_access_to = set( @@ -1384,7 +1389,11 @@ def get(self, request): # lint-amnesty, pylint: disable=too-many-statements status=status.HTTP_404_NOT_FOUND ) teamset_teams = CourseTeam.objects.filter(course_id=requested_course_key, topic_id=teamset_id) - if has_course_staff_privileges(request.user, requested_course_key): + # TODO: remove role checks once course_roles is fully impelented and data is migrated + if ( + has_course_staff_privileges(request.user, requested_course_key) or + request.user.has_perm(CourseRolesPermission.MANAGE_STUDENTS.perm_name, requested_course_key) + ): teams_with_access = list(teamset_teams) else: teams_with_access = [ @@ -1689,7 +1698,11 @@ def check_access(self): """ Raises 403 if user does not have access to this endpoint. """ - if not has_course_staff_privileges(self.request.user, self.course.id): + # TODO: remove role checks once course_roles is fully impelented and data is migrated + if not ( + has_course_staff_privileges(self.request.user, self.course.id) or + self.request.user.has_perm(CourseRolesPermission.MANAGE_STUDENTS.perm_name, self.course.id) + ): raise PermissionDenied( "To manage team membership of {}, you must be course staff.".format( self.course.id diff --git a/openedx/core/djangoapps/content/learning_sequences/api/processors/schedule.py b/openedx/core/djangoapps/content/learning_sequences/api/processors/schedule.py index 7d6e373b43e1..dd8c7a6c53a4 100644 --- a/openedx/core/djangoapps/content/learning_sequences/api/processors/schedule.py +++ b/openedx/core/djangoapps/content/learning_sequences/api/processors/schedule.py @@ -12,6 +12,8 @@ from common.djangoapps.student.auth import user_has_role from common.djangoapps.student.roles import CourseBetaTesterRole +from openedx.core.djangoapps.course_roles.data import CourseRolesPermission + from ...data import ScheduleData, ScheduleItemData, UserCourseOutlineData from .base import OutlineProcessor @@ -60,7 +62,12 @@ def load_data(self, full_course_outline): course_usage_key = self.course_key.make_usage_key('course', 'course') self._course_start = self.keys_to_schedule_fields[course_usage_key].get('start') self._course_end = self.keys_to_schedule_fields[course_usage_key].get('end') - self._is_beta_tester = user_has_role(self.user, CourseBetaTesterRole(self.course_key)) + # TODO: remove role checks once course_roles is fully impelented and data is migrated + self._is_beta_tester = ( + user_has_role(self.user, CourseBetaTesterRole(self.course_key)) or + self.user.has_perm(CourseRolesPermission.VIEW_ALL_PUBLISHED_CONTENT.perm_name, self.course_key) or + self.user.has_perm(CourseRolesPermission.VIEW_LIVE_PUBLISHED_CONTENT.perm_name, self.course_key) + ) def inaccessible_sequences(self, full_course_outline): """ diff --git a/openedx/core/djangoapps/schedules/tests/test_resolvers.py b/openedx/core/djangoapps/schedules/tests/test_resolvers.py index 36bc8beacd32..19a66a6fb3a9 100644 --- a/openedx/core/djangoapps/schedules/tests/test_resolvers.py +++ b/openedx/core/djangoapps/schedules/tests/test_resolvers.py @@ -274,7 +274,7 @@ def create_resolver(self, user_start_date_offset=8): def test_schedule_context(self): resolver = self.create_resolver() # using this to make sure the select_related stays intact - with self.assertNumQueries(38): + with self.assertNumQueries(50): sc = resolver.get_schedules() schedules = list(sc) apple_logo_url = 'http://email-media.s3.amazonaws.com/edX/2021/store_apple_229x78.jpg' diff --git a/openedx/features/content_type_gating/tests/test_access.py b/openedx/features/content_type_gating/tests/test_access.py index c42a530e3ccf..8496655b21cf 100644 --- a/openedx/features/content_type_gating/tests/test_access.py +++ b/openedx/features/content_type_gating/tests/test_access.py @@ -1190,9 +1190,10 @@ def test_content_type_gate_for_block(self): self.user, blocks_dict['not_graded_1'], course['course'].id ) is None - @patch.object(ContentTypeGatingService, '_get_user', return_value=UserFactory.build()) + @patch.object(ContentTypeGatingService, '_get_user') def test_check_children_for_content_type_gating_paywall(self, mocked_user): # pylint: disable=unused-argument ''' Verify that the method returns a content type gate when appropriate ''' + mocked_user.return_value = self.user course = self._create_course() blocks_dict = course['blocks'] CourseEnrollmentFactory.create( diff --git a/openedx/features/content_type_gating/tests/test_models.py b/openedx/features/content_type_gating/tests/test_models.py index 64c7a5761514..f445f18ae336 100644 --- a/openedx/features/content_type_gating/tests/test_models.py +++ b/openedx/features/content_type_gating/tests/test_models.py @@ -75,7 +75,7 @@ def test_enabled_for_enrollment( user = self.user course_key = self.course_overview.id - query_count = 15 + query_count = 12 with self.assertNumQueries(query_count): enabled = ContentTypeGatingConfig.enabled_for_enrollment( diff --git a/openedx/features/course_duration_limits/tests/test_models.py b/openedx/features/course_duration_limits/tests/test_models.py index 7f17e95e281d..5a1606a49f0e 100644 --- a/openedx/features/course_duration_limits/tests/test_models.py +++ b/openedx/features/course_duration_limits/tests/test_models.py @@ -78,7 +78,7 @@ def test_enabled_for_enrollment( user = self.user course_key = self.course_overview.id # lint-amnesty, pylint: disable=unused-variable - query_count = 15 + query_count = 12 with self.assertNumQueries(query_count): enabled = CourseDurationLimitConfig.enabled_for_enrollment(user, self.course_overview)