Commit d58f2ac2 by Andy Armstrong Committed by GitHub

Merge pull request #15580 from edx/andya/course-home-messaging

Show page banner for anonymous and unenrolled users
parents 4370142a 3e786ec6
...@@ -17,12 +17,10 @@ from certificates.models import CertificateStatuses ...@@ -17,12 +17,10 @@ from certificates.models import CertificateStatuses
from commerce.utils import EcommerceService from commerce.utils import EcommerceService
from course_modes.models import CourseMode from course_modes.models import CourseMode
from courseware.access import has_access, has_ccx_coach_role from courseware.access import has_access, has_ccx_coach_role
from courseware.access_response import StartDateError from courseware.access_utils import check_course_open_for_learner
from courseware.access_utils import in_preview_mode, check_course_open_for_learner
from courseware.courses import ( from courseware.courses import (
can_self_enroll_in_course, can_self_enroll_in_course,
get_course, get_course,
get_course_by_id,
get_course_overview_with_access, get_course_overview_with_access,
get_course_with_access, get_course_with_access,
get_courses, get_courses,
...@@ -49,6 +47,7 @@ from django.db.models import Q ...@@ -49,6 +47,7 @@ from django.db.models import Q
from django.http import Http404, HttpResponse, HttpResponseBadRequest, HttpResponseForbidden, QueryDict from django.http import Http404, HttpResponse, HttpResponseBadRequest, HttpResponseForbidden, QueryDict
from django.shortcuts import redirect from django.shortcuts import redirect
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.utils.http import urlquote_plus
from django.utils.text import slugify from django.utils.text import slugify
from django.utils.timezone import UTC from django.utils.timezone import UTC
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
...@@ -83,17 +82,17 @@ from openedx.core.djangoapps.plugin_api.views import EdxFragmentView ...@@ -83,17 +82,17 @@ from openedx.core.djangoapps.plugin_api.views import EdxFragmentView
from openedx.core.djangoapps.programs.utils import ProgramMarketingDataExtender from openedx.core.djangoapps.programs.utils import ProgramMarketingDataExtender
from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
from openedx.core.djangoapps.util.user_messages import register_warning_message
from openedx.core.djangolib.markup import HTML, Text
from openedx.features.course_experience import UNIFIED_COURSE_TAB_FLAG, course_home_url_name from openedx.features.course_experience import UNIFIED_COURSE_TAB_FLAG, course_home_url_name
from openedx.features.course_experience.course_tools import CourseToolsPluginManager from openedx.features.course_experience.course_tools import CourseToolsPluginManager
from openedx.features.course_experience.views.course_dates import CourseDatesFragmentView from openedx.features.course_experience.views.course_dates import CourseDatesFragmentView
from openedx.features.enterprise_support.api import data_sharing_consent_required from openedx.features.enterprise_support.api import data_sharing_consent_required
from pytz import utc
from rest_framework import status from rest_framework import status
from shoppingcart.utils import is_shopping_cart_enabled from shoppingcart.utils import is_shopping_cart_enabled
from student.models import CourseEnrollment, UserTestGroup from student.models import CourseEnrollment, UserTestGroup
from survey.utils import must_answer_survey from survey.utils import must_answer_survey
from util.cache import cache, cache_if_anonymous from util.cache import cache, cache_if_anonymous
from util.date_utils import strftime_localized
from util.db import outer_atomic from util.db import outer_atomic
from util.milestones_helpers import get_prerequisite_courses_display from util.milestones_helpers import get_prerequisite_courses_display
from util.views import _record_feedback_in_zendesk, ensure_valid_course_key, ensure_valid_usage_key from util.views import _record_feedback_in_zendesk, ensure_valid_course_key, ensure_valid_usage_key
...@@ -269,7 +268,7 @@ def course_info(request, course_id): ...@@ -269,7 +268,7 @@ def course_info(request, course_id):
masquerade, user = setup_masquerade(request, course_key, staff_access, reset_masquerade_data=True) masquerade, user = setup_masquerade(request, course_key, staff_access, reset_masquerade_data=True)
# LEARNER-612: CCX redirect handled by new Course Home (DONE) # LEARNER-612: CCX redirect handled by new Course Home (DONE)
# TODO: LEARNER-1697: Transition banner messages to new Course Home. # LEARNER-1697: Transition banner messages to new Course Home (DONE)
# if user is not enrolled in a course then app will show enroll/get register link inside course info page. # if user is not enrolled in a course then app will show enroll/get register link inside course info page.
user_is_enrolled = CourseEnrollment.is_enrolled(user, course.id) user_is_enrolled = CourseEnrollment.is_enrolled(user, course.id)
show_enroll_banner = request.user.is_authenticated() and not user_is_enrolled show_enroll_banner = request.user.is_authenticated() and not user_is_enrolled
...@@ -296,13 +295,6 @@ def course_info(request, course_id): ...@@ -296,13 +295,6 @@ def course_info(request, course_id):
if course.bypass_home and is_from_dashboard: if course.bypass_home and is_from_dashboard:
return redirect(reverse('courseware', args=[course_id])) return redirect(reverse('courseware', args=[course_id]))
# TODO: LEARNER-1697: Transition handling of enroll links in new Course Home.
# link to where the student should go to enroll in the course:
# about page if there is not marketing site, SITE_NAME if there is
url_to_enroll = reverse(course_about, args=[course_id])
if settings.FEATURES.get('ENABLE_MKTG_SITE'):
url_to_enroll = marketing_link('COURSES')
# Construct the dates fragment # Construct the dates fragment
dates_fragment = None dates_fragment = None
...@@ -334,7 +326,7 @@ def course_info(request, course_id): ...@@ -334,7 +326,7 @@ def course_info(request, course_id):
'show_enroll_banner': show_enroll_banner, 'show_enroll_banner': show_enroll_banner,
'user_is_enrolled': user_is_enrolled, 'user_is_enrolled': user_is_enrolled,
'dates_fragment': dates_fragment, 'dates_fragment': dates_fragment,
'url_to_enroll': url_to_enroll, 'url_to_enroll': CourseTabView.url_to_enroll(course_key),
'course_tools': course_tools, 'course_tools': course_tools,
# TODO: (Experimental Code). See https://openedx.atlassian.net/wiki/display/RET/2.+In-course+Verification+Prompts # TODO: (Experimental Code). See https://openedx.atlassian.net/wiki/display/RET/2.+In-course+Verification+Prompts
...@@ -391,6 +383,10 @@ class StaticCourseTabView(EdxFragmentView): ...@@ -391,6 +383,10 @@ class StaticCourseTabView(EdxFragmentView):
tab = CourseTabList.get_tab_by_slug(course.tabs, tab_slug) tab = CourseTabList.get_tab_by_slug(course.tabs, tab_slug)
if tab is None: if tab is None:
raise Http404 raise Http404
# Show warnings if the user has limited access
CourseTabView.register_user_access_warning_messages(request, course_key)
return super(StaticCourseTabView, self).get(request, course=course, tab=tab, **kwargs) return super(StaticCourseTabView, self).get(request, course=course, tab=tab, **kwargs)
def render_to_fragment(self, request, course=None, tab=None, **kwargs): def render_to_fragment(self, request, course=None, tab=None, **kwargs):
...@@ -431,6 +427,9 @@ class CourseTabView(EdxFragmentView): ...@@ -431,6 +427,9 @@ class CourseTabView(EdxFragmentView):
# Verify that the user has access to the course # Verify that the user has access to the course
check_access_to_course(request, course) check_access_to_course(request, course)
# Show warnings if the user has limited access
self.register_user_access_warning_messages(request, course_key)
# Render the page # Render the page
tab = CourseTabList.get_tab_by_type(course.tabs, tab_type) tab = CourseTabList.get_tab_by_type(course.tabs, tab_type)
page_context = self.create_page_context(request, course=course, tab=tab, **kwargs) page_context = self.create_page_context(request, course=course, tab=tab, **kwargs)
...@@ -440,6 +439,48 @@ class CourseTabView(EdxFragmentView): ...@@ -440,6 +439,48 @@ class CourseTabView(EdxFragmentView):
return CourseTabView.handle_exceptions(request, course, exception) return CourseTabView.handle_exceptions(request, course, exception)
@staticmethod @staticmethod
def url_to_enroll(course_key):
"""
Returns the URL to use to enroll in the specified course.
"""
url_to_enroll = reverse('about_course', args=[unicode(course_key)])
if settings.FEATURES.get('ENABLE_MKTG_SITE'):
url_to_enroll = marketing_link('COURSES')
return url_to_enroll
@staticmethod
def register_user_access_warning_messages(request, course_key):
"""
Register messages to be shown to the user if they have limited access.
"""
is_enrolled = CourseEnrollment.is_enrolled(request.user, course_key)
is_staff = has_access(request.user, 'staff', course_key)
if request.user.is_anonymous():
register_warning_message(
request,
Text(_("To see course content, {sign_in_link} or {register_link}.")).format(
sign_in_link=HTML('<a href="/login?next={current_url}">{sign_in_label}</a>').format(
sign_in_label=_("sign in"),
current_url=urlquote_plus(request.path),
),
register_link=HTML('<a href="/register?next={current_url}">{register_label}</a>').format(
register_label=_("register"),
current_url=urlquote_plus(request.path),
),
)
)
elif not is_enrolled and not is_staff:
register_warning_message(
request,
Text(_('You must be enrolled in the course to see course content. {enroll_link}.')).format(
enroll_link=HTML('<a href="{url_to_enroll}">{enroll_link_label}</a>').format(
url_to_enroll=CourseTabView.url_to_enroll(course_key),
enroll_link_label=_("Enroll now"),
)
)
)
@staticmethod
def handle_exceptions(request, course, exception): def handle_exceptions(request, course, exception):
""" """
Handle exceptions raised when rendering a view. Handle exceptions raised when rendering a view.
......
...@@ -51,6 +51,13 @@ $full-width-banner-margin: 20px; ...@@ -51,6 +51,13 @@ $full-width-banner-margin: 20px;
.user-messages { .user-messages {
padding-top: $baseline; padding-top: $baseline;
// Hack: force override the global important rule
// that courseware links don't have an underline.
a:hover {
color: $link-color;
text-decoration: underline !important;
}
} }
.alert { .alert {
......
...@@ -10,10 +10,14 @@ from openedx.core.djangolib.markup import HTML ...@@ -10,10 +10,14 @@ from openedx.core.djangolib.markup import HTML
from openedx.core.djangoapps.util.user_messages import user_messages from openedx.core.djangoapps.util.user_messages import user_messages
%> %>
% if user_messages: <%
banner_messages = list(user_messages(request))
%>
% if banner_messages:
<div class="page-banner"> <div class="page-banner">
<div class="user-messages"> <div class="user-messages">
% for message in user_messages(request): % for message in banner_messages:
<div class="alert ${message.css_class}" role="alert"> <div class="alert ${message.css_class}" role="alert">
<span class="icon icon-alert fa ${message.icon_class}" aria-hidden="true"></span> <span class="icon icon-alert fa ${message.icon_class}" aria-hidden="true"></span>
${HTML(message.message_html)} ${HTML(message.message_html)}
......
...@@ -186,12 +186,13 @@ class TestCourseHomePageAccess(CourseHomePageTestCase): ...@@ -186,12 +186,13 @@ class TestCourseHomePageAccess(CourseHomePageTestCase):
@override_waffle_flag(UNIFIED_COURSE_TAB_FLAG, active=True) @override_waffle_flag(UNIFIED_COURSE_TAB_FLAG, active=True)
@override_waffle_flag(SHOW_REVIEWS_TOOL_FLAG, active=True) @override_waffle_flag(SHOW_REVIEWS_TOOL_FLAG, active=True)
@ddt.data( @ddt.data(
CourseUserType.ANONYMOUS, [CourseUserType.ANONYMOUS, 'To see course content'],
CourseUserType.ENROLLED, [CourseUserType.ENROLLED, None],
CourseUserType.UNENROLLED, [CourseUserType.UNENROLLED, 'You must be enrolled in the course to see course content.'],
CourseUserType.UNENROLLED_STAFF, [CourseUserType.UNENROLLED_STAFF, None],
) )
def test_home_page(self, user_type): @ddt.unpack
def test_home_page(self, user_type, expected_message):
self.user = self.create_user_for_course(self.course, user_type) self.user = self.create_user_for_course(self.course, user_type)
# Render the course home page # Render the course home page
...@@ -212,15 +213,21 @@ class TestCourseHomePageAccess(CourseHomePageTestCase): ...@@ -212,15 +213,21 @@ class TestCourseHomePageAccess(CourseHomePageTestCase):
self.assertContains(response, 'Learn About Verified Certificate', count=expected_count) self.assertContains(response, 'Learn About Verified Certificate', count=expected_count)
self.assertContains(response, TEST_WELCOME_MESSAGE, count=expected_count) self.assertContains(response, TEST_WELCOME_MESSAGE, count=expected_count)
# Verify that the expected message is shown to the user
self.assertContains(response, '<div class="user-messages">', count=1 if expected_message else 0)
if expected_message:
self.assertContains(response, expected_message)
@override_waffle_flag(UNIFIED_COURSE_TAB_FLAG, active=False) @override_waffle_flag(UNIFIED_COURSE_TAB_FLAG, active=False)
@override_waffle_flag(SHOW_REVIEWS_TOOL_FLAG, active=True) @override_waffle_flag(SHOW_REVIEWS_TOOL_FLAG, active=True)
@ddt.data( @ddt.data(
CourseUserType.ANONYMOUS, [CourseUserType.ANONYMOUS, 'To see course content'],
CourseUserType.ENROLLED, [CourseUserType.ENROLLED, None],
CourseUserType.UNENROLLED, [CourseUserType.UNENROLLED, 'You must be enrolled in the course to see course content.'],
CourseUserType.UNENROLLED_STAFF, [CourseUserType.UNENROLLED_STAFF, None],
) )
def test_home_page_not_unified(self, user_type): @ddt.unpack
def test_home_page_not_unified(self, user_type, expected_message):
""" """
Verifies the course home tab when not unified. Verifies the course home tab when not unified.
""" """
...@@ -246,6 +253,11 @@ class TestCourseHomePageAccess(CourseHomePageTestCase): ...@@ -246,6 +253,11 @@ class TestCourseHomePageAccess(CourseHomePageTestCase):
self.assertContains(response, 'Start Course', count=expected_count) self.assertContains(response, 'Start Course', count=expected_count)
self.assertContains(response, 'Learn About Verified Certificate', count=expected_count) self.assertContains(response, 'Learn About Verified Certificate', count=expected_count)
# Verify that the expected message is shown to the user
self.assertContains(response, '<div class="user-messages">', count=1 if expected_message else 0)
if expected_message:
self.assertContains(response, expected_message)
def test_sign_in_button(self): def test_sign_in_button(self):
""" """
Verify that the sign in button will return to this page. Verify that the sign in button will return to this page.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment