AA-131: Allow anonymous users through course home MFE
This commit is contained in:
@@ -224,7 +224,7 @@ class SequenceModule(SequenceFields, ProctoringFields, XModule):
|
||||
progress = reduce(Progress.add_counts, progresses, None)
|
||||
return progress
|
||||
|
||||
def handle_ajax(self, dispatch, data): # TODO: bounds checking
|
||||
def handle_ajax(self, dispatch, data, view=STUDENT_VIEW): # TODO: bounds checking
|
||||
''' get = request.POST instance '''
|
||||
if dispatch == 'goto_position':
|
||||
# set position to default value if either 'position' argument not
|
||||
@@ -263,7 +263,7 @@ class SequenceModule(SequenceFields, ProctoringFields, XModule):
|
||||
else:
|
||||
# check if prerequisite has been met
|
||||
prereq_met, prereq_meta_info = self._compute_is_prereq_met(True)
|
||||
meta = self._get_render_metadata(context, display_items, prereq_met, prereq_meta_info, banner_text, STUDENT_VIEW)
|
||||
meta = self._get_render_metadata(context, display_items, prereq_met, prereq_meta_info, banner_text, view)
|
||||
meta['display_name'] = self.display_name_with_default
|
||||
meta['format'] = getattr(self, 'format', '')
|
||||
return json.dumps(meta)
|
||||
|
||||
@@ -96,7 +96,18 @@ class OutlineTabTestViews(BaseCourseHomeTests):
|
||||
def test_get_unauthenticated_user(self):
|
||||
self.client.logout()
|
||||
response = self.client.get(self.url)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
course_blocks = response.data.get('course_blocks')
|
||||
self.assertEqual(course_blocks, None)
|
||||
|
||||
course_tools = response.data.get('course_tools')
|
||||
self.assertEqual(len(course_tools), 0)
|
||||
|
||||
dates_widget = response.data.get('dates_widget')
|
||||
self.assertTrue(dates_widget)
|
||||
date_blocks = dates_widget.get('course_date_blocks')
|
||||
self.assertEqual(len(date_blocks), 0)
|
||||
|
||||
@override_experiment_waffle_flag(COURSE_HOME_MICROFRONTEND, active=True)
|
||||
@override_waffle_flag(COURSE_HOME_MICROFRONTEND_OUTLINE_TAB, active=True)
|
||||
|
||||
@@ -134,12 +134,10 @@ class OutlineTabView(RetrieveAPIView):
|
||||
**Returns**
|
||||
|
||||
* 200 on success with above fields.
|
||||
* 403 if the user is not authenticated.
|
||||
* 404 if the course is not available or cannot be seen.
|
||||
|
||||
"""
|
||||
|
||||
permission_classes = (IsAuthenticated,)
|
||||
serializer_class = OutlineTabSerializer
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
@@ -169,32 +167,6 @@ class OutlineTabView(RetrieveAPIView):
|
||||
allow_anonymous = COURSE_ENABLE_UNENROLLED_ACCESS_FLAG.is_enabled(course_key)
|
||||
allow_public = allow_anonymous and course.course_visibility == COURSE_VISIBILITY_PUBLIC
|
||||
allow_public_outline = allow_anonymous and course.course_visibility == COURSE_VISIBILITY_PUBLIC_OUTLINE
|
||||
is_enrolled = enrollment and enrollment.is_active
|
||||
is_staff = bool(has_access(request.user, 'staff', course_key))
|
||||
show_enrolled = is_enrolled or is_staff
|
||||
|
||||
show_handouts = show_enrolled or allow_public
|
||||
handouts_html = get_course_info_section(request, request.user, course, 'handouts') if show_handouts else ''
|
||||
|
||||
offer_data = show_enrolled and generate_offer_data(request.user, course_overview)
|
||||
access_expiration = show_enrolled and get_access_expiration_data(request.user, course_overview)
|
||||
|
||||
welcome_message_html = show_enrolled and get_current_update_for_user(request, course)
|
||||
|
||||
enroll_alert = {
|
||||
'can_enroll': True,
|
||||
'extra_text': None,
|
||||
}
|
||||
if not show_enrolled:
|
||||
if CourseMode.is_masters_only(course_key):
|
||||
enroll_alert['can_enroll'] = False
|
||||
enroll_alert['extra_text'] = _('Please contact your degree administrator or '
|
||||
'edX Support if you have questions.')
|
||||
elif course.invitation_only:
|
||||
enroll_alert['can_enroll'] = False
|
||||
|
||||
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
|
||||
user_timezone_locale = user_timezone_locale_prefs(request)
|
||||
@@ -204,16 +176,62 @@ class OutlineTabView(RetrieveAPIView):
|
||||
if course_home_mfe_dates_tab_is_active(course.id):
|
||||
dates_tab_link = get_microfrontend_url(course_key=course.id, view_name='dates')
|
||||
|
||||
# Set all of the defaults
|
||||
access_expiration = None
|
||||
course_blocks = None
|
||||
if show_enrolled or allow_public or allow_public_outline:
|
||||
outline_user = request.user if show_enrolled else None
|
||||
course_blocks = get_course_outline_block_tree(request, course_key_string, outline_user)
|
||||
|
||||
course_goals = {
|
||||
'goal_options': [],
|
||||
'selected_goal': None
|
||||
}
|
||||
course_tools = CourseToolsPluginManager.get_enabled_course_tools(request, course_key)
|
||||
dates_widget = {
|
||||
'course_date_blocks': [],
|
||||
'dates_tab_link': dates_tab_link,
|
||||
'user_timezone': user_timezone,
|
||||
}
|
||||
enroll_alert = {
|
||||
'can_enroll': True,
|
||||
'extra_text': None,
|
||||
}
|
||||
handouts_html = None
|
||||
offer_data = None
|
||||
resume_course = {
|
||||
'has_visited_course': False,
|
||||
'url': None,
|
||||
}
|
||||
welcome_message_html = None
|
||||
|
||||
is_enrolled = enrollment and enrollment.is_active
|
||||
is_staff = bool(has_access(request.user, 'staff', course_key))
|
||||
show_enrolled = is_enrolled or is_staff
|
||||
if show_enrolled:
|
||||
course_blocks = get_course_outline_block_tree(request, course_key_string, request.user)
|
||||
date_blocks = get_course_date_blocks(course, request.user, request, num_assignments=1)
|
||||
dates_widget['course_date_blocks'] = [block for block in date_blocks if not isinstance(block, TodaysDate)]
|
||||
|
||||
handouts_html = get_course_info_section(request, request.user, course, 'handouts')
|
||||
welcome_message_html = get_current_update_for_user(request, course)
|
||||
|
||||
offer_data = generate_offer_data(request.user, course_overview)
|
||||
access_expiration = get_access_expiration_data(request.user, course_overview)
|
||||
|
||||
# Only show the set course goal message for enrolled, unverified
|
||||
# users in a course that allows for verified statuses.
|
||||
is_already_verified = CourseEnrollment.is_enrolled_as_verified(request.user, course_key)
|
||||
if not is_already_verified and has_course_goal_permission(request, course_key_string,
|
||||
{'is_enrolled': is_enrolled}):
|
||||
course_goals = {
|
||||
'goal_options': valid_course_goals_ordered(include_unsure=True),
|
||||
'selected_goal': None
|
||||
}
|
||||
|
||||
selected_goal = get_course_goal(request.user, course_key)
|
||||
if selected_goal:
|
||||
course_goals['selected_goal'] = {
|
||||
'key': selected_goal.goal_key,
|
||||
'text': get_course_goal_text(selected_goal.goal_key),
|
||||
}
|
||||
|
||||
try:
|
||||
resume_block = get_key_to_last_completed_block(request.user, course.id)
|
||||
resume_course['has_visited_course'] = True
|
||||
@@ -224,47 +242,31 @@ class OutlineTabView(RetrieveAPIView):
|
||||
'location': str(resume_block)
|
||||
})
|
||||
resume_course['url'] = request.build_absolute_uri(resume_path)
|
||||
elif allow_public_outline or allow_public:
|
||||
course_blocks = get_course_outline_block_tree(request, course_key_string, None)
|
||||
if allow_public:
|
||||
handouts_html = get_course_info_section(request, request.user, course, 'handouts')
|
||||
|
||||
dates_widget = {
|
||||
'course_date_blocks': [block for block in date_blocks if not isinstance(block, TodaysDate)],
|
||||
'dates_tab_link': dates_tab_link,
|
||||
'user_timezone': user_timezone,
|
||||
}
|
||||
|
||||
# Only show the set course goal message for enrolled, unverified
|
||||
# users in a course that allows for verified statuses.
|
||||
is_already_verified = CourseEnrollment.is_enrolled_as_verified(request.user, course_key)
|
||||
if (not is_already_verified and
|
||||
has_course_goal_permission(request, course_key_string, {'is_enrolled': is_enrolled})):
|
||||
course_goals = {
|
||||
'goal_options': valid_course_goals_ordered(include_unsure=True),
|
||||
'selected_goal': None
|
||||
}
|
||||
|
||||
selected_goal = get_course_goal(request.user, course_key)
|
||||
if selected_goal:
|
||||
course_goals['selected_goal'] = {
|
||||
'key': selected_goal.goal_key,
|
||||
'text': get_course_goal_text(selected_goal.goal_key),
|
||||
}
|
||||
else:
|
||||
course_goals = {
|
||||
'goal_options': [],
|
||||
'selected_goal': None
|
||||
}
|
||||
if not show_enrolled:
|
||||
if CourseMode.is_masters_only(course_key):
|
||||
enroll_alert['can_enroll'] = False
|
||||
enroll_alert['extra_text'] = _('Please contact your degree administrator or '
|
||||
'edX Support if you have questions.')
|
||||
elif course.invitation_only:
|
||||
enroll_alert['can_enroll'] = False
|
||||
|
||||
data = {
|
||||
'access_expiration': access_expiration or None,
|
||||
'access_expiration': access_expiration,
|
||||
'course_blocks': course_blocks,
|
||||
'course_goals': course_goals,
|
||||
'course_tools': course_tools,
|
||||
'dates_widget': dates_widget,
|
||||
'enroll_alert': enroll_alert,
|
||||
'handouts_html': handouts_html or None,
|
||||
'handouts_html': handouts_html,
|
||||
'has_ended': course.has_ended(),
|
||||
'offer': offer_data or None,
|
||||
'offer': offer_data,
|
||||
'resume_course': resume_course,
|
||||
'welcome_message_html': welcome_message_html or None,
|
||||
'welcome_message_html': welcome_message_html,
|
||||
}
|
||||
context = self.get_serializer_context()
|
||||
context['course_overview'] = course_overview
|
||||
|
||||
@@ -525,6 +525,8 @@ def get_course_assignments(course_key, user, include_access=False):
|
||||
Each returned object is a namedtuple with fields: title, url, date, contains_gated_content, complete, past_due,
|
||||
assignment_type
|
||||
"""
|
||||
if not user.id:
|
||||
return []
|
||||
store = modulestore()
|
||||
course_usage_key = store.make_course_usage_key(course_key)
|
||||
block_data = get_course_blocks(user, course_usage_key, allow_start_dates_in_future=True, include_completion=True)
|
||||
|
||||
@@ -55,7 +55,8 @@ from lms.djangoapps.ccx.custom_exception import CCXLocatorValidationException
|
||||
from lms.djangoapps.certificates import api as certs_api
|
||||
from lms.djangoapps.certificates.models import CertificateStatuses
|
||||
from lms.djangoapps.commerce.utils import EcommerceService
|
||||
from lms.djangoapps.course_home_api.utils import is_request_from_learning_mfe
|
||||
from lms.djangoapps.course_home_api.toggles import course_home_mfe_dates_tab_is_active
|
||||
from lms.djangoapps.course_home_api.utils import get_microfrontend_url, is_request_from_learning_mfe
|
||||
from lms.djangoapps.courseware.access import has_access, has_ccx_coach_role
|
||||
from lms.djangoapps.courseware.access_utils import check_course_open_for_learner, check_public_access
|
||||
from lms.djangoapps.courseware.courses import (
|
||||
@@ -643,7 +644,8 @@ class CourseTabView(EdxFragmentView):
|
||||
register_label=_("register"),
|
||||
current_url=urlquote_plus(request.path),
|
||||
),
|
||||
)
|
||||
),
|
||||
once_only=True
|
||||
)
|
||||
else:
|
||||
PageLevelMessages.register_warning_message(
|
||||
@@ -1010,6 +1012,9 @@ def dates(request, course_id):
|
||||
from lms.urls import COURSE_DATES_NAME, RESET_COURSE_DEADLINES_NAME
|
||||
|
||||
course_key = CourseKey.from_string(course_id)
|
||||
if course_home_mfe_dates_tab_is_active(course_key) and not request.user.is_staff:
|
||||
microfrontend_url = get_microfrontend_url(course_key=course_key, view_name=COURSE_DATES_NAME)
|
||||
raise Redirect(microfrontend_url)
|
||||
|
||||
# Enable NR tracing for this view based on course
|
||||
monitoring_utils.set_custom_attribute('course_id', text_type(course_key))
|
||||
@@ -1617,7 +1622,7 @@ def render_xblock(request, usage_key_string, check_if_enrolled=True):
|
||||
Returns an HttpResponse with HTML content for the xBlock with the given usage_key.
|
||||
The returned HTML is a chromeless rendering of the xBlock (excluding content of the containing courseware).
|
||||
"""
|
||||
from lms.urls import COURSE_DATES_NAME, RESET_COURSE_DEADLINES_NAME
|
||||
from lms.urls import RESET_COURSE_DEADLINES_NAME
|
||||
from openedx.features.course_experience.urls import COURSE_HOME_VIEW_NAME
|
||||
|
||||
usage_key = UsageKey.from_string(usage_key_string)
|
||||
@@ -1626,7 +1631,7 @@ def render_xblock(request, usage_key_string, check_if_enrolled=True):
|
||||
course_key = usage_key.course_key
|
||||
|
||||
requested_view = request.GET.get('view', 'student_view')
|
||||
if requested_view != 'student_view':
|
||||
if requested_view != 'student_view' and requested_view != 'public_view':
|
||||
return HttpResponseBadRequest(
|
||||
u"Rendering of the xblock view '{}' is not supported.".format(bleach.clean(requested_view, strip=True))
|
||||
)
|
||||
@@ -1659,7 +1664,7 @@ def render_xblock(request, usage_key_string, check_if_enrolled=True):
|
||||
missed_deadlines, missed_gated_content = dates_banner_should_display(course_key, request.user)
|
||||
|
||||
context = {
|
||||
'fragment': block.render('student_view', context=student_view_context),
|
||||
'fragment': block.render(requested_view, context=student_view_context),
|
||||
'course': course,
|
||||
'disable_accordion': True,
|
||||
'allow_iframing': True,
|
||||
|
||||
@@ -189,16 +189,14 @@ class ExperimentWaffleFlag(CourseWaffleFlag):
|
||||
if not request:
|
||||
return 0
|
||||
|
||||
if not hasattr(request, 'user') or not request.user.id:
|
||||
# We need username for stable bucketing and id for tracking, so just skip anonymous (not-logged-in) users
|
||||
return 0
|
||||
if hasattr(request, 'user'):
|
||||
user = get_specific_masquerading_user(request.user, course_key)
|
||||
|
||||
user = get_specific_masquerading_user(request.user, course_key)
|
||||
if user is None:
|
||||
user = request.user
|
||||
masquerading_as_specific_student = False
|
||||
else:
|
||||
masquerading_as_specific_student = True
|
||||
if user is None:
|
||||
user = request.user
|
||||
masquerading_as_specific_student = False
|
||||
else:
|
||||
masquerading_as_specific_student = True
|
||||
|
||||
# If a course key is passed in, include it in the experiment name
|
||||
# in order to separate caches and analytics calls per course-run.
|
||||
@@ -238,14 +236,15 @@ class ExperimentWaffleFlag(CourseWaffleFlag):
|
||||
break
|
||||
else:
|
||||
bucket = stable_bucketing_hash_group(
|
||||
bucketing_group_name, self.num_buckets, user.username
|
||||
bucketing_group_name, self.num_buckets, user
|
||||
)
|
||||
|
||||
session_key = 'tracked.{}'.format(experiment_name)
|
||||
anonymous = not hasattr(request, 'user') or not request.user.id
|
||||
if (
|
||||
track and hasattr(request, 'session') and
|
||||
session_key not in request.session and
|
||||
not masquerading_as_specific_student
|
||||
not masquerading_as_specific_student and not anonymous
|
||||
):
|
||||
segment.track(
|
||||
user_id=user.id,
|
||||
|
||||
@@ -12,7 +12,7 @@ import hashlib
|
||||
import re
|
||||
|
||||
|
||||
def stable_bucketing_hash_group(group_name, group_count, username):
|
||||
def stable_bucketing_hash_group(group_name, group_count, user):
|
||||
"""
|
||||
Return the bucket that a user should be in for a given stable bucketing assignment.
|
||||
|
||||
@@ -22,11 +22,14 @@ def stable_bucketing_hash_group(group_name, group_count, username):
|
||||
Arguments:
|
||||
group_name: The name of the grouping/experiment.
|
||||
group_count: How many groups to bucket users into.
|
||||
username: The username of the user being bucketed.
|
||||
user: The user being bucketed.
|
||||
"""
|
||||
# We need username for stable bucketing and id for tracking, so just skip anonymous (not-logged-in) users
|
||||
if not user or not user.id:
|
||||
return 0
|
||||
hasher = hashlib.md5()
|
||||
hasher.update(group_name.encode('utf-8'))
|
||||
hasher.update(username.encode('utf-8'))
|
||||
hasher.update(user.username.encode('utf-8'))
|
||||
hash_str = hasher.hexdigest()
|
||||
|
||||
return int(re.sub('[8-9a-f]', '1', re.sub('[0-7]', '0', hash_str)), 2) % group_count
|
||||
|
||||
@@ -138,7 +138,7 @@ class Rev934(DeveloperErrorViewMixin, APIView):
|
||||
upgrade_price = six.text_type(get_cosmetic_verified_display_price(course))
|
||||
could_upsell = bool(user_upsell and basket_url)
|
||||
|
||||
bucket = stable_bucketing_hash_group(MOBILE_UPSELL_EXPERIMENT, 2, user.username)
|
||||
bucket = stable_bucketing_hash_group(MOBILE_UPSELL_EXPERIMENT, 2, user)
|
||||
|
||||
if could_upsell and hasattr(request, 'session') and MOBILE_UPSELL_EXPERIMENT not in request.session:
|
||||
properties = {
|
||||
|
||||
@@ -47,6 +47,7 @@ from common.djangoapps.student.models import (
|
||||
)
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.modulestore.search import path_to_location
|
||||
from xmodule.x_module import PUBLIC_VIEW, STUDENT_VIEW
|
||||
|
||||
from .serializers import CourseInfoSerializer
|
||||
from .utils import serialize_upgrade_info
|
||||
@@ -483,7 +484,12 @@ class SequenceMetadata(DeveloperErrorViewMixin, APIView):
|
||||
str(usage_key.course_key),
|
||||
str(usage_key),
|
||||
disable_staff_debug_info=True)
|
||||
return Response(json.loads(sequence.handle_ajax('metadata', None)))
|
||||
|
||||
view = STUDENT_VIEW
|
||||
if request.user.is_anonymous:
|
||||
view = PUBLIC_VIEW
|
||||
|
||||
return Response(json.loads(sequence.handle_ajax('metadata', None, view=view)))
|
||||
|
||||
|
||||
class Resume(DeveloperErrorViewMixin, APIView):
|
||||
|
||||
@@ -12,7 +12,6 @@ from django.utils.translation import get_language_bidi
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
from web_fragments.fragment import Fragment
|
||||
|
||||
from lms.djangoapps.courseware.access import has_access
|
||||
from lms.djangoapps.courseware.courses import get_course_date_blocks, get_course_with_access
|
||||
from lms.djangoapps.courseware.tabs import DatesTab
|
||||
from lms.djangoapps.course_home_api.toggles import course_home_mfe_dates_tab_is_active
|
||||
|
||||
@@ -14,16 +14,17 @@ from django.views.decorators.csrf import ensure_csrf_cookie
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
from web_fragments.fragment import Fragment
|
||||
|
||||
from lms.djangoapps.course_home_api.toggles import course_home_mfe_outline_tab_is_active
|
||||
from lms.djangoapps.course_home_api.utils import get_microfrontend_url
|
||||
from lms.djangoapps.courseware.access import has_access
|
||||
from lms.djangoapps.courseware.courses import can_self_enroll_in_course, get_course_info_section, get_course_with_access
|
||||
from lms.djangoapps.commerce.utils import EcommerceService
|
||||
from lms.djangoapps.course_goals.api import (
|
||||
get_course_goal,
|
||||
get_course_goal_options,
|
||||
get_goal_api_url,
|
||||
has_course_goal_permission
|
||||
)
|
||||
from lms.djangoapps.courseware.exceptions import CourseAccessRedirect
|
||||
from lms.djangoapps.courseware.exceptions import CourseAccessRedirect, Redirect
|
||||
from lms.djangoapps.courseware.utils import can_show_verified_upgrade, verified_upgrade_deadline_link
|
||||
from lms.djangoapps.courseware.views.views import CourseTabView
|
||||
from lms.djangoapps.courseware.toggles import COURSEWARE_PROCTORING_IMPROVEMENTS
|
||||
@@ -70,6 +71,9 @@ class CourseHomeView(CourseTabView):
|
||||
|
||||
def render_to_fragment(self, request, course=None, tab=None, **kwargs):
|
||||
course_id = six.text_type(course.id)
|
||||
if course_home_mfe_outline_tab_is_active(course.id) and not request.user.is_staff:
|
||||
microfrontend_url = get_microfrontend_url(course_key=course_id, view_name="home")
|
||||
raise Redirect(microfrontend_url)
|
||||
home_fragment_view = CourseHomeFragmentView()
|
||||
return home_fragment_view.render_to_fragment(request, course_id=course_id, **kwargs)
|
||||
|
||||
|
||||
@@ -150,7 +150,7 @@ def _is_in_holdback_and_bucket(user):
|
||||
return False
|
||||
|
||||
# Holdback is 10%
|
||||
bucket = stable_bucketing_hash_group(DISCOUNT_APPLICABILITY_HOLDBACK, 10, user.username)
|
||||
bucket = stable_bucketing_hash_group(DISCOUNT_APPLICABILITY_HOLDBACK, 10, user)
|
||||
|
||||
request = get_current_request()
|
||||
if hasattr(request, 'session') and DISCOUNT_APPLICABILITY_HOLDBACK not in request.session:
|
||||
|
||||
Reference in New Issue
Block a user