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:
Matthew Piatetsky
2019-01-10 13:48:29 -05:00
committed by GitHub
15 changed files with 95 additions and 62 deletions

View File

@@ -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

View 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

View File

@@ -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)

View File

@@ -14,6 +14,10 @@
list-style: none;
padding: 0;
margin: 0;
li {
margin: 5px 0;
}
}
}

View File

@@ -63,6 +63,10 @@ $full-width-banner-margin: 20px;
list-style: none;
padding: 0;
margin: 0;
li {
margin: 5px 0;
}
}
}

View File

@@ -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.
"""

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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):
"""

View File

@@ -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,

View File

@@ -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

View File

@@ -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

View File

@@ -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