REVE-103: Fix masquerade for feature based enrollments
This commit is contained in:
@@ -22,6 +22,7 @@ show_preview_menu = course and staff_access and supports_preview_menu
|
||||
course_partitions = get_all_partitions_for_course(course)
|
||||
masquerade_user_name = masquerade.user_name if masquerade else None
|
||||
masquerade_group_id = masquerade.group_id if masquerade else None
|
||||
masquerade_user_partition_id = masquerade.user_partition_id if masquerade else None
|
||||
staff_selected = selected(not masquerade or masquerade.role != "student")
|
||||
specific_student_selected = selected(not staff_selected and masquerade.user_name)
|
||||
student_selected = selected(not staff_selected and not specific_student_selected and not masquerade_group_id)
|
||||
@@ -39,7 +40,7 @@ show_preview_menu = course and staff_access and supports_preview_menu
|
||||
% if course_partitions:
|
||||
% for course_partition in course_partitions:
|
||||
% for group in sorted(course_partition.groups, key=lambda group: group.name):
|
||||
<option value="group.id" data-group-id="${group.id}" data-partition-id="${course_partition.id}" ${selected(masquerade_group_id == group.id)}>
|
||||
<option value="group.id" data-group-id="${group.id}" data-partition-id="${course_partition.id}" ${selected(masquerade_user_partition_id == course_partition.id and masquerade_group_id == group.id)}>
|
||||
${_("Learner in {content_group}").format(content_group=group.name)}
|
||||
</option>
|
||||
% endfor
|
||||
|
||||
@@ -10,6 +10,7 @@ from django.db import models
|
||||
from django.utils.encoding import python_2_unicode_compatible
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils import timezone
|
||||
from lms.djangoapps.courseware.masquerade import get_course_masquerade, is_masquerading_as_specific_student
|
||||
|
||||
from experiments.models import ExperimentData
|
||||
from student.models import CourseEnrollment
|
||||
@@ -92,15 +93,18 @@ class ContentTypeGatingConfig(StackedConfigurationModel):
|
||||
# TODO: clean up as part of REV-100
|
||||
experiment_data_holdback_key = EXPERIMENT_DATA_HOLDBACK_KEY.format(user)
|
||||
is_in_holdback = False
|
||||
try:
|
||||
holdback_value = ExperimentData.objects.get(
|
||||
user=user,
|
||||
experiment_id=EXPERIMENT_ID,
|
||||
key=experiment_data_holdback_key,
|
||||
).value
|
||||
is_in_holdback = holdback_value == 'True'
|
||||
except ExperimentData.DoesNotExist:
|
||||
pass
|
||||
no_masquerade = get_course_masquerade(user, course_key) is None
|
||||
student_masquerade = is_masquerading_as_specific_student(user, course_key)
|
||||
if user and (no_masquerade or student_masquerade):
|
||||
try:
|
||||
holdback_value = ExperimentData.objects.get(
|
||||
user=user,
|
||||
experiment_id=EXPERIMENT_ID,
|
||||
key=experiment_data_holdback_key,
|
||||
).value
|
||||
is_in_holdback = holdback_value == 'True'
|
||||
except ExperimentData.DoesNotExist:
|
||||
pass
|
||||
if is_in_holdback:
|
||||
return False
|
||||
current_config = cls.current(course_key=enrollment.course_id)
|
||||
|
||||
@@ -11,6 +11,7 @@ from course_modes.models import CourseMode
|
||||
|
||||
import crum
|
||||
from django.apps import apps
|
||||
from django.conf import settings
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
@@ -21,7 +22,7 @@ from lms.djangoapps.courseware.masquerade import (
|
||||
is_masquerading_as_specific_student,
|
||||
get_masquerading_user_group,
|
||||
)
|
||||
from xmodule.partitions.partitions import Group, UserPartition, UserPartitionError
|
||||
from xmodule.partitions.partitions import Group, UserPartition, UserPartitionError, ENROLLMENT_TRACK_PARTITION_ID
|
||||
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
|
||||
@@ -140,8 +141,18 @@ class ContentTypeGatingPartitionScheme(object):
|
||||
# same logic as normal to return that student's group. If the current
|
||||
# user is masquerading as a generic student in a specific group, then
|
||||
# return that group.
|
||||
if get_course_masquerade(user, course_key) and not is_masquerading_as_specific_student(user, course_key):
|
||||
return get_masquerading_user_group(course_key, user, user_partition)
|
||||
course_masquerade = get_course_masquerade(user, course_key)
|
||||
if course_masquerade and not is_masquerading_as_specific_student(user, course_key):
|
||||
masquerade_group = get_masquerading_user_group(course_key, user, user_partition)
|
||||
if masquerade_group is not None:
|
||||
return masquerade_group
|
||||
else:
|
||||
audit_mode_id = settings.COURSE_ENROLLMENT_MODES.get(CourseMode.AUDIT, {}).get('id')
|
||||
if course_masquerade.user_partition_id == ENROLLMENT_TRACK_PARTITION_ID:
|
||||
if course_masquerade.group_id != audit_mode_id:
|
||||
return cls.FULL_ACCESS
|
||||
else:
|
||||
return cls.LIMITED_ACCESS
|
||||
|
||||
# For now, treat everyone as a Full-access user, until we have the rest of the
|
||||
# feature gating logic in place.
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
"""
|
||||
Test audit user's access to various content based on content-gating features.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
import json
|
||||
from datetime import datetime, timedelta
|
||||
import ddt
|
||||
from django.conf import settings
|
||||
from django.test.client import RequestFactory
|
||||
from django.test.utils import override_settings
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
from mock import patch
|
||||
|
||||
from course_modes.tests.factories import CourseModeFactory
|
||||
from experiments.models import ExperimentKeyValue
|
||||
from xmodule.partitions.partitions import ENROLLMENT_TRACK_PARTITION_ID
|
||||
|
||||
from lms.djangoapps.courseware.module_render import load_single_xblock
|
||||
from lms.djangoapps.courseware.tests.factories import (
|
||||
InstructorFactory,
|
||||
@@ -23,16 +26,14 @@ from lms.djangoapps.courseware.tests.factories import (
|
||||
)
|
||||
from openedx.core.djangoapps.user_api.tests.factories import UserCourseTagFactory
|
||||
from openedx.core.djangoapps.util.testing import TestConditionalContent
|
||||
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.partitions import CONTENT_GATING_PARTITION_ID, CONTENT_TYPE_GATE_GROUP_IDS
|
||||
from openedx.features.content_type_gating.models import ContentTypeGatingConfig
|
||||
from openedx.features.course_duration_limits.config import (
|
||||
EXPERIMENT_DATA_HOLDBACK_KEY,
|
||||
EXPERIMENT_ID,
|
||||
)
|
||||
from student.models import CourseEnrollment
|
||||
from student.roles import CourseBetaTesterRole, CourseInstructorRole, CourseStaffRole
|
||||
from student.roles import CourseInstructorRole
|
||||
from student.tests.factories import (
|
||||
CourseEnrollmentFactory,
|
||||
UserFactory,
|
||||
@@ -201,7 +202,7 @@ class TestProblemTypeAccess(SharedModuleStoreTestCase):
|
||||
# enroll all users into the all track types course
|
||||
self.users = {}
|
||||
for mode_type in self.MODE_TYPES:
|
||||
self.users[mode_type] = UserFactory.create()
|
||||
self.users[mode_type] = UserFactory.create(username=mode_type)
|
||||
CourseEnrollmentFactory.create(
|
||||
user=self.users[mode_type],
|
||||
course_id=self.courses['all_track_types']['course'].id,
|
||||
@@ -245,7 +246,8 @@ class TestProblemTypeAccess(SharedModuleStoreTestCase):
|
||||
....
|
||||
}
|
||||
"""
|
||||
course = CourseFactory.create(run=run, display_name=display_name)
|
||||
start_date = timezone.now() - timedelta(weeks=1)
|
||||
course = CourseFactory.create(run=run, display_name=display_name, start=start_date)
|
||||
|
||||
for mode in modes:
|
||||
CourseModeFactory.create(course_id=course.id, mode_slug=mode)
|
||||
@@ -465,6 +467,64 @@ class TestProblemTypeAccess(SharedModuleStoreTestCase):
|
||||
request_factory=self.factory,
|
||||
)
|
||||
|
||||
@ddt.data(
|
||||
({'user_partition_id': CONTENT_GATING_PARTITION_ID,
|
||||
'group_id': CONTENT_TYPE_GATE_GROUP_IDS['limited_access']}, True),
|
||||
({'user_partition_id': CONTENT_GATING_PARTITION_ID,
|
||||
'group_id': CONTENT_TYPE_GATE_GROUP_IDS['full_access']}, False),
|
||||
({'user_partition_id': ENROLLMENT_TRACK_PARTITION_ID,
|
||||
'group_id': settings.COURSE_ENROLLMENT_MODES['audit']['id']}, True),
|
||||
({'user_partition_id': ENROLLMENT_TRACK_PARTITION_ID,
|
||||
'group_id': settings.COURSE_ENROLLMENT_MODES['verified']['id']}, False),
|
||||
({'role': 'staff'}, False),
|
||||
({'role': 'student'}, True),
|
||||
({'username': 'audit'}, True),
|
||||
({'username': 'verified'}, False),
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_masquerade(self, masquerade_config, is_gated):
|
||||
instructor = UserFactory.create()
|
||||
CourseEnrollmentFactory.create(
|
||||
user=instructor,
|
||||
course_id=self.course.id,
|
||||
mode='audit'
|
||||
)
|
||||
CourseInstructorRole(self.course.id).add_users(instructor)
|
||||
self.client.login(username=instructor.username, password=TEST_PASSWORD)
|
||||
|
||||
self.update_masquerade(**masquerade_config)
|
||||
|
||||
block = self.blocks_dict['problem']
|
||||
block_view_url = reverse('render_xblock', kwargs={'usage_key_string': unicode(block.scope_ids.usage_id)})
|
||||
response = self.client.get(block_view_url)
|
||||
if is_gated:
|
||||
self.assertEquals(response.status_code, 404)
|
||||
else:
|
||||
self.assertEquals(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': unicode(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
|
||||
|
||||
|
||||
@override_settings(FIELD_OVERRIDE_PROVIDERS=(
|
||||
'openedx.features.content_type_gating.field_override.ContentTypeGatingFieldOverride',
|
||||
|
||||
@@ -70,9 +70,12 @@ class TestContentTypeGatingConfig(CacheIsolationTestCase):
|
||||
user = self.user
|
||||
course_key = self.course_overview.id
|
||||
|
||||
query_count = 5
|
||||
if not pass_enrollment and already_enrolled:
|
||||
if already_enrolled and pass_enrollment:
|
||||
query_count = 4
|
||||
elif not pass_enrollment and already_enrolled:
|
||||
query_count = 6
|
||||
else:
|
||||
query_count = 5
|
||||
|
||||
with self.assertNumQueries(query_count):
|
||||
enabled = ContentTypeGatingConfig.enabled_for_enrollment(
|
||||
|
||||
@@ -9,11 +9,14 @@ from django.apps import apps
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from course_modes.models import CourseMode
|
||||
from util.date_utils import DEFAULT_SHORT_DATE_FORMAT, strftime_localized
|
||||
|
||||
from course_modes.models import CourseMode
|
||||
|
||||
from lms.djangoapps.courseware.access_response import AccessError
|
||||
from lms.djangoapps.courseware.access_utils import ACCESS_GRANTED
|
||||
from lms.djangoapps.courseware.date_summary import verified_upgrade_deadline_link
|
||||
from lms.djangoapps.courseware.masquerade import get_course_masquerade, is_masquerading_as_student
|
||||
from openedx.core.djangoapps.catalog.utils import get_course_run_details
|
||||
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
|
||||
from openedx.core.djangoapps.util.user_messages import PageLevelMessages
|
||||
@@ -93,6 +96,10 @@ def check_course_expired(user, course):
|
||||
"""
|
||||
Check if the course expired for the user.
|
||||
"""
|
||||
# masquerading course staff should always have access
|
||||
if get_course_masquerade(user, course.id):
|
||||
return ACCESS_GRANTED
|
||||
|
||||
if not CourseDurationLimitConfig.enabled_for_enrollment(user=user, course_key=course.id):
|
||||
return ACCESS_GRANTED
|
||||
|
||||
@@ -107,15 +114,29 @@ def register_course_expired_message(request, course):
|
||||
"""
|
||||
Add a banner notifying the user of the user course expiration date if it exists.
|
||||
"""
|
||||
if CourseDurationLimitConfig.enabled_for_enrollment(user=request.user, course_key=course.id):
|
||||
expiration_date = get_user_course_expiration_date(request.user, course)
|
||||
if expiration_date:
|
||||
upgrade_message = _('Your access to this course expires on {expiration_date}. \
|
||||
<a href="{upgrade_link}">Upgrade now</a> for unlimited access.')
|
||||
PageLevelMessages.register_info_message(
|
||||
request,
|
||||
HTML(upgrade_message).format(
|
||||
expiration_date=expiration_date.strftime('%b %-d'),
|
||||
upgrade_link=verified_upgrade_deadline_link(user=request.user, course=course)
|
||||
)
|
||||
if not CourseDurationLimitConfig.enabled_for_enrollment(user=request.user, course_key=course.id):
|
||||
return
|
||||
|
||||
expiration_date = get_user_course_expiration_date(request.user, course)
|
||||
if not expiration_date:
|
||||
return
|
||||
|
||||
if is_masquerading_as_student(request.user, course.id) and timezone.now() > expiration_date:
|
||||
upgrade_message = _('This learner would not have access to this course. '
|
||||
'Their access expired on {expiration_date}.')
|
||||
PageLevelMessages.register_warning_message(
|
||||
request,
|
||||
HTML(upgrade_message).format(
|
||||
expiration_date=expiration_date.strftime('%b %-d')
|
||||
)
|
||||
)
|
||||
else:
|
||||
upgrade_message = _('Your access to this course expires on {expiration_date}. \
|
||||
<a href="{upgrade_link}">Upgrade now</a> for unlimited access.')
|
||||
PageLevelMessages.register_info_message(
|
||||
request,
|
||||
HTML(upgrade_message).format(
|
||||
expiration_date=expiration_date.strftime('%b %-d'),
|
||||
upgrade_link=verified_upgrade_deadline_link(user=request.user, course=course)
|
||||
)
|
||||
)
|
||||
|
||||
@@ -5,14 +5,21 @@ Course Duration Limit Configuration Models
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import models
|
||||
from django.utils.encoding import python_2_unicode_compatible
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils import timezone
|
||||
from xmodule.partitions.partitions import ENROLLMENT_TRACK_PARTITION_ID
|
||||
|
||||
from course_modes.models import CourseMode
|
||||
from lms.djangoapps.courseware.masquerade import get_masquerade_role, get_course_masquerade, \
|
||||
is_masquerading_as_specific_student
|
||||
|
||||
from experiments.models import ExperimentData
|
||||
from openedx.core.djangoapps.config_model_utils.models import StackedConfigurationModel
|
||||
from openedx.features.content_type_gating.partitions import CONTENT_GATING_PARTITION_ID, CONTENT_TYPE_GATE_GROUP_IDS
|
||||
from openedx.features.course_duration_limits.config import (
|
||||
CONTENT_TYPE_GATING_FLAG,
|
||||
EXPERIMENT_ID,
|
||||
@@ -79,13 +86,25 @@ class CourseDurationLimitConfig(StackedConfigurationModel):
|
||||
# if the user is has a role of staff, instructor or beta tester their access should not expire
|
||||
if user is None and enrollment is not None:
|
||||
user = enrollment.user
|
||||
if user:
|
||||
staff_role = CourseStaffRole(course_key).has_user(user)
|
||||
instructor_role = CourseInstructorRole(course_key).has_user(user)
|
||||
beta_tester_role = CourseBetaTesterRole(course_key).has_user(user)
|
||||
|
||||
if staff_role or instructor_role or beta_tester_role:
|
||||
return False
|
||||
if user:
|
||||
course_masquerade = get_course_masquerade(user, course_key)
|
||||
if course_masquerade:
|
||||
verified_mode_id = settings.COURSE_ENROLLMENT_MODES.get(CourseMode.VERIFIED, {}).get('id')
|
||||
is_verified = (course_masquerade.user_partition_id == ENROLLMENT_TRACK_PARTITION_ID
|
||||
and course_masquerade.group_id == verified_mode_id)
|
||||
is_full_access = (course_masquerade.user_partition_id == CONTENT_GATING_PARTITION_ID
|
||||
and course_masquerade.group_id == CONTENT_TYPE_GATE_GROUP_IDS['full_access'])
|
||||
is_staff = get_masquerade_role(user, course_key) == 'staff'
|
||||
if is_verified or is_full_access or is_staff:
|
||||
return False
|
||||
else:
|
||||
staff_role = CourseStaffRole(course_key).has_user(user)
|
||||
instructor_role = CourseInstructorRole(course_key).has_user(user)
|
||||
beta_tester_role = CourseBetaTesterRole(course_key).has_user(user)
|
||||
|
||||
if staff_role or instructor_role or beta_tester_role:
|
||||
return False
|
||||
|
||||
# enrollment might be None if the user isn't enrolled. In that case,
|
||||
# return enablement as if the user enrolled today
|
||||
@@ -95,15 +114,18 @@ class CourseDurationLimitConfig(StackedConfigurationModel):
|
||||
# TODO: clean up as part of REV-100
|
||||
experiment_data_holdback_key = EXPERIMENT_DATA_HOLDBACK_KEY.format(user)
|
||||
is_in_holdback = False
|
||||
try:
|
||||
holdback_value = ExperimentData.objects.get(
|
||||
user=user,
|
||||
experiment_id=EXPERIMENT_ID,
|
||||
key=experiment_data_holdback_key,
|
||||
).value
|
||||
is_in_holdback = holdback_value == 'True'
|
||||
except ExperimentData.DoesNotExist:
|
||||
pass
|
||||
no_masquerade = get_course_masquerade(user, course_key) is None
|
||||
student_masquerade = is_masquerading_as_specific_student(user, course_key)
|
||||
if user and (no_masquerade or student_masquerade):
|
||||
try:
|
||||
holdback_value = ExperimentData.objects.get(
|
||||
user=user,
|
||||
experiment_id=EXPERIMENT_ID,
|
||||
key=experiment_data_holdback_key,
|
||||
).value
|
||||
is_in_holdback = holdback_value == 'True'
|
||||
except ExperimentData.DoesNotExist:
|
||||
pass
|
||||
if is_in_holdback:
|
||||
return False
|
||||
current_config = cls.current(course_key=enrollment.course_id)
|
||||
|
||||
@@ -1,17 +1,27 @@
|
||||
"""
|
||||
Contains tests to verify correctness of course expiration functionality
|
||||
"""
|
||||
import json
|
||||
from datetime import timedelta
|
||||
|
||||
from django.conf import settings
|
||||
from django.urls import reverse
|
||||
from django.utils.timezone import now
|
||||
import ddt
|
||||
import mock
|
||||
|
||||
|
||||
from course_modes.models import CourseMode
|
||||
from experiments.models import ExperimentData
|
||||
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
|
||||
from openedx.features.content_type_gating.partitions import CONTENT_GATING_PARTITION_ID, CONTENT_TYPE_GATE_GROUP_IDS
|
||||
from openedx.features.course_duration_limits.access import get_user_course_expiration_date, MIN_DURATION, MAX_DURATION
|
||||
from openedx.features.course_duration_limits.config import EXPERIMENT_ID, EXPERIMENT_DATA_HOLDBACK_KEY
|
||||
from openedx.features.course_experience.tests.views.helpers import add_course_mode
|
||||
from openedx.features.course_duration_limits.models import CourseDurationLimitConfig
|
||||
from student.models import CourseEnrollment
|
||||
from student.tests.factories import UserFactory
|
||||
from student.roles import CourseInstructorRole
|
||||
from student.tests.factories import UserFactory, CourseEnrollmentFactory
|
||||
from xmodule.partitions.partitions import ENROLLMENT_TRACK_PARTITION_ID
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
|
||||
@@ -101,3 +111,158 @@ class CourseExpirationTestCase(ModuleStoreTestCase):
|
||||
result = get_user_course_expiration_date(self.user, future_course)
|
||||
content_availability_date = start_date
|
||||
self.assertEqual(result, content_availability_date + access_duration)
|
||||
|
||||
@mock.patch("openedx.features.course_duration_limits.access.get_course_run_details")
|
||||
@ddt.data(
|
||||
({'user_partition_id': CONTENT_GATING_PARTITION_ID,
|
||||
'group_id': CONTENT_TYPE_GATE_GROUP_IDS['limited_access']}, True),
|
||||
({'user_partition_id': CONTENT_GATING_PARTITION_ID,
|
||||
'group_id': CONTENT_TYPE_GATE_GROUP_IDS['full_access']}, False),
|
||||
({'user_partition_id': ENROLLMENT_TRACK_PARTITION_ID,
|
||||
'group_id': settings.COURSE_ENROLLMENT_MODES['audit']['id']}, True),
|
||||
({'user_partition_id': ENROLLMENT_TRACK_PARTITION_ID,
|
||||
'group_id': settings.COURSE_ENROLLMENT_MODES['verified']['id']}, False),
|
||||
({'role': 'staff'}, False),
|
||||
({'role': 'student'}, True),
|
||||
({'username': 'audit'}, True),
|
||||
({'username': 'verified'}, False),
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_masquerade(self, masquerade_config, show_expiration_banner, mock_get_course_run_details):
|
||||
mock_get_course_run_details.return_value = {'weeks_to_complete': 12}
|
||||
audit_student = UserFactory(username='audit')
|
||||
CourseEnrollmentFactory.create(
|
||||
user=audit_student,
|
||||
course_id=self.course.id,
|
||||
mode='audit'
|
||||
)
|
||||
verified_student = UserFactory(username='verified')
|
||||
CourseEnrollmentFactory.create(
|
||||
user=verified_student,
|
||||
course_id=self.course.id,
|
||||
mode='verified'
|
||||
)
|
||||
CourseDurationLimitConfig.objects.create(
|
||||
enabled=True,
|
||||
course=CourseOverview.get_from_id(self.course.id),
|
||||
enabled_as_of=self.course.start,
|
||||
)
|
||||
|
||||
instructor = UserFactory.create(username='instructor')
|
||||
CourseEnrollmentFactory.create(
|
||||
user=instructor,
|
||||
course_id=self.course.id,
|
||||
mode='audit'
|
||||
)
|
||||
CourseInstructorRole(self.course.id).add_users(instructor)
|
||||
self.client.login(username=instructor.username, password='test')
|
||||
|
||||
self.update_masquerade(**masquerade_config)
|
||||
|
||||
course_home_url = reverse('openedx.course_experience.course_home', args=[unicode(self.course.id)])
|
||||
response = self.client.get(course_home_url, follow=True)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertItemsEqual(response.redirect_chain, [])
|
||||
banner_text = 'Your access to this course expires on'
|
||||
if show_expiration_banner:
|
||||
self.assertIn(banner_text, response.content)
|
||||
else:
|
||||
self.assertNotIn(banner_text, response.content)
|
||||
|
||||
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': unicode(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.features.course_duration_limits.access.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}
|
||||
audit_student = UserFactory(username='audit')
|
||||
CourseEnrollmentFactory.create(
|
||||
user=audit_student,
|
||||
course_id=self.course.id,
|
||||
mode='audit'
|
||||
)
|
||||
ExperimentData.objects.create(
|
||||
user=audit_student,
|
||||
experiment_id=EXPERIMENT_ID,
|
||||
key=EXPERIMENT_DATA_HOLDBACK_KEY.format(audit_student),
|
||||
value='True'
|
||||
)
|
||||
CourseDurationLimitConfig.objects.create(
|
||||
enabled=True,
|
||||
course=CourseOverview.get_from_id(self.course.id),
|
||||
enabled_as_of=self.course.start,
|
||||
)
|
||||
|
||||
instructor = UserFactory.create(username='instructor')
|
||||
CourseEnrollmentFactory.create(
|
||||
user=instructor,
|
||||
course_id=self.course.id,
|
||||
mode='audit'
|
||||
)
|
||||
CourseInstructorRole(self.course.id).add_users(instructor)
|
||||
self.client.login(username=instructor.username, password='test')
|
||||
|
||||
self.update_masquerade(username='audit')
|
||||
|
||||
course_home_url = reverse('openedx.course_experience.course_home', args=[unicode(self.course.id)])
|
||||
response = self.client.get(course_home_url, follow=True)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertItemsEqual(response.redirect_chain, [])
|
||||
banner_text = 'Your access to this course expires on'
|
||||
self.assertNotIn(banner_text, response.content)
|
||||
|
||||
@mock.patch("openedx.features.course_duration_limits.access.get_course_run_details")
|
||||
def test_masquerade_expired(self, mock_get_course_run_details):
|
||||
mock_get_course_run_details.return_value = {'weeks_to_complete': 1}
|
||||
|
||||
audit_student = UserFactory(username='audit')
|
||||
enrollment = CourseEnrollmentFactory.create(
|
||||
user=audit_student,
|
||||
course_id=self.course.id,
|
||||
mode='audit',
|
||||
)
|
||||
enrollment.created = self.course.start
|
||||
enrollment.save()
|
||||
CourseDurationLimitConfig.objects.create(
|
||||
enabled=True,
|
||||
course=CourseOverview.get_from_id(self.course.id),
|
||||
enabled_as_of=self.course.start,
|
||||
)
|
||||
|
||||
instructor = UserFactory.create(username='instructor')
|
||||
CourseEnrollmentFactory.create(
|
||||
user=instructor,
|
||||
course_id=self.course.id,
|
||||
mode='audit'
|
||||
)
|
||||
CourseInstructorRole(self.course.id).add_users(instructor)
|
||||
self.client.login(username=instructor.username, password='test')
|
||||
|
||||
self.update_masquerade(username='audit')
|
||||
|
||||
course_home_url = reverse('openedx.course_experience.course_home', args=[unicode(self.course.id)])
|
||||
response = self.client.get(course_home_url, follow=True)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertItemsEqual(response.redirect_chain, [])
|
||||
banner_text = 'This learner would not have access to this course. Their access expired on'
|
||||
self.assertIn(banner_text, response.content)
|
||||
|
||||
Reference in New Issue
Block a user