Skip to content
Snippets Groups Projects
Unverified Commit 3fb3f913 authored by Carla Duarte's avatar Carla Duarte Committed by GitHub
Browse files

Merge pull request #25661 from edx/ciduarte/AA-131

AA-131: Allow anonymous users through course home MFE
parents 3abd0e8e 99163bdf
No related branches found
Tags release-2020-08-27-14.14
No related merge requests found
Showing
with 122 additions and 91 deletions
......@@ -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))
)
......@@ -1662,7 +1667,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:
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment