Merge pull request #19560 from edx/REVMI-2
add informational banners when masquerading as user who would not have access due to start date
This commit is contained in:
@@ -11,7 +11,7 @@ from operator import itemgetter
|
||||
from contentstore.utils import reverse_course_url, reverse_usage_url
|
||||
from contentstore.course_group_config import GroupConfiguration, CONTENT_GROUP_CONFIGURATION_NAME
|
||||
from contentstore.tests.utils import CourseTestCase
|
||||
from openedx.features.content_type_gating.partitions import CONTENT_GATING_PARTITION_ID
|
||||
from openedx.features.content_type_gating.helpers import CONTENT_GATING_PARTITION_ID
|
||||
from xmodule.partitions.partitions import Group, UserPartition, ENROLLMENT_TRACK_PARTITION_ID
|
||||
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
|
||||
from xmodule.validation import StudioValidation, StudioValidationMessage
|
||||
|
||||
38
common/djangoapps/student/role_helpers.py
Normal file
38
common/djangoapps/student/role_helpers.py
Normal file
@@ -0,0 +1,38 @@
|
||||
"""
|
||||
Helpers for student roles
|
||||
"""
|
||||
from django_comment_common.models import (
|
||||
FORUM_ROLE_ADMINISTRATOR,
|
||||
FORUM_ROLE_MODERATOR,
|
||||
FORUM_ROLE_GROUP_MODERATOR,
|
||||
FORUM_ROLE_COMMUNITY_TA,
|
||||
Role
|
||||
)
|
||||
from student.roles import (
|
||||
CourseBetaTesterRole,
|
||||
CourseInstructorRole,
|
||||
CourseStaffRole,
|
||||
OrgStaffRole,
|
||||
OrgInstructorRole,
|
||||
GlobalStaff
|
||||
)
|
||||
|
||||
|
||||
def has_staff_roles(user, course_key):
|
||||
"""
|
||||
Return true if a user has any of the following roles
|
||||
Staff, Instructor, Beta Tester, Forum Community TA, Forum Group Moderator, Forum Moderator, Forum Administrator
|
||||
"""
|
||||
forum_roles = [FORUM_ROLE_COMMUNITY_TA, FORUM_ROLE_GROUP_MODERATOR,
|
||||
FORUM_ROLE_MODERATOR, FORUM_ROLE_ADMINISTRATOR]
|
||||
is_staff = CourseStaffRole(course_key).has_user(user)
|
||||
is_instructor = CourseInstructorRole(course_key).has_user(user)
|
||||
is_beta_tester = CourseBetaTesterRole(course_key).has_user(user)
|
||||
is_org_staff = OrgStaffRole(course_key.org).has_user(user)
|
||||
is_org_instructor = OrgInstructorRole(course_key.org).has_user(user)
|
||||
is_global_staff = GlobalStaff().has_user(user)
|
||||
has_forum_role = Role.user_has_role_for_course(user, course_key, forum_roles)
|
||||
if any([is_staff, is_instructor, is_beta_tester, is_org_staff,
|
||||
is_org_instructor, is_global_staff, has_forum_role]):
|
||||
return True
|
||||
return False
|
||||
@@ -7,11 +7,20 @@ from datetime import datetime, timedelta
|
||||
from logging import getLogger
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils.translation import ugettext as _
|
||||
from pytz import UTC
|
||||
|
||||
from courseware.access_response import AccessResponse, StartDateError
|
||||
from courseware.masquerade import get_course_masquerade, is_masquerading_as_student
|
||||
from courseware.masquerade import (
|
||||
get_course_masquerade,
|
||||
is_masquerading_as_specific_student,
|
||||
is_masquerading_as_student
|
||||
)
|
||||
from crum import get_current_request
|
||||
from openedx.core.djangoapps.util.user_messages import PageLevelMessages
|
||||
from openedx.core.djangolib.markup import HTML
|
||||
from openedx.features.course_experience import COURSE_PRE_START_ACCESS_FLAG
|
||||
from student.role_helpers import has_staff_roles
|
||||
from student.roles import CourseBetaTesterRole
|
||||
from xmodule.util.xmodule_django import get_current_request_hostname
|
||||
|
||||
@@ -61,17 +70,30 @@ def check_start_date(user, days_early_for_beta, start, course_key):
|
||||
AccessResponse: Either ACCESS_GRANTED or StartDateError.
|
||||
"""
|
||||
start_dates_disabled = settings.FEATURES['DISABLE_START_DATES']
|
||||
if start_dates_disabled and not is_masquerading_as_student(user, course_key):
|
||||
masquerading_as_student = is_masquerading_as_student(user, course_key)
|
||||
masquerading_as_specific_student = is_masquerading_as_specific_student(user, course_key)
|
||||
|
||||
if start_dates_disabled and not masquerading_as_student:
|
||||
return ACCESS_GRANTED
|
||||
else:
|
||||
now = datetime.now(UTC)
|
||||
if start is None or in_preview_mode() or get_course_masquerade(user, course_key):
|
||||
if start is None or in_preview_mode():
|
||||
return ACCESS_GRANTED
|
||||
|
||||
effective_start = adjust_start_date(user, days_early_for_beta, start, course_key)
|
||||
if now > effective_start:
|
||||
return ACCESS_GRANTED
|
||||
|
||||
if get_course_masquerade(user, course_key):
|
||||
if masquerading_as_student or (masquerading_as_specific_student and not has_staff_roles(user, course_key)):
|
||||
request = get_current_request()
|
||||
PageLevelMessages.register_warning_message(
|
||||
request,
|
||||
HTML(_('This user does not have access to this content due to the content start date')),
|
||||
once_only=True
|
||||
)
|
||||
return ACCESS_GRANTED
|
||||
|
||||
return StartDateError(start)
|
||||
|
||||
|
||||
|
||||
@@ -14,6 +14,10 @@
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
li {
|
||||
margin: 5px 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -63,6 +63,10 @@ $full-width-banner-margin: 20px;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
li {
|
||||
margin: 5px 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ class UserMessageCollection():
|
||||
raise NotImplementedError('Subclasses must define a namespace for messages.')
|
||||
|
||||
@classmethod
|
||||
def get_message_html(self, body_html, title=None):
|
||||
def get_message_html(cls, body_html, title=None, dismissable=False, **kwargs): # pylint: disable=unused-argument
|
||||
"""
|
||||
Returns the entire HTML snippet for the message.
|
||||
|
||||
@@ -105,7 +105,7 @@ class UserMessageCollection():
|
||||
return body_html
|
||||
|
||||
@classmethod
|
||||
def register_user_message(self, request, message_type, body_html, **kwargs):
|
||||
def register_user_message(cls, request, message_type, body_html, once_only=False, **kwargs):
|
||||
"""
|
||||
Register a message to be shown to the user in the next page.
|
||||
|
||||
@@ -114,10 +114,12 @@ class UserMessageCollection():
|
||||
body_html (str): body of the message in html format
|
||||
title (str): optional title for the message as plain text
|
||||
dismissable (bool): shows a dismiss button (defaults to no button)
|
||||
once_only (bool): show the message only once per request
|
||||
"""
|
||||
assert isinstance(message_type, UserMessageType)
|
||||
message = Text(self.get_message_html(body_html, **kwargs))
|
||||
messages.add_message(request, message_type.value, Text(message), extra_tags=self.get_namespace())
|
||||
message = Text(cls.get_message_html(body_html, **kwargs))
|
||||
if not once_only or message not in [m.message for m in messages.get_messages(request)]:
|
||||
messages.add_message(request, message_type.value, Text(message), extra_tags=cls.get_namespace())
|
||||
|
||||
@classmethod
|
||||
def register_info_message(self, request, message, **kwargs):
|
||||
@@ -184,7 +186,7 @@ class PageLevelMessages(UserMessageCollection):
|
||||
NAMESPACE = 'page_level_messages'
|
||||
|
||||
@classmethod
|
||||
def get_message_html(self, body_html, title=None, dismissable=False):
|
||||
def get_message_html(cls, body_html, title=None, dismissable=False, **kwargs):
|
||||
"""
|
||||
Returns the entire HTML snippet for the message.
|
||||
"""
|
||||
|
||||
@@ -7,7 +7,7 @@ from django.conf import settings
|
||||
from openedx.core.djangoapps.content.block_structure.transformer import (
|
||||
BlockStructureTransformer,
|
||||
)
|
||||
from openedx.features.content_type_gating.partitions import CONTENT_GATING_PARTITION_ID
|
||||
from openedx.features.content_type_gating.helpers import CONTENT_GATING_PARTITION_ID
|
||||
from openedx.features.content_type_gating.models import ContentTypeGatingConfig
|
||||
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ students in the Unlocked Group of the ContentTypeGating partition.
|
||||
from django.conf import settings
|
||||
|
||||
from lms.djangoapps.courseware.field_overrides import FieldOverrideProvider
|
||||
from openedx.features.content_type_gating.partitions import CONTENT_GATING_PARTITION_ID
|
||||
from openedx.features.content_type_gating.helpers import CONTENT_GATING_PARTITION_ID
|
||||
from openedx.features.content_type_gating.models import ContentTypeGatingConfig
|
||||
|
||||
|
||||
|
||||
@@ -2,46 +2,15 @@
|
||||
Helper functions used by both content_type_gating and course_duration_limits.
|
||||
"""
|
||||
|
||||
from django_comment_common.models import (
|
||||
FORUM_ROLE_ADMINISTRATOR,
|
||||
FORUM_ROLE_MODERATOR,
|
||||
FORUM_ROLE_GROUP_MODERATOR,
|
||||
FORUM_ROLE_COMMUNITY_TA,
|
||||
Role
|
||||
)
|
||||
from student.roles import (
|
||||
CourseBetaTesterRole,
|
||||
CourseInstructorRole,
|
||||
CourseStaffRole,
|
||||
OrgStaffRole,
|
||||
OrgInstructorRole,
|
||||
GlobalStaff
|
||||
)
|
||||
from xmodule.partitions.partitions import Group
|
||||
|
||||
# Studio generates partition IDs starting at 100. There is already a manually generated
|
||||
# partition for Enrollment Track that uses ID 50, so we'll use 51.
|
||||
CONTENT_GATING_PARTITION_ID = 51
|
||||
|
||||
CONTENT_TYPE_GATE_GROUP_IDS = {
|
||||
'limited_access': 1,
|
||||
'full_access': 2,
|
||||
}
|
||||
LIMITED_ACCESS = Group(CONTENT_TYPE_GATE_GROUP_IDS['limited_access'], 'Limited-access Users')
|
||||
FULL_ACCESS = Group(CONTENT_TYPE_GATE_GROUP_IDS['full_access'], 'Full-access Users')
|
||||
|
||||
|
||||
def has_staff_roles(user, course_key):
|
||||
"""
|
||||
Disable feature based enrollments for the enrollment if a user has any of the following roles
|
||||
Staff, Instructor, Beta Tester, Forum Community TA, Forum Group Moderator, Forum Moderator, Forum Administrator
|
||||
"""
|
||||
forum_roles = [FORUM_ROLE_COMMUNITY_TA, FORUM_ROLE_GROUP_MODERATOR,
|
||||
FORUM_ROLE_MODERATOR, FORUM_ROLE_ADMINISTRATOR]
|
||||
is_staff = CourseStaffRole(course_key).has_user(user)
|
||||
is_instructor = CourseInstructorRole(course_key).has_user(user)
|
||||
is_beta_tester = CourseBetaTesterRole(course_key).has_user(user)
|
||||
is_org_staff = OrgStaffRole(course_key.org).has_user(user)
|
||||
is_org_instructor = OrgInstructorRole(course_key.org).has_user(user)
|
||||
is_global_staff = GlobalStaff().has_user(user)
|
||||
has_forum_role = Role.user_has_role_for_course(user, course_key, forum_roles)
|
||||
if any([is_staff, is_instructor, is_beta_tester, is_org_staff,
|
||||
is_org_instructor, is_global_staff, has_forum_role]):
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -20,12 +20,13 @@ from lms.djangoapps.courseware.masquerade import (
|
||||
)
|
||||
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, has_staff_roles, LIMITED_ACCESS
|
||||
from openedx.features.content_type_gating.helpers import FULL_ACCESS, LIMITED_ACCESS
|
||||
from openedx.features.course_duration_limits.config import (
|
||||
CONTENT_TYPE_GATING_FLAG,
|
||||
FEATURE_BASED_ENROLLMENT_GLOBAL_KILL_FLAG,
|
||||
)
|
||||
from student.models import CourseEnrollment
|
||||
from student.role_helpers import has_staff_roles
|
||||
from xmodule.partitions.partitions import ENROLLMENT_TRACK_PARTITION_ID
|
||||
|
||||
|
||||
|
||||
@@ -18,14 +18,10 @@ from lms.djangoapps.commerce.utils import EcommerceService
|
||||
from xmodule.partitions.partitions import UserPartition, UserPartitionError
|
||||
from openedx.core.lib.mobile_utils import is_request_from_mobile_app
|
||||
from openedx.features.content_type_gating.models import ContentTypeGatingConfig
|
||||
from openedx.features.content_type_gating.helpers import FULL_ACCESS, LIMITED_ACCESS
|
||||
from openedx.features.content_type_gating.helpers import CONTENT_GATING_PARTITION_ID, FULL_ACCESS, LIMITED_ACCESS
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
# Studio generates partition IDs starting at 100. There is already a manually generated
|
||||
# partition for Enrollment Track that uses ID 50, so we'll use 51.
|
||||
CONTENT_GATING_PARTITION_ID = 51
|
||||
|
||||
|
||||
def create_content_gating_partition(course):
|
||||
"""
|
||||
|
||||
@@ -35,11 +35,8 @@ 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.lib.url_utils import quote_slashes
|
||||
from openedx.features.content_type_gating.helpers import CONTENT_TYPE_GATE_GROUP_IDS
|
||||
from openedx.features.content_type_gating.partitions import (
|
||||
CONTENT_GATING_PARTITION_ID,
|
||||
ContentTypeGatingPartition
|
||||
)
|
||||
from openedx.features.content_type_gating.helpers import CONTENT_GATING_PARTITION_ID, CONTENT_TYPE_GATE_GROUP_IDS
|
||||
from openedx.features.content_type_gating.partitions import ContentTypeGatingPartition
|
||||
from openedx.features.content_type_gating.models import ContentTypeGatingConfig
|
||||
from openedx.features.course_duration_limits.config import (
|
||||
EXPERIMENT_ID,
|
||||
|
||||
@@ -3,7 +3,8 @@ from mock import Mock, patch
|
||||
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
from openedx.core.djangolib.testing.utils import CacheIsolationTestCase
|
||||
from openedx.features.content_type_gating.partitions import create_content_gating_partition, CONTENT_GATING_PARTITION_ID
|
||||
from openedx.features.content_type_gating.helpers import CONTENT_GATING_PARTITION_ID
|
||||
from openedx.features.content_type_gating.partitions import create_content_gating_partition
|
||||
from openedx.features.content_type_gating.models import ContentTypeGatingConfig
|
||||
from xmodule.partitions.partitions import UserPartitionError
|
||||
|
||||
|
||||
@@ -20,13 +20,13 @@ from lms.djangoapps.courseware.masquerade import (
|
||||
)
|
||||
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_TYPE_GATE_GROUP_IDS, has_staff_roles
|
||||
from openedx.features.content_type_gating.partitions import CONTENT_GATING_PARTITION_ID
|
||||
from openedx.features.content_type_gating.helpers import CONTENT_GATING_PARTITION_ID, CONTENT_TYPE_GATE_GROUP_IDS
|
||||
from openedx.features.course_duration_limits.config import (
|
||||
CONTENT_TYPE_GATING_FLAG,
|
||||
FEATURE_BASED_ENROLLMENT_GLOBAL_KILL_FLAG,
|
||||
)
|
||||
from student.models import CourseEnrollment
|
||||
from student.role_helpers import has_staff_roles
|
||||
from xmodule.partitions.partitions import ENROLLMENT_TRACK_PARTITION_ID
|
||||
|
||||
|
||||
|
||||
@@ -29,8 +29,7 @@ from lms.djangoapps.courseware.tests.factories import (
|
||||
)
|
||||
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
|
||||
from openedx.core.djangoapps.schedules.tests.factories import ScheduleFactory
|
||||
from openedx.features.content_type_gating.helpers import CONTENT_TYPE_GATE_GROUP_IDS
|
||||
from openedx.features.content_type_gating.partitions import CONTENT_GATING_PARTITION_ID
|
||||
from openedx.features.content_type_gating.helpers 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
|
||||
|
||||
Reference in New Issue
Block a user