From 3030efec78b77bcfcb363b0c3e7fb99dce9df82c Mon Sep 17 00:00:00 2001 From: Michael Terry Date: Mon, 22 Jun 2020 17:40:08 -0400 Subject: [PATCH] AA-177: Add masquerading for course home MFE - Looks at masquerading config for dates, outline, metadata, and celebration APIs in course_home_api / courseware_api. - Consolidates and cleans up places we check whether masquerading gives us full access to a course. --- common/djangoapps/course_modes/models.py | 16 ++- common/djangoapps/course_modes/views.py | 5 +- common/djangoapps/student/tests/test_views.py | 3 +- .../tests/test_field_override_performance.py | 4 +- lms/djangoapps/course_api/tests/test_views.py | 2 +- .../course_metadata/v1/views.py | 10 +- .../dates/v1/tests/test_views.py | 10 ++ .../course_home_api/dates/v1/views.py | 10 ++ .../outline/v1/tests/test_views.py | 21 +++- .../course_home_api/outline/v1/views.py | 17 +++- lms/djangoapps/course_home_api/tests/utils.py | 8 +- lms/djangoapps/courseware/courses.py | 6 +- lms/djangoapps/courseware/date_summary.py | 2 +- lms/djangoapps/courseware/tests/helpers.py | 46 +++++++++ .../courseware/tests/test_access.py | 6 +- .../courseware/tests/test_course_info.py | 4 +- .../courseware/tests/test_masquerade.py | 32 ++---- lms/djangoapps/courseware/tests/test_views.py | 18 ++-- .../django_comment_client/base/tests.py | 8 +- lms/djangoapps/discussion/tests/test_views.py | 16 +-- lms/djangoapps/experiments/utils.py | 2 +- .../mobile_api/users/serializers.py | 2 +- lms/djangoapps/mobile_api/users/tests.py | 3 +- lms/djangoapps/support/tests/test_views.py | 2 +- lms/djangoapps/support/views/enrollments.py | 2 +- .../djangoapps/config_model_utils/utils.py | 2 +- .../courseware_api/tests/test_views.py | 20 +++- .../core/djangoapps/courseware_api/views.py | 43 +++----- .../features/content_type_gating/helpers.py | 78 ++++++++++++++- .../features/content_type_gating/models.py | 83 +--------------- .../content_type_gating/partitions.py | 3 +- .../content_type_gating/tests/test_access.py | 31 +----- .../features/course_duration_limits/access.py | 4 +- .../features/course_duration_limits/models.py | 97 ++----------------- .../tests/test_course_expiration.py | 32 +----- .../tests/test_models.py | 7 +- .../tests/views/test_course_home.py | 6 +- .../tests/views/test_course_outline.py | 59 ++--------- .../tests/views/test_course_updates.py | 2 +- .../tests/views/test_masquerade.py | 43 ++------ 40 files changed, 318 insertions(+), 447 deletions(-) diff --git a/common/djangoapps/course_modes/models.py b/common/djangoapps/course_modes/models.py index 6152033f21..66f349b7da 100644 --- a/common/djangoapps/course_modes/models.py +++ b/common/djangoapps/course_modes/models.py @@ -336,17 +336,16 @@ class CourseMode(models.Model): @classmethod @request_cached(CACHE_NAMESPACE) def modes_for_course( - cls, course_id=None, include_expired=False, only_selectable=True, course=None, exclude_credit=True + cls, course_id=None, include_expired=False, only_selectable=True, course=None, ): """ Returns a list of the non-expired modes for a given course id If no modes have been set in the table, returns the default mode - Arguments: + Keyword Arguments: course_id (CourseKey): Search for course modes for this course. - Keyword Arguments: include_expired (bool): If True, expired course modes will be included in the returned JSON data. If False, these modes will be omitted. @@ -370,10 +369,10 @@ class CourseMode(models.Model): course_id = course.id course = None - if course_id is not None: - found_course_modes = cls.objects.filter(course_id=course_id) + if course is not None: + found_course_modes = course.modes.all() else: - found_course_modes = course.modes + found_course_modes = cls.objects.filter(course_id=course_id) # Filter out expired course modes if include_expired is not set if not include_expired: @@ -386,10 +385,7 @@ class CourseMode(models.Model): # we exclude them from the list if we're only looking for selectable modes # (e.g. on the track selection page or in the payment/verification flows). if only_selectable: - if course is not None and hasattr(course, 'selectable_modes'): - found_course_modes = course.selectable_modes - elif exclude_credit: - found_course_modes = found_course_modes.exclude(mode_slug__in=cls.CREDIT_MODES) + found_course_modes = found_course_modes.exclude(mode_slug__in=cls.CREDIT_MODES) modes = ([mode.to_tuple() for mode in found_course_modes]) if not modes: diff --git a/common/djangoapps/course_modes/views.py b/common/djangoapps/course_modes/views.py index 4cb6d52c06..c93c74bd6c 100644 --- a/common/djangoapps/course_modes/views.py +++ b/common/djangoapps/course_modes/views.py @@ -176,10 +176,7 @@ class ChooseModeView(View): user=request.user, course_key=course_key ), - "course_duration_limit_enabled": CourseDurationLimitConfig.enabled_for_enrollment( - user=request.user, - course_key=course_key - ), + "course_duration_limit_enabled": CourseDurationLimitConfig.enabled_for_enrollment(request.user, course), } context.update( get_experiment_user_metadata_context( diff --git a/common/djangoapps/student/tests/test_views.py b/common/djangoapps/student/tests/test_views.py index bfdc87c662..3096cf336d 100644 --- a/common/djangoapps/student/tests/test_views.py +++ b/common/djangoapps/student/tests/test_views.py @@ -778,7 +778,8 @@ class StudentDashboardTests(SharedModuleStoreTestCase, MilestonesTestCaseMixin, self.override_waffle_switch(True) course = CourseFactory.create(start=self.THREE_YEARS_AGO) - add_course_mode(course, upgrade_deadline_expired=False) + add_course_mode(course, mode_slug=CourseMode.AUDIT) + add_course_mode(course) enrollment = CourseEnrollmentFactory.create( user=self.user, course_id=course.id diff --git a/lms/djangoapps/ccx/tests/test_field_override_performance.py b/lms/djangoapps/ccx/tests/test_field_override_performance.py index c507dbd758..67fe65f765 100644 --- a/lms/djangoapps/ccx/tests/test_field_override_performance.py +++ b/lms/djangoapps/ccx/tests/test_field_override_performance.py @@ -244,7 +244,7 @@ class TestFieldOverrideMongoPerformance(FieldOverridePerformanceTestCase): __test__ = True # TODO: decrease query count as part of REVO-28 - QUERY_COUNT = 33 + QUERY_COUNT = 31 TEST_DATA = { # (providers, course_width, enable_ccx, view_as_ccx): ( # # of sql queries to default, @@ -273,7 +273,7 @@ class TestFieldOverrideSplitPerformance(FieldOverridePerformanceTestCase): __test__ = True # TODO: decrease query count as part of REVO-28 - QUERY_COUNT = 33 + QUERY_COUNT = 31 TEST_DATA = { ('no_overrides', 1, True, False): (QUERY_COUNT, 3), diff --git a/lms/djangoapps/course_api/tests/test_views.py b/lms/djangoapps/course_api/tests/test_views.py index 9ddc4af1f3..efdd4678ad 100644 --- a/lms/djangoapps/course_api/tests/test_views.py +++ b/lms/djangoapps/course_api/tests/test_views.py @@ -395,7 +395,7 @@ class CourseListSearchViewTest(CourseApiTestViewMixin, ModuleStoreTestCase, Sear self.setup_user(self.audit_user) # These query counts were found empirically - query_counts = [62, 45, 45, 45, 45, 45, 45, 45, 45, 45, 15] + query_counts = [53, 45, 45, 45, 45, 45, 45, 45, 45, 45, 15] ordered_course_ids = sorted([str(cid) for cid in (course_ids + [c.id for c in self.courses])]) self.clear_caches() diff --git a/lms/djangoapps/course_home_api/course_metadata/v1/views.py b/lms/djangoapps/course_home_api/course_metadata/v1/views.py index a9ca215e5a..8fed35903d 100644 --- a/lms/djangoapps/course_home_api/course_metadata/v1/views.py +++ b/lms/djangoapps/course_home_api/course_metadata/v1/views.py @@ -2,7 +2,6 @@ General view for the Course Home that contains metadata every page needs. """ - from rest_framework.generics import RetrieveAPIView from rest_framework.response import Response @@ -10,6 +9,7 @@ from opaque_keys.edx.keys import CourseKey from student.models import CourseEnrollment from lms.djangoapps.courseware.access import has_access +from lms.djangoapps.courseware.masquerade import setup_masquerade from lms.djangoapps.courseware.tabs import get_course_tab_list from lms.djangoapps.course_api.api import course_detail from lms.djangoapps.course_home_api.course_metadata.v1.serializers import CourseHomeMetadataSerializer @@ -52,6 +52,14 @@ class CourseHomeMetadataView(RetrieveAPIView): def get(self, request, *args, **kwargs): course_key_string = kwargs.get('course_key_string') course_key = CourseKey.from_string(course_key_string) + + _, request.user = setup_masquerade( + request, + course_key, + staff_access=has_access(request.user, 'staff', course_key), + reset_masquerade_data=True, + ) + course = course_detail(request, request.user.username, course_key) user_is_enrolled = CourseEnrollment.is_enrolled(request.user, course_key_string) diff --git a/lms/djangoapps/course_home_api/dates/v1/tests/test_views.py b/lms/djangoapps/course_home_api/dates/v1/tests/test_views.py index 6de10fbc58..7d103551fd 100644 --- a/lms/djangoapps/course_home_api/dates/v1/tests/test_views.py +++ b/lms/djangoapps/course_home_api/dates/v1/tests/test_views.py @@ -66,3 +66,13 @@ class DatesTabTestViews(BaseCourseHomeTests): self.assertContains(response, 'missed_gated_content') self.assertContains(response, 'content_type_gating_enabled') self.assertContains(response, 'verified_upgrade_link') + + @COURSE_HOME_MICROFRONTEND.override(active=True) + @COURSE_HOME_MICROFRONTEND_DATES_TAB.override(active=True) + def test_masquerade(self): + self.upgrade_to_staff() + CourseEnrollment.enroll(self.user, self.course.id, 'audit') + self.assertTrue(self.client.get(self.url).data.get('learner_is_full_access')) + + self.update_masquerade(role='student') + self.assertFalse(self.client.get(self.url).data.get('learner_is_full_access')) diff --git a/lms/djangoapps/course_home_api/dates/v1/views.py b/lms/djangoapps/course_home_api/dates/v1/views.py index ff544e808e..2f67db1f7e 100644 --- a/lms/djangoapps/course_home_api/dates/v1/views.py +++ b/lms/djangoapps/course_home_api/dates/v1/views.py @@ -10,9 +10,11 @@ from rest_framework.response import Response from edx_django_utils import monitoring as monitoring_utils from opaque_keys.edx.keys import CourseKey +from lms.djangoapps.courseware.access import has_access from lms.djangoapps.courseware.context_processor import user_timezone_locale_prefs from lms.djangoapps.courseware.courses import get_course_date_blocks, get_course_with_access from lms.djangoapps.courseware.date_summary import TodaysDate, verified_upgrade_deadline_link +from lms.djangoapps.courseware.masquerade import setup_masquerade from lms.djangoapps.course_home_api.dates.v1.serializers import DatesTabSerializer from lms.djangoapps.course_home_api.toggles import course_home_mfe_dates_tab_is_active from openedx.features.course_experience.utils import dates_banner_should_display @@ -70,6 +72,14 @@ class DatesTabView(RetrieveAPIView): monitoring_utils.set_custom_metric('is_staff', request.user.is_staff) course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=False) + + _, request.user = setup_masquerade( + request, + course_key, + staff_access=has_access(request.user, 'staff', course_key), + reset_masquerade_data=True, + ) + blocks = get_course_date_blocks(course, request.user, request, include_access=True, include_past_dates=True) missed_deadlines, missed_gated_content = dates_banner_should_display(course_key, request.user) diff --git a/lms/djangoapps/course_home_api/outline/v1/tests/test_views.py b/lms/djangoapps/course_home_api/outline/v1/tests/test_views.py index 2ec68cf865..5b479b177c 100644 --- a/lms/djangoapps/course_home_api/outline/v1/tests/test_views.py +++ b/lms/djangoapps/course_home_api/outline/v1/tests/test_views.py @@ -3,11 +3,13 @@ Tests for Outline Tab API in the Course Home API """ import ddt -from course_modes.models import CourseMode from django.urls import reverse -from student.models import CourseEnrollment +from course_modes.models import CourseMode from lms.djangoapps.course_home_api.tests.utils import BaseCourseHomeTests +from openedx.core.djangoapps.user_api.preferences.api import set_user_preference +from student.models import CourseEnrollment +from student.tests.factories import UserFactory @ddt.ddt @@ -40,7 +42,6 @@ class OutlineTabTestViews(BaseCourseHomeTests): def test_get_authenticated_user_not_enrolled(self): response = self.client.get(self.url) self.assertEqual(response.status_code, 200) - self.assertFalse(response.data.get('learner_is_verified')) course_tools = response.data.get('course_tools') self.assertEqual(len(course_tools), 0) @@ -56,4 +57,18 @@ class OutlineTabTestViews(BaseCourseHomeTests): response = self.client.get(self.url) self.assertEqual(response.status_code, 403) + def test_masquerade(self): + user = UserFactory() + set_user_preference(user, 'time_zone', 'Asia/Tokyo') + CourseEnrollment.enroll(user, self.course.id) + + self.upgrade_to_staff() # needed for masquerade + + # Sanity check on our normal user + self.assertEqual(self.client.get(self.url).data['dates_widget']['user_timezone'], None) + + # Now switch users and confirm we get a different result + self.update_masquerade(username=user.username) + self.assertEqual(self.client.get(self.url).data['dates_widget']['user_timezone'], 'Asia/Tokyo') + # TODO: write test_get_unknown_course when more data is pulled into the Outline Tab API diff --git a/lms/djangoapps/course_home_api/outline/v1/views.py b/lms/djangoapps/course_home_api/outline/v1/views.py index a5d5280e88..3d2f7b73b3 100644 --- a/lms/djangoapps/course_home_api/outline/v1/views.py +++ b/lms/djangoapps/course_home_api/outline/v1/views.py @@ -8,15 +8,17 @@ from rest_framework.response import Response from edx_django_utils import monitoring as monitoring_utils from django.urls import reverse -from opaque_keys.edx.keys import CourseKey, UsageKey +from opaque_keys.edx.keys import CourseKey from lms.djangoapps.course_api.blocks.transformers.blocks_api import BlocksAPITransformer from lms.djangoapps.course_home_api.outline.v1.serializers import OutlineTabSerializer from lms.djangoapps.course_home_api.toggles import course_home_mfe_dates_tab_is_active +from lms.djangoapps.courseware.access import has_access +from lms.djangoapps.courseware.context_processor import user_timezone_locale_prefs from lms.djangoapps.courseware.courses import get_course_date_blocks, get_course_with_access from lms.djangoapps.courseware.date_summary import TodaysDate -from lms.djangoapps.courseware.context_processor import user_timezone_locale_prefs +from lms.djangoapps.courseware.masquerade import setup_masquerade from lms.djangoapps.course_home_api.utils import get_microfrontend_url from openedx.core.djangoapps.content.block_structure.transformers import BlockStructureTransformers from openedx.features.course_experience.course_tools import CourseToolsPluginManager @@ -78,9 +80,16 @@ class OutlineTabView(RetrieveAPIView): monitoring_utils.set_custom_metric('user_id', request.user.id) monitoring_utils.set_custom_metric('is_staff', request.user.is_staff) - course_tools = CourseToolsPluginManager.get_enabled_course_tools(request, course_key) - course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=False) + + _, request.user = setup_masquerade( + request, + course_key, + staff_access=has_access(request.user, 'staff', course_key), + reset_masquerade_data=True, + ) + + course_tools = CourseToolsPluginManager.get_enabled_course_tools(request, course_key) date_blocks = get_course_date_blocks(course, request.user, request, num_assignments=1) # User locale settings diff --git a/lms/djangoapps/course_home_api/tests/utils.py b/lms/djangoapps/course_home_api/tests/utils.py index cfa98b3ccb..09c62d9014 100644 --- a/lms/djangoapps/course_home_api/tests/utils.py +++ b/lms/djangoapps/course_home_api/tests/utils.py @@ -2,7 +2,6 @@ Base classes or util functions for use in Course Home API tests """ - import unittest from datetime import datetime @@ -10,6 +9,7 @@ from django.conf import settings from course_modes.models import CourseMode from course_modes.tests.factories import CourseModeFactory +from lms.djangoapps.courseware.tests.helpers import MasqueradeMixin from lms.djangoapps.verify_student.models import VerificationDeadline from openedx.core.djangoapps.content.course_overviews.tests.factories import CourseOverviewFactory from student.tests.factories import UserFactory @@ -19,7 +19,7 @@ from xmodule.modulestore.tests.factories import ItemFactory, CourseFactory @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms') -class BaseCourseHomeTests(SharedModuleStoreTestCase): +class BaseCourseHomeTests(SharedModuleStoreTestCase, MasqueradeMixin): """ Base class for Course Home API tests. @@ -66,3 +66,7 @@ class BaseCourseHomeTests(SharedModuleStoreTestCase): def setUp(self): super().setUp() self.client.login(username=self.user.username, password='foo') + + def upgrade_to_staff(self): + self.user.is_staff = True + self.user.save() diff --git a/lms/djangoapps/courseware/courses.py b/lms/djangoapps/courseware/courses.py index 72b69b456f..4beabc17b1 100644 --- a/lms/djangoapps/courseware/courses.py +++ b/lms/djangoapps/courseware/courses.py @@ -609,11 +609,7 @@ def get_courses(user, org=None, filter_=None): org=org, filter_=filter_, ).prefetch_related( - Prefetch( - 'modes', - queryset=CourseMode.objects.exclude(mode_slug__in=CourseMode.CREDIT_MODES), - to_attr='selectable_modes', - ), + 'modes', ).select_related( 'image_set' ) diff --git a/lms/djangoapps/courseware/date_summary.py b/lms/djangoapps/courseware/date_summary.py index 172d153023..8cb8550398 100644 --- a/lms/djangoapps/courseware/date_summary.py +++ b/lms/djangoapps/courseware/date_summary.py @@ -434,7 +434,7 @@ class CourseExpiredDate(DateSummary): @property def date(self): - if not CourseDurationLimitConfig.enabled_for_enrollment(user=self.user, course_key=self.course_id): + if not CourseDurationLimitConfig.enabled_for_enrollment(self.user, self.course): return return get_user_course_expiration_date(self.user, self.course) diff --git a/lms/djangoapps/courseware/tests/helpers.py b/lms/djangoapps/courseware/tests/helpers.py index be4da4753b..bcee0f60a0 100644 --- a/lms/djangoapps/courseware/tests/helpers.py +++ b/lms/djangoapps/courseware/tests/helpers.py @@ -329,6 +329,52 @@ class CourseAccessTestMixin(TestCase): self.assertFalse(has_access(user, action, CourseOverview.get_from_id(course.id))) +class MasqueradeMixin: + """ + Adds masquerade utilities for your TestCase. + + Your test case class must have self.client. And can optionally have self.course if you don't want + to pass in the course parameter below. + """ + + def update_masquerade(self, course=None, role='student', group_id=None, username=None, user_partition_id=None): + """ + Installs a masquerade for the specified user and course, to enable + the user to masquerade as belonging to the specific partition/group + combination. + + Arguments: + course (object): a course or None for self.course + user_partition_id (int): the integer partition id, referring to partitions already + configured in the course. + group_id (int); the integer group id, within the specified partition. + username (str): user to masquerade as + role (str): role to masquerade as + + Returns: the response object for the AJAX call to update the user's masquerade. + """ + course = course or self.course + masquerade_url = reverse( + 'masquerade_update', + kwargs={ + 'course_key_string': str(course.id), + } + ) + response = self.client.post( + masquerade_url, + json.dumps({ + 'role': role, + 'group_id': group_id, + 'user_name': username, + 'user_partition_id': user_partition_id, + }), + 'application/json' + ) + self.assertEqual(response.status_code, 200) + self.assertTrue(response.json()['success'], response.json().get('error')) + return response + + def masquerade_as_group_member(user, course, partition_id, group_id): """ Installs a masquerade for the specified user and course, to enable diff --git a/lms/djangoapps/courseware/tests/test_access.py b/lms/djangoapps/courseware/tests/test_access.py index fc579a3544..3dc4c18d60 100644 --- a/lms/djangoapps/courseware/tests/test_access.py +++ b/lms/djangoapps/courseware/tests/test_access.py @@ -844,18 +844,18 @@ class CourseOverviewAccessTestCase(ModuleStoreTestCase): if user_attr_name == 'user_staff' and action == 'see_exists': # always checks staff role, and if the course has started, check the duration configuration if course_attr_name == 'course_started': - num_queries = 3 + num_queries = 2 else: num_queries = 1 elif user_attr_name == 'user_normal' and action == 'see_exists': if course_attr_name == 'course_started': - num_queries = 6 + num_queries = 4 else: # checks staff role and enrollment data num_queries = 2 elif user_attr_name == 'user_anonymous' and action == 'see_exists': if course_attr_name == 'course_started': - num_queries = 3 + num_queries = 1 else: num_queries = 0 else: diff --git a/lms/djangoapps/courseware/tests/test_course_info.py b/lms/djangoapps/courseware/tests/test_course_info.py index 59b535330e..eda6085a5d 100644 --- a/lms/djangoapps/courseware/tests/test_course_info.py +++ b/lms/djangoapps/courseware/tests/test_course_info.py @@ -431,8 +431,8 @@ class SelfPacedCourseInfoTestCase(LoginEnrollmentTestCase, SharedModuleStoreTest def test_num_queries_instructor_paced(self): # TODO: decrease query count as part of REVO-28 - self.fetch_course_info_with_queries(self.instructor_paced_course, 44, 3) + self.fetch_course_info_with_queries(self.instructor_paced_course, 42, 3) def test_num_queries_self_paced(self): # TODO: decrease query count as part of REVO-28 - self.fetch_course_info_with_queries(self.self_paced_course, 44, 3) + self.fetch_course_info_with_queries(self.self_paced_course, 42, 3) diff --git a/lms/djangoapps/courseware/tests/test_masquerade.py b/lms/djangoapps/courseware/tests/test_masquerade.py index 17327a02fe..a8d857be29 100644 --- a/lms/djangoapps/courseware/tests/test_masquerade.py +++ b/lms/djangoapps/courseware/tests/test_masquerade.py @@ -22,7 +22,7 @@ from lms.djangoapps.courseware.masquerade import ( CourseMasquerade, MasqueradingKeyValueStore, get_masquerading_user_group, ) from lms.djangoapps.courseware.tests.factories import StaffFactory -from lms.djangoapps.courseware.tests.helpers import LoginEnrollmentTestCase, masquerade_as_group_member +from lms.djangoapps.courseware.tests.helpers import LoginEnrollmentTestCase, MasqueradeMixin, masquerade_as_group_member from lms.djangoapps.courseware.tests.test_submitting_problems import ProblemSubmissionTestMixin from openedx.core.djangoapps.lang_pref import LANGUAGE_KEY from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration @@ -37,7 +37,7 @@ from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory from xmodule.partitions.partitions import Group, UserPartition -class MasqueradeTestCase(SharedModuleStoreTestCase, LoginEnrollmentTestCase): +class MasqueradeTestCase(SharedModuleStoreTestCase, LoginEnrollmentTestCase, MasqueradeMixin): """ Base class for masquerade tests that sets up a test course and enrolls a user in the course. """ @@ -205,24 +205,6 @@ class StaffMasqueradeTestCase(MasqueradeTestCase): """ return StaffFactory(course_key=self.course.id) - def update_masquerade(self, role, group_id=None, user_name=None): - """ - Toggle masquerade state. - """ - masquerade_url = reverse( - 'masquerade_update', - kwargs={ - 'course_key_string': six.text_type(self.course.id), - } - ) - response = self.client.post( - masquerade_url, - json.dumps({"role": role, "group_id": group_id, "user_name": user_name}), - "application/json" - ) - self.assertEqual(response.status_code, 200) - return response - class TestStaffMasqueradeAsStudent(StaffMasqueradeTestCase): """ @@ -334,7 +316,7 @@ class TestStaffMasqueradeAsSpecificStudent(StaffMasqueradeTestCase, ProblemSubmi # Masquerade as the student,enable the self paced configuration, and check we can see the info page. SelfPacedConfiguration(enable_course_home_improvements=True).save() - self.update_masquerade(role='student', user_name=self.student_user.username) + self.update_masquerade(role='student', username=self.student_user.username) response = self.get_course_info_page() self.assertContains(response, "OOGIE BLOOGIE") @@ -363,7 +345,7 @@ class TestStaffMasqueradeAsSpecificStudent(StaffMasqueradeTestCase, ProblemSubmi self.assertEqual(self.get_progress_detail(), u'0/2') # Masquerade as the student, and check we can see the student state. - self.update_masquerade(role='student', user_name=student.username) + self.update_masquerade(role='student', username=student.username) self.assertEqual(self.get_progress_detail(), u'2/2') # Temporarily override the student state. @@ -402,7 +384,7 @@ class TestStaffMasqueradeAsSpecificStudent(StaffMasqueradeTestCase, ProblemSubmi # Set student language preference and set masquerade to view same page the student. set_user_preference(self.student_user, preference_key=LANGUAGE_KEY, preference_value='es-419') - self.update_masquerade(role='student', user_name=self.student_user.username) + self.update_masquerade(role='student', username=self.student_user.username) # Reload the page and check we have expected language preference in system and in cookies. self.get_courseware_page() @@ -423,7 +405,7 @@ class TestStaffMasqueradeAsSpecificStudent(StaffMasqueradeTestCase, ProblemSubmi self.assertIn("OOGIE BLOOGIE", content) # Masquerade as the student, and check we can see the info page. - self.update_masquerade(role='student', user_name=self.student_user.username) + self.update_masquerade(role='student', username=self.student_user.username) content = self.get_course_info_page().content.decode('utf-8') self.assertIn("OOGIE BLOOGIE", content) @@ -446,7 +428,7 @@ class TestStaffMasqueradeAsSpecificStudent(StaffMasqueradeTestCase, ProblemSubmi self.assertIn("1 of 2 possible points", staff_progress) # Should now see the student's scores - self.update_masquerade(role='student', user_name=self.student_user.username) + self.update_masquerade(role='student', username=self.student_user.username) masquerade_progress = self.get_progress_page().content.decode('utf-8') self.assertNotIn("1 of 2 possible points", masquerade_progress) self.assertIn("2 of 2 possible points", masquerade_progress) diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py index 851eb4bac8..fa29d0b48d 100644 --- a/lms/djangoapps/courseware/tests/test_views.py +++ b/lms/djangoapps/courseware/tests/test_views.py @@ -268,8 +268,8 @@ class IndexQueryTestCase(ModuleStoreTestCase): NUM_PROBLEMS = 20 @ddt.data( - (ModuleStoreEnum.Type.mongo, 10, 167), - (ModuleStoreEnum.Type.split, 4, 165), + (ModuleStoreEnum.Type.mongo, 10, 170), + (ModuleStoreEnum.Type.split, 4, 166), ) @ddt.unpack def test_index_query_counts(self, store_type, expected_mongo_query_count, expected_mysql_query_count): @@ -1434,8 +1434,8 @@ class ProgressPageTests(ProgressPageBaseTests): self.assertContains(resp, u"Download Your Certificate") @ddt.data( - (True, 54), - (False, 53), + (True, 52), + (False, 51), ) @ddt.unpack def test_progress_queries_paced_courses(self, self_paced, query_count): @@ -1448,8 +1448,8 @@ class ProgressPageTests(ProgressPageBaseTests): @patch.dict(settings.FEATURES, {'ASSUME_ZERO_GRADE_IF_ABSENT_FOR_ALL_TESTS': False}) @ddt.data( - (False, 62, 40), - (True, 53, 35) + (False, 60, 41), + (True, 51, 36) ) @ddt.unpack def test_progress_queries(self, enable_waffle, initial, subsequent): @@ -1628,7 +1628,8 @@ class ProgressPageTests(ProgressPageBaseTests): CourseDurationLimitConfig.objects.create(enabled=True, enabled_as_of=datetime(2018, 1, 1)) user = UserFactory.create() self.assertTrue(self.client.login(username=user.username, password='test')) - add_course_mode(self.course, upgrade_deadline_expired=False) + add_course_mode(self.course, mode_slug=CourseMode.AUDIT) + add_course_mode(self.course) CourseEnrollmentFactory(user=user, course_id=self.course.id, mode=course_mode) response = self._get_progress_page() @@ -2807,7 +2808,8 @@ class TestIndexViewWithCourseDurationLimits(ModuleStoreTestCase): """ CourseDurationLimitConfig.objects.create(enabled=True, enabled_as_of=datetime(2018, 1, 1)) self.assertTrue(self.client.login(username=self.user.username, password='test')) - add_course_mode(self.course, upgrade_deadline_expired=False) + add_course_mode(self.course, mode_slug=CourseMode.AUDIT) + add_course_mode(self.course) response = self.client.get( reverse( 'courseware_section', diff --git a/lms/djangoapps/discussion/django_comment_client/base/tests.py b/lms/djangoapps/discussion/django_comment_client/base/tests.py index de936ae575..d1c4a47af4 100644 --- a/lms/djangoapps/discussion/django_comment_client/base/tests.py +++ b/lms/djangoapps/discussion/django_comment_client/base/tests.py @@ -405,8 +405,8 @@ class ViewsQueryCountTestCase( return inner @ddt.data( - (ModuleStoreEnum.Type.mongo, 3, 4, 40), - (ModuleStoreEnum.Type.split, 3, 13, 40), + (ModuleStoreEnum.Type.mongo, 3, 4, 38), + (ModuleStoreEnum.Type.split, 3, 13, 38), ) @ddt.unpack @count_queries @@ -414,8 +414,8 @@ class ViewsQueryCountTestCase( self.create_thread_helper(mock_request) @ddt.data( - (ModuleStoreEnum.Type.mongo, 3, 3, 36), - (ModuleStoreEnum.Type.split, 3, 10, 36), + (ModuleStoreEnum.Type.mongo, 3, 3, 34), + (ModuleStoreEnum.Type.split, 3, 10, 34), ) @ddt.unpack @count_queries diff --git a/lms/djangoapps/discussion/tests/test_views.py b/lms/djangoapps/discussion/tests/test_views.py index 5c3274b01a..78021561dc 100644 --- a/lms/djangoapps/discussion/tests/test_views.py +++ b/lms/djangoapps/discussion/tests/test_views.py @@ -463,18 +463,18 @@ class SingleThreadQueryCountTestCase(ForumsEnableMixin, ModuleStoreTestCase): # course is outside the context manager that is verifying the number of queries, # and with split mongo, that method ends up querying disabled_xblocks (which is then # cached and hence not queried as part of call_single_thread). - (ModuleStoreEnum.Type.mongo, False, 1, 5, 2, 23, 7), - (ModuleStoreEnum.Type.mongo, False, 50, 5, 2, 23, 7), + (ModuleStoreEnum.Type.mongo, False, 1, 5, 2, 21, 7), + (ModuleStoreEnum.Type.mongo, False, 50, 5, 2, 21, 7), # split mongo: 3 queries, regardless of thread response size. - (ModuleStoreEnum.Type.split, False, 1, 3, 3, 23, 7), - (ModuleStoreEnum.Type.split, False, 50, 3, 3, 23, 7), + (ModuleStoreEnum.Type.split, False, 1, 3, 3, 21, 8), + (ModuleStoreEnum.Type.split, False, 50, 3, 3, 21, 8), # Enabling Enterprise integration should have no effect on the number of mongo queries made. - (ModuleStoreEnum.Type.mongo, True, 1, 5, 2, 23, 7), - (ModuleStoreEnum.Type.mongo, True, 50, 5, 2, 23, 7), + (ModuleStoreEnum.Type.mongo, True, 1, 5, 2, 21, 7), + (ModuleStoreEnum.Type.mongo, True, 50, 5, 2, 21, 7), # split mongo: 3 queries, regardless of thread response size. - (ModuleStoreEnum.Type.split, True, 1, 3, 3, 23, 7), - (ModuleStoreEnum.Type.split, True, 50, 3, 3, 23, 7), + (ModuleStoreEnum.Type.split, True, 1, 3, 3, 21, 8), + (ModuleStoreEnum.Type.split, True, 50, 3, 3, 21, 8), ) @ddt.unpack def test_number_of_mongo_queries( diff --git a/lms/djangoapps/experiments/utils.py b/lms/djangoapps/experiments/utils.py index f04d81362a..4ee77775b5 100644 --- a/lms/djangoapps/experiments/utils.py +++ b/lms/djangoapps/experiments/utils.py @@ -348,7 +348,7 @@ def get_audit_access_expiration(user, course): """ Return the expiration date and course duration for the user's audit access to this course. """ - if not CourseDurationLimitConfig.enabled_for_enrollment(user=user, course_key=course.id): + if not CourseDurationLimitConfig.enabled_for_enrollment(user, course): return None, None return get_user_course_expiration_date(user, course), get_user_course_duration(user, course) diff --git a/lms/djangoapps/mobile_api/users/serializers.py b/lms/djangoapps/mobile_api/users/serializers.py index 762e511fae..f64fc52fc4 100644 --- a/lms/djangoapps/mobile_api/users/serializers.py +++ b/lms/djangoapps/mobile_api/users/serializers.py @@ -96,7 +96,7 @@ class CourseEnrollmentSerializer(serializers.ModelSerializer): """ Returns expiration date for a course audit expiration, if any or null """ - if not CourseDurationLimitConfig.enabled_for_enrollment(user=model.user, course_key=model.course.id): + if not CourseDurationLimitConfig.enabled_for_enrollment(model.user, model.course): return None return get_user_course_expiration_date(model.user, model.course) diff --git a/lms/djangoapps/mobile_api/users/tests.py b/lms/djangoapps/mobile_api/users/tests.py index e93a7fb42f..68e92a88f9 100644 --- a/lms/djangoapps/mobile_api/users/tests.py +++ b/lms/djangoapps/mobile_api/users/tests.py @@ -290,7 +290,8 @@ class TestUserEnrollmentApi(UrlResetMixin, MobileAPITestCase, MobileAuthUserTest course = CourseFactory.create(start=self.LAST_WEEK, mobile_available=True) self.enroll(course.id) - add_course_mode(course, upgrade_deadline_expired=False) + add_course_mode(course, mode_slug=CourseMode.AUDIT) + add_course_mode(course) def _get_enrollment_data(self, api_version, expired): self.login() diff --git a/lms/djangoapps/support/tests/test_views.py b/lms/djangoapps/support/tests/test_views.py index babfa2f94f..330abacca4 100644 --- a/lms/djangoapps/support/tests/test_views.py +++ b/lms/djangoapps/support/tests/test_views.py @@ -391,7 +391,7 @@ class SupportViewEnrollmentsTests(SharedModuleStoreTestCase, SupportViewTestCase def _assert_generated_modes(self, response): """Dry method to generate course modes dict and test with response data.""" - modes = CourseMode.modes_for_course(self.course.id, include_expired=True, exclude_credit=False) + modes = CourseMode.modes_for_course(self.course.id, include_expired=True, only_selectable=False) modes_data = [] for mode in modes: expiry = mode.expiration_datetime.strftime('%Y-%m-%dT%H:%M:%SZ') if mode.expiration_datetime else None diff --git a/lms/djangoapps/support/views/enrollments.py b/lms/djangoapps/support/views/enrollments.py index 6a7f2dd399..7240cf2658 100644 --- a/lms/djangoapps/support/views/enrollments.py +++ b/lms/djangoapps/support/views/enrollments.py @@ -188,7 +188,7 @@ class EnrollmentSupportListView(GenericAPIView): course_modes = CourseMode.modes_for_course( course_key, include_expired=True, - exclude_credit=False + only_selectable=False, ) return [ ModeSerializer(mode).data diff --git a/openedx/core/djangoapps/config_model_utils/utils.py b/openedx/core/djangoapps/config_model_utils/utils.py index 692a61934f..cc41ed263c 100644 --- a/openedx/core/djangoapps/config_model_utils/utils.py +++ b/openedx/core/djangoapps/config_model_utils/utils.py @@ -4,7 +4,7 @@ from student.models import FBEEnrollmentExclusion -def is_in_holdback(user, enrollment): +def is_in_holdback(enrollment): """ Return true if given user is in holdback expermiment """ diff --git a/openedx/core/djangoapps/courseware_api/tests/test_views.py b/openedx/core/djangoapps/courseware_api/tests/test_views.py index 5c8df000f2..3da184df0c 100644 --- a/openedx/core/djangoapps/courseware_api/tests/test_views.py +++ b/openedx/core/djangoapps/courseware_api/tests/test_views.py @@ -11,6 +11,7 @@ from django.conf import settings from lms.djangoapps.courseware.access_utils import ACCESS_DENIED, ACCESS_GRANTED from lms.djangoapps.courseware.tabs import ExternalLinkCourseTab +from lms.djangoapps.courseware.tests.helpers import MasqueradeMixin from student.models import CourseEnrollment, CourseEnrollmentCelebration from student.tests.factories import CourseEnrollmentCelebrationFactory, UserFactory from xmodule.modulestore.django import modulestore @@ -159,7 +160,7 @@ class ResumeApiTestViews(BaseCoursewareTests, CompletionWaffleTestMixin): @ddt.ddt -class CelebrationApiTestViews(BaseCoursewareTests): +class CelebrationApiTestViews(BaseCoursewareTests, MasqueradeMixin): """ Tests for the celebration API """ @@ -207,3 +208,20 @@ class CelebrationApiTestViews(BaseCoursewareTests): response = self.client.post('/api/courseware/celebration/course-v1:does+not+exist', {'first_section': True}, content_type='application/json') assert response.status_code == 404 + + def test_masquerade(self): + self.user.is_staff = True + self.user.save() + + user = UserFactory() + CourseEnrollment.enroll(user, self.course.id, 'verified') + + response = self.client.post(self.url, {'first_section': True}, content_type='application/json') + assert response.status_code == 201 + + self.update_masquerade(username=user.username) + response = self.client.post(self.url, {'first_section': False}, content_type='application/json') + assert response.status_code == 202 + + celebration = CourseEnrollmentCelebration.objects.first() + assert celebration.celebrate_first_section # make sure it didn't change during masquerade attempt diff --git a/openedx/core/djangoapps/courseware_api/views.py b/openedx/core/djangoapps/courseware_api/views.py index e7c35852a2..fa63450dae 100644 --- a/openedx/core/djangoapps/courseware_api/views.py +++ b/openedx/core/djangoapps/courseware_api/views.py @@ -21,12 +21,6 @@ from edxnotes.helpers import is_feature_enabled from lms.djangoapps.course_api.api import course_detail from lms.djangoapps.courseware.access import has_access from lms.djangoapps.courseware.courses import check_course_access -from lms.djangoapps.courseware.masquerade import is_masquerading -from lms.djangoapps.courseware.masquerade import is_masquerading_as_audit_enrollment -from lms.djangoapps.courseware.masquerade import is_masquerading_as_full_access -from lms.djangoapps.courseware.masquerade import is_masquerading_as_limited_access -from lms.djangoapps.courseware.masquerade import is_masquerading_as_non_audit_enrollment -from lms.djangoapps.courseware.masquerade import is_masquerading_as_staff from lms.djangoapps.courseware.masquerade import setup_masquerade from lms.djangoapps.courseware.module_render import get_module_by_usage_id from lms.djangoapps.courseware.tabs import get_course_tab_list @@ -96,27 +90,10 @@ class CoursewareMeta: @property def content_type_gating_enabled(self): - course_key = self.course_key - user = self.effective_user - is_enabled = None - course_masquerade = self.course_masquerade - if is_masquerading(user, course_key, course_masquerade): - if is_masquerading_as_staff(user, course_key): - is_enabled = False - elif is_masquerading_as_full_access(user, course_key, course_masquerade): - is_enabled = False - elif is_masquerading_as_non_audit_enrollment(user, course_key, course_masquerade): - is_enabled = False - elif is_masquerading_as_audit_enrollment(user, course_key, course_masquerade): - is_enabled = ContentTypeGatingConfig.enabled_for_course(course_key) - elif is_masquerading_as_limited_access(user, course_key, course_masquerade): - is_enabled = ContentTypeGatingConfig.enabled_for_course(course_key) - if is_enabled is None: - is_enabled = ContentTypeGatingConfig.enabled_for_enrollment( - user=user, - course_key=course_key, - ) - return is_enabled + return ContentTypeGatingConfig.enabled_for_enrollment( + user=self.effective_user, + course_key=self.course_key, + ) @property def can_show_upgrade_sock(self): @@ -396,7 +373,7 @@ class Celebration(DeveloperErrorViewMixin, APIView): **Returns** - * 200 or 201 on success with above fields. + * 200 or 201 or 202 on success with above fields. * 400 if an invalid parameter was sent. * 404 if the course is not available or cannot be seen. """ @@ -414,6 +391,16 @@ class Celebration(DeveloperErrorViewMixin, APIView): """ course_key = CourseKey.from_string(course_key_string) + # Check if we're masquerading as someone else. If so, we should just ignore this request. + _, user = setup_masquerade( + request, + course_key, + staff_access=has_access(request.user, 'staff', course_key), + reset_masquerade_data=True, + ) + if user != request.user: + return Response(status=202) # "Accepted" + data = dict(request.data) first_section = data.pop('first_section', None) if data: diff --git a/openedx/features/content_type_gating/helpers.py b/openedx/features/content_type_gating/helpers.py index d1a6705327..2326e209af 100644 --- a/openedx/features/content_type_gating/helpers.py +++ b/openedx/features/content_type_gating/helpers.py @@ -2,10 +2,14 @@ Helper functions used by both content_type_gating and course_duration_limits. """ - import logging +from django.utils import timezone + from course_modes.models import CourseMode +from openedx.core.djangoapps.config_model_utils.utils import is_in_holdback +from student.models import CourseEnrollment +from student.role_helpers import has_staff_roles from xmodule.partitions.partitions import Group # Studio generates partition IDs starting at 100. There is already a manually generated @@ -21,16 +25,22 @@ FULL_ACCESS = Group(CONTENT_TYPE_GATE_GROUP_IDS['full_access'], 'Full-access Use LOG = logging.getLogger(__name__) -def correct_modes_for_fbe(course_key, enrollment=None, user=None): +def correct_modes_for_fbe(course_key=None, enrollment=None, user=None, course=None): """ If CONTENT_TYPE_GATING is enabled use the following logic to determine whether enabled_for_enrollment should be false """ - if course_key is None: + if course_key is None and course is None: return True - modes = CourseMode.modes_for_course(course_key, include_expired=True, only_selectable=False) + # Separate these two calls to help with cache hits (most modes_for_course callers pass in a positional course key) + if course: + modes = CourseMode.modes_for_course(course=course, include_expired=True, only_selectable=False) + else: + modes = CourseMode.modes_for_course(course_key, include_expired=True, only_selectable=False) + modes_dict = {mode.slug: mode for mode in modes} + course_key = course_key or course.id # If there is no audit mode or no verified mode, FBE will not be enabled if (CourseMode.AUDIT not in modes_dict) or (CourseMode.VERIFIED not in modes_dict): @@ -57,3 +67,63 @@ def correct_modes_for_fbe(course_key, enrollment=None, user=None): if mode_slug != CourseMode.AUDIT: return False return True + + +def has_full_access_role_in_masquerade(user, course_key): + """ + The roles of the masquerade user are used to determine whether the content gate displays. + + Returns: + True if we are masquerading as a full-access generic user + False if we are masquerading as a non-full-access generic user + None if we are not masquerading or masquerading as a specific student that should go through normal checks + """ + # The masquerade module imports from us, so avoid a circular dependency here + from lms.djangoapps.courseware.masquerade import ( + get_course_masquerade, is_masquerading_as_full_access, is_masquerading_as_non_audit_enrollment, + is_masquerading_as_specific_student, is_masquerading_as_staff, + ) + + course_masquerade = get_course_masquerade(user, course_key) + if not course_masquerade or is_masquerading_as_specific_student(user, course_key): + return None + return (is_masquerading_as_staff(user, course_key) or + is_masquerading_as_full_access(user, course_key, course_masquerade) or + is_masquerading_as_non_audit_enrollment(user, course_key, course_masquerade)) + + +def enrollment_date_for_fbe(user, course_key=None, course=None): + """ + Gets the enrollment date for the given user and course, if FBE is enabled. + + One of course_key or course must be provided. + + Returns: + None if FBE is disabled for either this user or course + The enrollment creation date if an enrollment exists + now() if no enrollment. + """ + if user is None or (course_key is None and course is None): + raise ValueError('Both user and either course_key or course must be specified if no enrollment is provided') + + course_key = course_key or course.id + + full_access_masquerade = has_full_access_role_in_masquerade(user, course_key) + if full_access_masquerade: + return None + elif full_access_masquerade is False: + user = None # we are masquerading as a generic user, not a specific one -- avoid all user checks below + + if user and user.id and has_staff_roles(user, course_key): + return None + + enrollment = user and CourseEnrollment.get_enrollment(user, course_key, ['fbeenrollmentexclusion']) + + if is_in_holdback(enrollment): + return None + + if not correct_modes_for_fbe(enrollment=enrollment, user=user, course_key=course_key, course=course): + return None + + # If the user isn't enrolled, act as if the user enrolled today + return enrollment.created if enrollment else timezone.now() diff --git a/openedx/features/content_type_gating/models.py b/openedx/features/content_type_gating/models.py index 1a402de9f7..726786e23c 100644 --- a/openedx/features/content_type_gating/models.py +++ b/openedx/features/content_type_gating/models.py @@ -2,28 +2,14 @@ Content Type Gating Configuration Models """ -# -*- coding: utf-8 -*- - - -from django.conf import settings from django.core.exceptions import ValidationError from django.db import models from django.utils import timezone from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext_lazy as _ -from course_modes.models import CourseMode -from lms.djangoapps.courseware.masquerade import ( - get_course_masquerade, - get_masquerading_user_group, - is_masquerading_as_specific_student -) from openedx.core.djangoapps.config_model_utils.models import StackedConfigurationModel -from openedx.core.djangoapps.config_model_utils.utils import is_in_holdback -from openedx.features.content_type_gating.helpers import FULL_ACCESS, LIMITED_ACCESS, correct_modes_for_fbe -from student.models import CourseEnrollment -from student.role_helpers import has_staff_roles -from xmodule.partitions.partitions import ENROLLMENT_TRACK_PARTITION_ID +from openedx.features.content_type_gating.helpers import correct_modes_for_fbe, enrollment_date_for_fbe @python_2_unicode_compatible @@ -57,36 +43,7 @@ class ContentTypeGatingConfig(StackedConfigurationModel): ) @classmethod - def has_full_access_role_in_masquerade(cls, user, course_key, course_masquerade, student_masquerade, - user_partition): - """ - The roles of the masquerade user are used to determine whether the content gate displays. - The gate will not appear if the masquerade user has any of the following roles: - Staff, Instructor, Beta Tester, Forum Community TA, Forum Group Moderator, Forum Moderator, Forum Administrator - """ - if student_masquerade: - # If a request is masquerading as a specific user, the user variable will represent the correct user. - if user and user.id and has_staff_roles(user, course_key): - return True - elif user_partition: - # If the current user is masquerading as a generic student in a specific group, - # then return the value based on that group. - masquerade_group = get_masquerading_user_group(course_key, user, user_partition) - if masquerade_group is None: - audit_mode_id = settings.COURSE_ENROLLMENT_MODES.get(CourseMode.AUDIT, {}).get('id') - # We are checking the user partition id here because currently content - # cannot have both the enrollment track partition and content gating partition - # configured simultaneously. We may change this in the future and allow - # configuring both partitions on content and selecting both partitions in masquerade. - if course_masquerade.user_partition_id == ENROLLMENT_TRACK_PARTITION_ID: - return course_masquerade.group_id != audit_mode_id - elif masquerade_group is FULL_ACCESS: - return True - elif masquerade_group is LIMITED_ACCESS: - return False - - @classmethod - def enabled_for_enrollment(cls, user=None, course_key=None, user_partition=None): + def enabled_for_enrollment(cls, user=None, course_key=None): """ Return whether Content Type Gating is enabled for this enrollment. @@ -99,41 +56,9 @@ class ContentTypeGatingConfig(StackedConfigurationModel): user: The user being queried. course_key: The CourseKey of the course being queried. """ - if user is None or course_key is None: - raise ValueError('Both user and course_key must be specified if no enrollment is provided') - - enrollment = CourseEnrollment.get_enrollment(user, course_key, ['fbeenrollmentexclusion']) - - if user is None and enrollment is not None: - user = enrollment.user - - course_masquerade = get_course_masquerade(user, course_key) - no_masquerade = course_masquerade is None - student_masquerade = is_masquerading_as_specific_student(user, course_key) - user_variable_represents_correct_user = (no_masquerade or student_masquerade) - - if course_masquerade: - if cls.has_full_access_role_in_masquerade(user, course_key, course_masquerade, student_masquerade, - user_partition): - return False - # When a request is not in a masquerade state the user variable represents the correct user. - elif user and user.id and has_staff_roles(user, course_key): + target_datetime = enrollment_date_for_fbe(user, course_key=course_key) + if not target_datetime: return False - - # check if user is in holdback - if user_variable_represents_correct_user and is_in_holdback(user, enrollment): - return False - - if not correct_modes_for_fbe(course_key, enrollment, user): - return False - - # enrollment might be None if the user isn't enrolled. In that case, - # return enablement as if the user enrolled today - # Also, ignore enrollment creation date if the user is masquerading. - if enrollment is None or course_masquerade: - target_datetime = timezone.now() - else: - target_datetime = enrollment.created current_config = cls.current(course_key=course_key) return current_config.enabled_as_of_datetime(target_datetime=target_datetime) diff --git a/openedx/features/content_type_gating/partitions.py b/openedx/features/content_type_gating/partitions.py index c2e7ebc3cf..77369ddee0 100644 --- a/openedx/features/content_type_gating/partitions.py +++ b/openedx/features/content_type_gating/partitions.py @@ -148,8 +148,7 @@ class ContentTypeGatingPartitionScheme(object): """ Returns the Group for the specified user. """ - if not ContentTypeGatingConfig.enabled_for_enrollment(user=user, course_key=course_key, - user_partition=user_partition): + if not ContentTypeGatingConfig.enabled_for_enrollment(user=user, course_key=course_key): return FULL_ACCESS else: return LIMITED_ACCESS diff --git a/openedx/features/content_type_gating/tests/test_access.py b/openedx/features/content_type_gating/tests/test_access.py index 8da0f5872e..584fc0579a 100644 --- a/openedx/features/content_type_gating/tests/test_access.py +++ b/openedx/features/content_type_gating/tests/test_access.py @@ -18,12 +18,8 @@ from django.contrib.auth.models import User from mock import patch, Mock from pyquery import PyQuery as pq -from six.moves.html_parser import HTMLParser - -from course_modes.models import CourseMode from course_api.blocks.api import get_blocks from course_modes.tests.factories import CourseModeFactory -from experiments.models import ExperimentData, ExperimentKeyValue from lms.djangoapps.courseware.module_render import load_single_xblock from lms.djangoapps.courseware.tests.factories import ( BetaTesterFactory, @@ -33,6 +29,7 @@ from lms.djangoapps.courseware.tests.factories import ( OrgStaffFactory, StaffFactory ) +from lms.djangoapps.courseware.tests.helpers import MasqueradeMixin from lms.djangoapps.discussion.django_comment_client.tests.factories import RoleFactory from openedx.core.djangoapps.django_comment_common.models import ( FORUM_ROLE_ADMINISTRATOR, @@ -166,7 +163,7 @@ def _assert_block_is_empty(block, user_id, course, request_factory): @override_settings(FIELD_OVERRIDE_PROVIDERS=( 'openedx.features.content_type_gating.field_override.ContentTypeGatingFieldOverride', )) -class TestProblemTypeAccess(SharedModuleStoreTestCase): +class TestProblemTypeAccess(SharedModuleStoreTestCase, MasqueradeMixin): PROBLEM_TYPES = ['problem', 'openassessment', 'drag-and-drop-v2', 'done', 'edx_sga'] # 'html' is a component that just displays html, in these tests it is used to test that users who do not have access @@ -690,29 +687,6 @@ class TestProblemTypeAccess(SharedModuleStoreTestCase): else: self.assertEqual(response.status_code, 200) - def update_masquerade(self, role='student', group_id=None, username=None, user_partition_id=None): - """ - Toggle masquerade state. - """ - masquerade_url = reverse( - 'masquerade_update', - kwargs={ - 'course_key_string': six.text_type(self.course.id), - } - ) - response = self.client.post( - masquerade_url, - json.dumps({ - 'role': role, - 'group_id': group_id, - 'user_name': username, - 'user_partition_id': user_partition_id, - }), - 'application/json' - ) - self.assertEqual(response.status_code, 200) - return response - @ddt.data( InstructorFactory, StaffFactory, @@ -740,6 +714,7 @@ class TestProblemTypeAccess(SharedModuleStoreTestCase): user = role_factory.create() else: user = role_factory.create(course_key=self.course.id) + CourseEnrollment.enroll(user, self.course.id) self.update_masquerade(username=user.username) block = self.blocks_dict['problem'] diff --git a/openedx/features/course_duration_limits/access.py b/openedx/features/course_duration_limits/access.py index 8696d21561..a3f837c100 100644 --- a/openedx/features/course_duration_limits/access.py +++ b/openedx/features/course_duration_limits/access.py @@ -107,7 +107,7 @@ def check_course_expired(user, course): if get_course_masquerade(user, course.id): return ACCESS_GRANTED - if not CourseDurationLimitConfig.enabled_for_enrollment(user=user, course_key=course.id): + if not CourseDurationLimitConfig.enabled_for_enrollment(user, course): return ACCESS_GRANTED expiration_date = get_user_course_expiration_date(user, course) @@ -127,7 +127,7 @@ def generate_course_expired_message(user, course): """ Generate the message for the user course expiration date if it exists. """ - if not CourseDurationLimitConfig.enabled_for_enrollment(user=user, course_key=course.id): + if not CourseDurationLimitConfig.enabled_for_enrollment(user, course): return expiration_date = get_user_course_expiration_date(user, course) diff --git a/openedx/features/course_duration_limits/models.py b/openedx/features/course_duration_limits/models.py index 12b0f93d78..41f3f4b0e7 100644 --- a/openedx/features/course_duration_limits/models.py +++ b/openedx/features/course_duration_limits/models.py @@ -2,32 +2,14 @@ Course Duration Limit Configuration Models """ -# -*- coding: utf-8 -*- - - -from django.conf import settings from django.core.exceptions import ValidationError from django.db import models from django.utils import timezone from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext_lazy as _ -from course_modes.models import CourseMode -from lms.djangoapps.courseware.masquerade import ( - get_course_masquerade, - get_masquerade_role, - is_masquerading_as_specific_student -) from openedx.core.djangoapps.config_model_utils.models import StackedConfigurationModel -from openedx.core.djangoapps.config_model_utils.utils import is_in_holdback -from openedx.features.content_type_gating.helpers import ( - CONTENT_GATING_PARTITION_ID, - CONTENT_TYPE_GATE_GROUP_IDS, - correct_modes_for_fbe -) -from student.models import CourseEnrollment -from student.role_helpers import has_staff_roles -from xmodule.partitions.partitions import ENROLLMENT_TRACK_PARTITION_ID +from openedx.features.content_type_gating.helpers import correct_modes_for_fbe, enrollment_date_for_fbe @python_2_unicode_compatible @@ -52,35 +34,7 @@ class CourseDurationLimitConfig(StackedConfigurationModel): ) @classmethod - def has_full_access_role_in_masquerade(cls, user, course_key, course_masquerade): - """ - When masquerading, the course duration limits will never trigger the course to expire, redirecting the user. - The roles of the masquerade user are still used to determine whether the course duration limit banner displays. - Another banner also displays if the course is expired for the masquerade user. - Both banners will appear if the masquerade user does not have any of the following roles: - Staff, Instructor, Beta Tester, Forum Community TA, Forum Group Moderator, Forum Moderator, Forum Administrator - """ - masquerade_role = get_masquerade_role(user, course_key) - verified_mode_id = settings.COURSE_ENROLLMENT_MODES.get(CourseMode.VERIFIED, {}).get('id') - # Masquerading users can select the the role of a verified users without selecting a specific user - is_verified = (course_masquerade.user_partition_id == ENROLLMENT_TRACK_PARTITION_ID - and course_masquerade.group_id == verified_mode_id) - # Masquerading users can select the role of staff without selecting a specific user - is_staff = masquerade_role == 'staff' - # Masquerading users can select other full access roles for which content type gating is disabled - is_full_access = (course_masquerade.user_partition_id == CONTENT_GATING_PARTITION_ID - and course_masquerade.group_id == CONTENT_TYPE_GATE_GROUP_IDS['full_access']) - # When masquerading as a specific user, we can check that user's staff roles as we would with a normal user - is_staff_role = False - if course_masquerade.user_name: - is_staff_role = has_staff_roles(user, course_key) - - if is_verified or is_full_access or is_staff or is_staff_role: - return True - return False - - @classmethod - def enabled_for_enrollment(cls, user=None, course_key=None): + def enabled_for_enrollment(cls, user, course): """ Return whether Course Duration Limits are enabled for this enrollment. @@ -89,54 +43,15 @@ class CourseDurationLimitConfig(StackedConfigurationModel): such as the org, site, or globally), and if the configuration is specified to be ``enabled_as_of`` before the enrollment was created. - Only one of enrollment and (user, course_key) may be specified at a time. - Arguments: - enrollment: The enrollment being queried. user: The user being queried. course_key: The CourseKey of the course being queried. + course: The CourseOverview object being queried. """ - - if user is None or course_key is None: - raise ValueError('Both user and course_key must be specified if no enrollment is provided') - - enrollment = CourseEnrollment.get_enrollment(user, course_key, ['fbeenrollmentexclusion']) - - if user is None and enrollment is not None: - user = enrollment.user - - if user and user.id: - course_masquerade = get_course_masquerade(user, course_key) - if course_masquerade: - if cls.has_full_access_role_in_masquerade(user, course_key, course_masquerade): - return False - elif has_staff_roles(user, course_key): - return False - - is_masquerading = get_course_masquerade(user, course_key) - no_masquerade = is_masquerading is None - student_masquerade = is_masquerading_as_specific_student(user, course_key) - - # check if user is in holdback - if (no_masquerade or student_masquerade) and is_in_holdback(user, enrollment): + target_datetime = enrollment_date_for_fbe(user, course=course) + if not target_datetime: return False - - not_student_masquerade = is_masquerading and not student_masquerade - - # enrollment might be None if the user isn't enrolled. In that case, - # return enablement as if the user enrolled today - # When masquerading as a user group rather than a specific learner, - # course duration limits will be on if they are on for the course. - # When masquerading as a specific learner, course duration limits - # will be on if they are currently on for the learner. - if enrollment is None or not_student_masquerade: - # we bypass enabled_for_course here and use enabled_as_of_datetime directly - # because the correct_modes_for_fbe for FBE check contained in enabled_for_course - # is redundant with checks done upstream of this code - target_datetime = timezone.now() - else: - target_datetime = enrollment.created - current_config = cls.current(course_key=course_key) + current_config = cls.current(course_key=course.id) return current_config.enabled_as_of_datetime(target_datetime=target_datetime) @classmethod diff --git a/openedx/features/course_duration_limits/tests/test_course_expiration.py b/openedx/features/course_duration_limits/tests/test_course_expiration.py index 68f87527c1..3607997600 100644 --- a/openedx/features/course_duration_limits/tests/test_course_expiration.py +++ b/openedx/features/course_duration_limits/tests/test_course_expiration.py @@ -2,8 +2,6 @@ Contains tests to verify correctness of course expiration functionality """ - -import json from datetime import timedelta import ddt @@ -14,7 +12,6 @@ from django.urls import reverse from django.utils.timezone import now from course_modes.models import CourseMode -from experiments.models import ExperimentData from lms.djangoapps.courseware.tests.factories import ( BetaTesterFactory, GlobalStaffFactory, @@ -23,6 +20,7 @@ from lms.djangoapps.courseware.tests.factories import ( OrgStaffFactory, StaffFactory ) +from lms.djangoapps.courseware.tests.helpers import MasqueradeMixin from lms.djangoapps.discussion.django_comment_client.tests.factories import RoleFactory from openedx.core.djangoapps.content.course_overviews.models import CourseOverview from openedx.core.djangoapps.course_date_signals.utils import MAX_DURATION, MIN_DURATION @@ -46,7 +44,7 @@ from xmodule.partitions.partitions import ENROLLMENT_TRACK_PARTITION_ID @ddt.ddt -class CourseExpirationTestCase(ModuleStoreTestCase): +class CourseExpirationTestCase(ModuleStoreTestCase, MasqueradeMixin): """Tests to verify the get_user_course_expiration_date function is working correctly""" def setUp(self): super(CourseExpirationTestCase, self).setUp() @@ -57,7 +55,8 @@ class CourseExpirationTestCase(ModuleStoreTestCase): self.THREE_YEARS_AGO = now() - timedelta(days=(365 * 3)) # Make this a verified course so we can test expiration date - add_course_mode(self.course, upgrade_deadline_expired=False) + add_course_mode(self.course, mode_slug=CourseMode.AUDIT) + add_course_mode(self.course) def tearDown(self): CourseEnrollment.unenroll(self.user, self.course.id) @@ -223,29 +222,6 @@ class CourseExpirationTestCase(ModuleStoreTestCase): else: self.assertNotContains(response, banner_text) - def update_masquerade(self, role='student', group_id=None, username=None, user_partition_id=None): - """ - Toggle masquerade state. - """ - masquerade_url = reverse( - 'masquerade_update', - kwargs={ - 'course_key_string': six.text_type(self.course.id), - } - ) - response = self.client.post( - masquerade_url, - json.dumps({ - 'role': role, - 'group_id': group_id, - 'user_name': username, - 'user_partition_id': user_partition_id, - }), - 'application/json' - ) - self.assertEqual(response.status_code, 200) - return response - @mock.patch("openedx.core.djangoapps.course_date_signals.utils.get_course_run_details") def test_masquerade_in_holdback(self, mock_get_course_run_details): mock_get_course_run_details.return_value = {'weeks_to_complete': 12} diff --git a/openedx/features/course_duration_limits/tests/test_models.py b/openedx/features/course_duration_limits/tests/test_models.py index 16414054a3..2da1d5128f 100644 --- a/openedx/features/course_duration_limits/tests/test_models.py +++ b/openedx/features/course_duration_limits/tests/test_models.py @@ -74,13 +74,10 @@ class TestCourseDurationLimitConfig(CacheIsolationTestCase): user = self.user course_key = self.course_overview.id - query_count = 6 + query_count = 7 with self.assertNumQueries(query_count): - enabled = CourseDurationLimitConfig.enabled_for_enrollment( - user=user, - course_key=course_key, - ) + enabled = CourseDurationLimitConfig.enabled_for_enrollment(user, self.course_overview) self.assertEqual(not enrolled_before_enabled, enabled) def test_enabled_for_enrollment_failure(self): diff --git a/openedx/features/course_experience/tests/views/test_course_home.py b/openedx/features/course_experience/tests/views/test_course_home.py index 0374233b06..3824e23a61 100644 --- a/openedx/features/course_experience/tests/views/test_course_home.py +++ b/openedx/features/course_experience/tests/views/test_course_home.py @@ -219,7 +219,7 @@ class TestCourseHomePage(CourseHomePageTestCase): # Fetch the view and verify the query counts # TODO: decrease query count as part of REVO-28 - with self.assertNumQueries(74, table_blacklist=QUERY_COUNT_TABLE_BLACKLIST): + with self.assertNumQueries(72, table_blacklist=QUERY_COUNT_TABLE_BLACKLIST): with check_mongo_calls(4): url = course_home_url(self.course) self.client.get(url) @@ -252,7 +252,8 @@ class TestCourseHomePageAccess(CourseHomePageTestCase): super(TestCourseHomePageAccess, self).setUp() # Make this a verified course so that an upgrade message might be shown - add_course_mode(self.course, upgrade_deadline_expired=False) + add_course_mode(self.course, mode_slug=CourseMode.AUDIT) + add_course_mode(self.course) # Add a welcome message create_course_update(self.course, self.staff_user, TEST_WELCOME_MESSAGE) @@ -709,6 +710,7 @@ class TestCourseHomePageAccess(CourseHomePageTestCase): # Verify that unenrolled users visiting a course with a Master's track # that is not the only track are shown an enroll call to action message add_course_mode(self.course, CourseMode.MASTERS, 'Master\'s Mode', upgrade_deadline_expired=False) + remove_course_mode(self.course, CourseMode.AUDIT) self.create_user_for_course(self.course, CourseUserType.UNENROLLED) url = course_home_url(self.course) diff --git a/openedx/features/course_experience/tests/views/test_course_outline.py b/openedx/features/course_experience/tests/views/test_course_outline.py index 0690a63cd7..360d719733 100644 --- a/openedx/features/course_experience/tests/views/test_course_outline.py +++ b/openedx/features/course_experience/tests/views/test_course_outline.py @@ -26,10 +26,11 @@ from waffle.testutils import override_switch from course_modes.models import CourseMode from course_modes.tests.factories import CourseModeFactory -from lms.djangoapps.courseware.tests.factories import StaffFactory -from lms.urls import RESET_COURSE_DEADLINES_NAME from gating import api as lms_gating_api +from lms.djangoapps.courseware.tests.factories import StaffFactory +from lms.djangoapps.courseware.tests.helpers import MasqueradeMixin from lms.djangoapps.course_api.blocks.transformers.milestones import MilestonesAndSpecialExamsTransformer +from lms.urls import RESET_COURSE_DEADLINES_NAME from openedx.core.djangoapps.schedules.models import Schedule from openedx.core.djangoapps.schedules.tests.factories import ScheduleFactory from openedx.core.djangoapps.course_date_signals.models import SelfPacedRelativeDatesConfig @@ -55,7 +56,7 @@ GATING_NAMESPACE_QUALIFIER = '.gating' @ddt.ddt -class TestCourseOutlinePage(SharedModuleStoreTestCase): +class TestCourseOutlinePage(SharedModuleStoreTestCase, MasqueradeMixin): """ Test the course outline view. """ @@ -241,8 +242,6 @@ class TestCourseOutlinePage(SharedModuleStoreTestCase): enrollment = CourseEnrollment.objects.get(course_id=course.id) enrollment.schedule.start_date = timezone.now() - datetime.timedelta(days=30) enrollment.schedule.save() - post_dict = {'reset_deadlines_redirect_url_id_dict': json.dumps({'course_id': str(course.id)})} - course = self.courses[0] student_schedule = CourseEnrollment.objects.get(course_id=course.id, user=self.user).schedule student_schedule.start_date = timezone.now() - datetime.timedelta(days=30) @@ -255,19 +254,7 @@ class TestCourseOutlinePage(SharedModuleStoreTestCase): ) self.client.login(username=staff.username, password=TEST_PASSWORD) - masquerade_url = reverse( - 'masquerade_update', - kwargs={ - 'course_key_string': six.text_type(course.id), - } - ) - response = self.client.post( - masquerade_url, - json.dumps({"role": 'student', "group_id": None, "user_name": self.user.username}), - "application/json" - ) - - assert response.status_code == 200 + self.update_masquerade(course=course, username=self.user.username) post_dict = {'reset_deadlines_redirect_url_id_dict': json.dumps({'course_id': str(course.id)})} self.client.post(reverse(RESET_COURSE_DEADLINES_NAME), post_dict) @@ -292,19 +279,7 @@ class TestCourseOutlinePage(SharedModuleStoreTestCase): ) self.client.login(username=staff.username, password=TEST_PASSWORD) - masquerade_url = reverse( - 'masquerade_update', - kwargs={ - 'course_key_string': six.text_type(course.id), - } - ) - response = self.client.post( - masquerade_url, - json.dumps({"role": 'student', "group_id": None, "user_name": None}), - "application/json" - ) - - assert response.status_code == 200 + self.update_masquerade(course=course) post_dict = {'reset_deadlines_redirect_url_id_dict': json.dumps({'course_id': str(course.id)})} self.client.post(reverse(RESET_COURSE_DEADLINES_NAME), post_dict) @@ -772,28 +747,10 @@ class TestCourseOutlineResumeCourse(SharedModuleStoreTestCase, CompletionWaffleT self.assertEqual(DEFAULT_COMPLETION_TRACKING_START, view._completion_data_collection_start()) -class TestCourseOutlinePreview(SharedModuleStoreTestCase): +class TestCourseOutlinePreview(SharedModuleStoreTestCase, MasqueradeMixin): """ Unit tests for staff preview of the course outline. """ - def update_masquerade(self, course, role, group_id=None, user_name=None): - """ - Toggle masquerade state. - """ - masquerade_url = reverse( - 'masquerade_update', - kwargs={ - 'course_key_string': six.text_type(course.id), - } - ) - response = self.client.post( - masquerade_url, - json.dumps({'role': role, 'group_id': group_id, 'user_name': user_name}), - 'application/json' - ) - self.assertEqual(response.status_code, 200) - return response - def test_preview(self): """ Verify the behavior of preview for the course outline. @@ -830,7 +787,7 @@ class TestCourseOutlinePreview(SharedModuleStoreTestCase): self.assertContains(response, 'Future Chapter') # Verify that staff masquerading as a learner see the future chapter. - self.update_masquerade(course, role='student') + self.update_masquerade(course=course, role='student') response = self.client.get(url) self.assertEqual(response.status_code, 200) self.assertContains(response, 'Future Chapter') diff --git a/openedx/features/course_experience/tests/views/test_course_updates.py b/openedx/features/course_experience/tests/views/test_course_updates.py index 69ad5b78d1..c724c6b83a 100644 --- a/openedx/features/course_experience/tests/views/test_course_updates.py +++ b/openedx/features/course_experience/tests/views/test_course_updates.py @@ -134,7 +134,7 @@ class TestCourseUpdatesPage(SharedModuleStoreTestCase): # Fetch the view and verify that the query counts haven't changed # TODO: decrease query count as part of REVO-28 - with self.assertNumQueries(50, table_blacklist=QUERY_COUNT_TABLE_BLACKLIST): + with self.assertNumQueries(48, table_blacklist=QUERY_COUNT_TABLE_BLACKLIST): with check_mongo_calls(4): url = course_updates_url(self.course) self.client.get(url) diff --git a/openedx/features/course_experience/tests/views/test_masquerade.py b/openedx/features/course_experience/tests/views/test_masquerade.py index 57be7fe5f4..9c11710b53 100644 --- a/openedx/features/course_experience/tests/views/test_masquerade.py +++ b/openedx/features/course_experience/tests/views/test_masquerade.py @@ -2,13 +2,7 @@ Tests for masquerading functionality on course_experience """ - -import json - -import six -from django.urls import reverse - -from lms.djangoapps.commerce.models import CommerceConfiguration +from lms.djangoapps.courseware.tests.helpers import MasqueradeMixin from openedx.core.djangoapps.waffle_utils.testutils import override_waffle_flag from openedx.features.course_experience import DISPLAY_COURSE_SOCK_FLAG, SHOW_UPGRADE_MSG_ON_COURSE_HOME from student.roles import CourseStaffRole @@ -19,14 +13,14 @@ from xmodule.partitions.partitions import ENROLLMENT_TRACK_PARTITION_ID from xmodule.partitions.partitions_service import PartitionService from .helpers import add_course_mode -from .test_course_home import TEST_UPDATE_MESSAGE, course_home_url +from .test_course_home import course_home_url from .test_course_sock import TEST_VERIFICATION_SOCK_LOCATOR TEST_PASSWORD = 'test' UPGRADE_MESSAGE_CONTAINER = 'section-upgrade' -class MasqueradeTestBase(SharedModuleStoreTestCase): +class MasqueradeTestBase(SharedModuleStoreTestCase, MasqueradeMixin): """ Base test class for masquerading functionality on course_experience """ @@ -66,29 +60,6 @@ class MasqueradeTestBase(SharedModuleStoreTestCase): return group.id return None - def update_masquerade(self, role, course, username=None, group_id=None): - """ - Toggle masquerade state. - """ - masquerade_url = reverse( - 'masquerade_update', - kwargs={ - 'course_key_string': six.text_type(course.id), - } - ) - response = self.client.post( - masquerade_url, - json.dumps({ - "role": role, - "group_id": group_id, - "user_name": username, - "user_partition_id": ENROLLMENT_TRACK_PARTITION_ID - }), - "application/json" - ) - self.assertEqual(response.status_code, 200) - return response - class TestVerifiedUpgradesWithMasquerade(MasqueradeTestBase): """ @@ -99,7 +70,7 @@ class TestVerifiedUpgradesWithMasquerade(MasqueradeTestBase): @override_waffle_flag(SHOW_UPGRADE_MSG_ON_COURSE_HOME, active=True) def test_masquerade_as_student(self): # Elevate the staff user to be student - self.update_masquerade(role='student', course=self.verified_course) + self.update_masquerade(course=self.verified_course, user_partition_id=ENROLLMENT_TRACK_PARTITION_ID) response = self.client.get(course_home_url(self.verified_course)) self.assertContains(response, TEST_VERIFICATION_SOCK_LOCATOR, html=False) self.assertContains(response, UPGRADE_MESSAGE_CONTAINER, html=False) @@ -110,7 +81,8 @@ class TestVerifiedUpgradesWithMasquerade(MasqueradeTestBase): self.verified_course.id, 'Verified Certificate' ) - self.update_masquerade(role='student', course=self.verified_course, group_id=user_group_id) + self.update_masquerade(course=self.verified_course, group_id=user_group_id, + user_partition_id=ENROLLMENT_TRACK_PARTITION_ID) response = self.client.get(course_home_url(self.verified_course)) self.assertNotContains(response, TEST_VERIFICATION_SOCK_LOCATOR, html=False) self.assertNotContains(response, UPGRADE_MESSAGE_CONTAINER, html=False) @@ -121,7 +93,8 @@ class TestVerifiedUpgradesWithMasquerade(MasqueradeTestBase): self.masters_course.id, 'Masters' ) - self.update_masquerade(role='student', course=self.masters_course, group_id=user_group_id) + self.update_masquerade(course=self.masters_course, group_id=user_group_id, + user_partition_id=ENROLLMENT_TRACK_PARTITION_ID) response = self.client.get(course_home_url(self.masters_course)) self.assertNotContains(response, TEST_VERIFICATION_SOCK_LOCATOR, html=False) self.assertNotContains(response, UPGRADE_MESSAGE_CONTAINER, html=False)