diff --git a/openedx/features/content_type_gating/partitions.py b/openedx/features/content_type_gating/partitions.py index 2dec351def..ba0247fbb4 100644 --- a/openedx/features/content_type_gating/partitions.py +++ b/openedx/features/content_type_gating/partitions.py @@ -24,6 +24,7 @@ from lms.djangoapps.courseware.masquerade import ( from xmodule.partitions.partitions import Group, UserPartition, UserPartitionError from openedx.core.lib.mobile_utils import is_request_from_mobile_app from openedx.features.content_type_gating.models import ContentTypeGatingConfig +from student.roles import CourseBetaTesterRole LOG = logging.getLogger(__name__) @@ -157,6 +158,10 @@ class ContentTypeGatingPartitionScheme(object): if not course_mode.has_verified_mode(modes_dict): return cls.FULL_ACCESS + # If the user is a beta tester for this course they are granted FULL_ACCESS + if CourseBetaTesterRole(course_key).has_user(user): + return cls.FULL_ACCESS + course_enrollment = apps.get_model('student.CourseEnrollment') mode_slug, is_active = course_enrollment.enrollment_mode_for_user(user, course_key) diff --git a/openedx/features/content_type_gating/tests/test_access.py b/openedx/features/content_type_gating/tests/test_access.py index 51a9f41829..385bf329c6 100644 --- a/openedx/features/content_type_gating/tests/test_access.py +++ b/openedx/features/content_type_gating/tests/test_access.py @@ -16,7 +16,7 @@ from openedx.core.djangoapps.waffle_utils.testutils import override_waffle_flag from openedx.core.lib.url_utils import quote_slashes from openedx.features.content_type_gating.partitions import CONTENT_GATING_PARTITION_ID from openedx.features.content_type_gating.models import ContentTypeGatingConfig -from student.roles import CourseInstructorRole, CourseStaffRole +from student.roles import CourseBetaTesterRole, CourseInstructorRole, CourseStaffRole from student.tests.factories import ( AdminFactory, CourseAccessRoleFactory, @@ -384,13 +384,21 @@ class TestProblemTypeAccess(SharedModuleStoreTestCase): # There are two types of course team members: instructor and staff # they have different privileges, but for the purpose of this test the important thing is that they should both # have access to all graded content + course_team = [] instructor = UserFactory.create() CourseInstructorRole(self.course.id).add_users(instructor) + course_team.append(instructor) + staff = UserFactory.create() CourseStaffRole(self.course.id).add_users(staff) + course_team.append(staff) + + beta_tester = UserFactory.create() + CourseBetaTesterRole(self.course.id).add_users(beta_tester) + course_team.append(beta_tester) # assert that all course team members have access to graded content - for course_team_member in [instructor, staff]: + for course_team_member in course_team: self._assert_block_is_gated( block=self.blocks_dict['problem'], user_id=course_team_member.id, diff --git a/openedx/features/course_duration_limits/access.py b/openedx/features/course_duration_limits/access.py index 6178319e56..9c56d166a3 100644 --- a/openedx/features/course_duration_limits/access.py +++ b/openedx/features/course_duration_limits/access.py @@ -18,6 +18,7 @@ from openedx.core.djangoapps.content.course_overviews.models import CourseOvervi from openedx.core.djangoapps.util.user_messages import PageLevelMessages from openedx.core.djangolib.markup import HTML from openedx.features.course_duration_limits.models import CourseDurationLimitConfig +from student.roles import CourseBetaTesterRole MIN_DURATION = timedelta(weeks=4) MAX_DURATION = timedelta(weeks=12) @@ -64,6 +65,10 @@ def get_user_course_expiration_date(user, course): if enrollment is None or enrollment.mode != 'audit': return None + # if the user is a beta tester their access should not expire + if CourseBetaTesterRole(course.id).has_user(user): + return None + try: # Content availability date is equivalent to max(enrollment date, course start date) # for most people. Using the schedule date will provide flexibility to deal with 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 565d18118a..921a916f3e 100644 --- a/openedx/features/course_experience/tests/views/test_course_home.py +++ b/openedx/features/course_experience/tests/views/test_course_home.py @@ -32,7 +32,7 @@ from openedx.features.course_experience import ( UNIFIED_COURSE_TAB_FLAG ) from student.models import CourseEnrollment -from student.roles import CourseInstructorRole, CourseStaffRole +from student.roles import CourseBetaTesterRole, CourseInstructorRole, CourseStaffRole from student.tests.factories import UserFactory from util.date_utils import strftime_localized from xmodule.modulestore import ModuleStoreEnum @@ -339,35 +339,51 @@ class TestCourseHomePageAccess(CourseHomePageTestCase): # create a list of those users who should not lose their access, # then assert that their access persists past the 'expiration date' - users_no_expired_access = [] + users = [] verified_user = UserFactory(password=self.TEST_PASSWORD) verified_enrollment = CourseEnrollment.enroll(verified_user, course.id, mode=CourseMode.VERIFIED) ScheduleFactory(start=THREE_YEARS_AGO, enrollment=verified_enrollment) - users_no_expired_access.append((verified_user, 'Verified Learner')) + users.append({ + 'user': verified_user, + 'description': 'Verified Learner' + }) - # There are two types of course team members: instructor and staff - # they have different privileges, but for the purpose of this test the important thing is that they should - # retain their access to the course after the access would expire for a normal audit learner - instructor = UserFactory.create(password=self.TEST_PASSWORD) - enrollment = CourseEnrollment.enroll(instructor, course.id, mode=CourseMode.AUDIT) - CourseInstructorRole(course.id).add_users(instructor) - ScheduleFactory(start=THREE_YEARS_AGO, enrollment=enrollment) - users_no_expired_access.append((instructor, 'Course Instructor')) + # There are a number of roles that make up the 'course team' and none of them should lose + # access to the course + course_team = [ + { + 'description': 'Course Instructor', + 'course_role': CourseInstructorRole, + }, + { + 'description': 'Course Staff', + 'course_role': CourseStaffRole, + }, + { + 'description': 'Beta Tester', + 'course_role': CourseBetaTesterRole, + } + ] - staff = UserFactory.create(password=self.TEST_PASSWORD) - enrollment = CourseEnrollment.enroll(staff, course.id, mode=CourseMode.AUDIT) - CourseStaffRole(course.id).add_users(staff) - ScheduleFactory(start=THREE_YEARS_AGO, enrollment=enrollment) - users_no_expired_access.append((staff, 'Course Staff')) + for course_team_member in course_team: + user = UserFactory.create(password=self.TEST_PASSWORD) + enrollment = CourseEnrollment.enroll(user, course.id, mode=CourseMode.AUDIT) + course_team_member['course_role'](course.id).add_users(user) + ScheduleFactory(start=THREE_YEARS_AGO, enrollment=enrollment) + users.append({ + 'user': user, + 'description': course_team_member['description'], + }) - for user, user_description in users_no_expired_access: - self.client.login(username=user.username, password=self.TEST_PASSWORD) + # ensure that all users who should have access indefinitely do + for user in users: + self.client.login(username=user['user'].username, password=self.TEST_PASSWORD) response = self.client.get(url) self.assertEqual( response.status_code, 200, - "Should not expire access for user [{}]".format(user_description) + "Should not expire access for user [{}]".format(user['description']) ) @mock.patch.dict(settings.FEATURES, {'DISABLE_START_DATES': False})