Commit 08df53e1 by Harry Rein

Adding in course messaging to the home page.

LEARNER-1894

This commit adds in course messaging for three use cases. First,
when the user is not signed in, the user is shown a message that
provides a link to sign in or register. If the user is signed in
but not enrolled, they are given a link to do so. If the user is
enrolled but the course has not yet started, they are shown a
message explaining when the course starts and shown a link (not
yet enabled) to add a reminder to their calendar.

The implementation defines a base message class and extends it
for the course home messages as well as the previously implemented
page level messages.
parent 27cd3d19
......@@ -26,10 +26,10 @@ class GlobalStatusMessage(ConfigurationModel):
msg = self.message
if course_key:
try:
course_message = self.coursemessage_set.get(course_key=course_key)
# Don't add the message if course_message is blank.
if course_message:
msg = u"{} <br /> {}".format(msg, course_message.message)
course_home_message = self.coursemessage_set.get(course_key=course_key)
# Don't add the message if course_home_message is blank.
if course_home_message:
msg = u"{} <br /> {}".format(msg, course_home_message.message)
except CourseMessage.DoesNotExist:
# We don't have a course-specific message, so pass.
pass
......
......@@ -82,7 +82,7 @@ from openedx.core.djangoapps.plugin_api.views import EdxFragmentView
from openedx.core.djangoapps.programs.utils import ProgramMarketingDataExtender
from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration
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.djangoapps.util.user_messages import PageLevelMessages
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.course_tools import CourseToolsPluginManager
......@@ -456,7 +456,7 @@ class CourseTabView(EdxFragmentView):
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(
PageLevelMessages.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(
......@@ -470,7 +470,7 @@ class CourseTabView(EdxFragmentView):
)
)
elif not is_enrolled and not is_staff:
register_warning_message(
PageLevelMessages.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(
......
// ------------------------------
// Styling for files located in the openedx/features repository.
// Course call to action message
.course-message {
.message-author {
display: inline-block;
width: 70px;
border-radius: $baseline*7/4;
border: 1px solid $lms-border-color;
@media (max-width: $grid-breakpoints-md) {
display: none;
}
}
.message-content {
position: relative;
border: 1px solid $lms-border-color;
margin: 0 $baseline $baseline/2;
padding: $baseline/2 $baseline;
border-radius: $baseline/4;
@media (max-width: $grid-breakpoints-md) {
width: 100%;
margin: $baseline 0;
}
&:after, &:before {
@include left(0);
bottom: 35%;
border: solid transparent;
height: 0;
width: 0;
content: " ";
position: absolute;
@media (max-width: $grid-breakpoints-md) {
display: none;
}
}
&:after {
@include border-right-color($white);
@include margin-left($baseline*-1+1);
border-width: $baseline/2;
}
&:before {
@include margin-left($baseline*-1);
@include border-right-color($lms-border-color);
border-width: $baseline/2;
}
.message-header {
font-weight: $font-semibold;
margin-bottom: $baseline/4;
}
a {
font-weight: $font-semibold;
text-decoration: underline;
}
}
}
// Welcome message
.welcome-message {
border: solid 1px $lms-border-color;
......
......@@ -11,6 +11,10 @@
// ----------------------------
$lms-max-width: 1180px !default;
$grid-breakpoints-sm: 576px !default;
$grid-breakpoints-md: 768px !default;
$grid-breakpoints-lg: 992px !default;
// ----------------------------
// #COLORS
// ----------------------------
......
......@@ -7,11 +7,11 @@
<%!
from django.utils.translation import ugettext as _
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 PageLevelMessages
%>
<%
banner_messages = list(user_messages(request))
banner_messages = list(PageLevelMessages.user_messages(request))
%>
% if banner_messages:
......
......@@ -7,12 +7,7 @@ from django.http import HttpResponseNotFound
from django.utils.translation import ugettext as _
from edxmako.shortcuts import render_to_response
from mako.exceptions import TopLevelLookupException
from openedx.core.djangoapps.util.user_messages import (
register_error_message,
register_info_message,
register_success_message,
register_warning_message,
)
from openedx.core.djangoapps.util.user_messages import PageLevelMessages
def show_reference_template(request, template):
......@@ -40,10 +35,10 @@ def show_reference_template(request, template):
# Add some messages to the course skeleton pages
if u'course-skeleton.html' in request.path:
register_info_message(request, _('This is a test message'))
register_success_message(request, _('This is a success message'))
register_warning_message(request, _('This is a test warning'))
register_error_message(request, _('This is a test error'))
PageLevelMessages.register_info_message(request, _('This is a test message'))
PageLevelMessages.register_success_message(request, _('This is a success message'))
PageLevelMessages.register_warning_message(request, _('This is a test warning'))
PageLevelMessages.register_error_message(request, _('This is a test error'))
return render_to_response(template, context)
except TopLevelLookupException:
......
......@@ -10,15 +10,7 @@ from django.test import RequestFactory
from openedx.core.djangolib.markup import HTML, Text
from student.tests.factories import UserFactory
from ..user_messages import (
register_error_message,
register_info_message,
register_success_message,
register_user_message,
register_warning_message,
user_messages,
UserMessageType,
)
from ..user_messages import PageLevelMessages, UserMessageType
TEST_MESSAGE = 'Test message'
......@@ -26,7 +18,7 @@ TEST_MESSAGE = 'Test message'
@ddt.ddt
class UserMessagesTestCase(TestCase):
"""
Unit tests for user messages.
Unit tests for page level user messages.
"""
def setUp(self):
super(UserMessagesTestCase, self).setUp()
......@@ -46,8 +38,8 @@ class UserMessagesTestCase(TestCase):
"""
Verifies that a user message is escaped correctly.
"""
register_user_message(self.request, UserMessageType.INFO, message)
messages = list(user_messages(self.request))
PageLevelMessages.register_user_message(self.request, UserMessageType.INFO, message)
messages = list(PageLevelMessages.user_messages(self.request))
self.assertEqual(len(messages), 1)
self.assertEquals(messages[0].message_html, expected_message_html)
......@@ -62,17 +54,17 @@ class UserMessagesTestCase(TestCase):
"""
Verifies that a user message returns the correct CSS and icon classes.
"""
register_user_message(self.request, message_type, TEST_MESSAGE)
messages = list(user_messages(self.request))
PageLevelMessages.register_user_message(self.request, message_type, TEST_MESSAGE)
messages = list(PageLevelMessages.user_messages(self.request))
self.assertEqual(len(messages), 1)
self.assertEquals(messages[0].css_class, expected_css_class)
self.assertEquals(messages[0].icon_class, expected_icon_class)
@ddt.data(
(register_error_message, UserMessageType.ERROR),
(register_info_message, UserMessageType.INFO),
(register_success_message, UserMessageType.SUCCESS),
(register_warning_message, UserMessageType.WARNING),
(PageLevelMessages.register_error_message, UserMessageType.ERROR),
(PageLevelMessages.register_info_message, UserMessageType.INFO),
(PageLevelMessages.register_success_message, UserMessageType.SUCCESS),
(PageLevelMessages.register_warning_message, UserMessageType.WARNING),
)
@ddt.unpack
def test_message_type(self, register_message_function, expected_message_type):
......@@ -80,6 +72,6 @@ class UserMessagesTestCase(TestCase):
Verifies that each user message function returns the correct type.
"""
register_message_function(self.request, TEST_MESSAGE)
messages = list(user_messages(self.request))
messages = list(PageLevelMessages.user_messages(self.request))
self.assertEqual(len(messages), 1)
self.assertEquals(messages[0].type, expected_message_type)
......@@ -14,12 +14,12 @@ There are two common use cases:
used to show a success message to the use.
"""
from abc import abstractmethod
from enum import Enum
from django.contrib import messages
from openedx.core.djangolib.markup import Text
EDX_USER_MESSAGE_TAG = 'edx-user-message'
from django.utils.translation import ugettext as _
from openedx.core.djangolib.markup import Text, HTML
class UserMessageType(Enum):
......@@ -49,7 +49,7 @@ ICON_CLASSES = {
class UserMessage():
"""
Representation of a message to be shown to a user
Representation of a message to be shown to a user.
"""
def __init__(self, type, message_html):
assert isinstance(type, UserMessageType)
......@@ -67,71 +67,124 @@ class UserMessage():
def icon_class(self):
"""
Returns the CSS icon class representing the message type.
Returns:
"""
return ICON_CLASSES[self.type]
def register_user_message(request, message_type, message, title=None):
class UserMessageCollection():
"""
Register a message to be shown to the user in the next page.
A collection of messages to be shown to a user.
"""
assert isinstance(message_type, UserMessageType)
messages.add_message(request, message_type.value, Text(message), extra_tags=EDX_USER_MESSAGE_TAG)
@classmethod
@abstractmethod
def get_namespace(self):
"""
Returns the namespace of the message collection.
The name is used to namespace the subset of django messages.
For example, return 'course_home_messages'.
"""
raise NotImplementedError('Subclasses must define a namespace for messages.')
def register_info_message(request, message, **kwargs):
"""
Registers an information message to be shown to the user.
"""
register_user_message(request, UserMessageType.INFO, message, **kwargs)
@classmethod
def get_message_html(self, body_html, title=None):
"""
Returns the entire HTML snippet for the message.
Classes that extend this base class can override the message styling
by implementing their own version of this function. Messages that do
not use a title can just pass the body_html.
"""
if title:
return Text(_('{header_open}{title}{header_close}{body}')).format(
header_open=HTML('<div class="message-header">'),
title=title,
body=body_html,
header_close=HTML('</div>')
)
return body_html
@classmethod
def register_user_message(self, request, message_type, body_html, title=None):
"""
Register a message to be shown to the user in the next page.
def register_success_message(request, message, **kwargs):
"""
Registers a success message to be shown to the user.
"""
register_user_message(request, UserMessageType.SUCCESS, message, **kwargs)
Arguments:
message_type (UserMessageType): the user message type
body_html (str): body of the message in html format
title (str): optional title for the message as plain text
"""
assert isinstance(message_type, UserMessageType)
message = Text(self.get_message_html(body_html, title))
messages.add_message(request, message_type.value, Text(message), extra_tags=self.get_namespace())
@classmethod
def register_info_message(self, request, message, **kwargs):
"""
Registers an information message to be shown to the user.
"""
self.register_user_message(request, UserMessageType.INFO, message, **kwargs)
def register_warning_message(request, message, **kwargs):
"""
Registers a warning message to be shown to the user.
"""
register_user_message(request, UserMessageType.WARNING, message, **kwargs)
@classmethod
def register_success_message(self, request, message, **kwargs):
"""
Registers a success message to be shown to the user.
"""
self.register_user_message(request, UserMessageType.SUCCESS, message, **kwargs)
@classmethod
def register_warning_message(self, request, message, **kwargs):
"""
Registers a warning message to be shown to the user.
"""
self.register_user_message(request, UserMessageType.WARNING, message, **kwargs)
def register_error_message(request, message, **kwargs):
"""
Registers an error message to be shown to the user.
"""
register_user_message(request, UserMessageType.ERROR, message, **kwargs)
@classmethod
def register_error_message(self, request, message, **kwargs):
"""
Registers an error message to be shown to the user.
"""
self.register_user_message(request, UserMessageType.ERROR, message, **kwargs)
@classmethod
def user_messages(self, request):
"""
Returns any outstanding user messages.
Note: this function also marks these messages as being complete
so they won't be returned in the next request.
"""
def _get_message_type_for_level(level):
"""
Returns the user message type associated with a level.
"""
for __, type in UserMessageType.__members__.items():
if type.value is level:
return type
raise 'Unable to find UserMessageType for level {level}'.format(level=level)
def _create_user_message(message):
"""
Creates a user message from a Django message.
"""
return UserMessage(
type=_get_message_type_for_level(message.level),
message_html=unicode(message.message),
)
django_messages = messages.get_messages(request)
return (_create_user_message(message) for message in django_messages if self.get_namespace() in message.tags)
def user_messages(request):
"""
Returns any outstanding user messages.
Note: this function also marks these messages as being complete
so they won't be returned in the next request.
class PageLevelMessages(UserMessageCollection):
"""
def _get_message_type_for_level(level):
"""
Returns the user message type associated with a level.
"""
for __, type in UserMessageType.__members__.items():
if type.value is level:
return type
raise 'Unable to find UserMessageType for level {level}'.format(level=level)
This set of messages appears as top page level messages.
"""
NAMESPACE = 'page_level_messages'
def _create_user_message(message):
@classmethod
def get_namespace(self):
"""
Creates a user message from a Django message.
Returns the namespace of the message collection.
"""
return UserMessage(
type=_get_message_type_for_level(message.level),
message_html=unicode(message.message),
)
django_messages = messages.get_messages(request)
return (_create_user_message(message) for message in django_messages if EDX_USER_MESSAGE_TAG in message.tags)
return self.NAMESPACE
......@@ -3,7 +3,8 @@ Unified course experience settings and helper methods.
"""
from django.utils.translation import ugettext as _
from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag, WaffleFlag, WaffleFlagNamespace
from openedx.core.djangoapps.util.user_messages import UserMessageCollection
from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag, WaffleFlagNamespace
# Namespace for course experience waffle flags.
......@@ -58,3 +59,17 @@ def course_home_url_name(course_key):
return 'openedx.course_experience.course_home'
else:
return 'info'
class CourseHomeMessages(UserMessageCollection):
"""
This set of messages appear above the outline on the course home page.
"""
NAMESPACE = 'course_home_level_messages'
@classmethod
def get_namespace(self):
"""
Returns the namespace of the message collection.
"""
return self.NAMESPACE
......@@ -57,6 +57,10 @@ from openedx.features.course_experience import UNIFIED_COURSE_TAB_FLAG, SHOW_REV
<div class="page-content">
<div class="layout layout-1t2t">
<main class="layout-col layout-col-b">
% if course_home_message_fragment:
${HTML(course_home_message_fragment.body_html())}
% endif
% if welcome_message_fragment and UNIFIED_COURSE_TAB_FLAG.is_enabled(course.id):
<div class="section section-dates">
${HTML(welcome_message_fragment.body_html())}
......
## mako
<%page expression_filter="h"/>
<%namespace name='static' file='../static_content.html'/>
<%!
from django.utils.translation import get_language_bidi
from openedx.core.djangolib.markup import HTML
from openedx.features.course_experience import CourseHomeMessages
%>
<%
is_rtl = get_language_bidi()
%>
% if course_home_messages:
% for message in course_home_messages:
<div class="course-message grid-manual">
% if not is_rtl:
<img class="message-author col col-2" src="${static.url(image_src)}"/>
% endif
<div class="message-content col col-9">
${HTML(message.message_html)}
</div>
% if is_rtl:
<img class="message-author col col-2" src="${static.url(image_src)}"/>
% endif
</div>
% endfor
% endif
......@@ -2,10 +2,10 @@
"""
Tests for the course home page.
"""
import datetime
from datetime import datetime, timedelta
import ddt
import mock
import pytz
from pytz import UTC
from waffle.testutils import override_flag
from courseware.tests.factories import StaffFactory
......@@ -31,6 +31,10 @@ TEST_CHAPTER_NAME = 'Test Chapter'
TEST_WELCOME_MESSAGE = '<h2>Welcome!</h2>'
TEST_UPDATE_MESSAGE = '<h2>Test Update!</h2>'
TEST_COURSE_UPDATES_TOOL = '/course/updates">'
TEST_COURSE_HOME_MESSAGE = 'course-message'
TEST_COURSE_HOME_MESSAGE_ANONYMOUS = '/login'
TEST_COURSE_HOME_MESSAGE_UNENROLLED = 'Enroll now'
TEST_COURSE_HOME_MESSAGE_PRE_START = 'Course starts in'
QUERY_COUNT_TABLE_BLACKLIST = WAFFLE_TABLES
......@@ -73,7 +77,12 @@ class CourseHomePageTestCase(SharedModuleStoreTestCase):
# pylint: disable=super-method-not-called
with super(CourseHomePageTestCase, cls).setUpClassAndTestData():
with cls.store.default_store(ModuleStoreEnum.Type.split):
cls.course = CourseFactory.create(org='edX', number='test', display_name='Test Course')
cls.course = CourseFactory.create(
org='edX',
number='test',
display_name='Test Course',
start=datetime.now(UTC) - timedelta(days=30),
)
with cls.store.bulk_operations(cls.course.id):
chapter = ItemFactory.create(
category='chapter',
......@@ -92,6 +101,15 @@ class CourseHomePageTestCase(SharedModuleStoreTestCase):
cls.user = UserFactory(password=TEST_PASSWORD)
CourseEnrollment.enroll(cls.user, cls.course.id)
def create_future_course(self, specific_date=None):
"""
Creates and returns a course in the future.
"""
return CourseFactory.create(
display_name='Test Future Course',
start=specific_date if specific_date else datetime.now(UTC) + timedelta(days=30),
)
class TestCourseHomePage(CourseHomePageTestCase):
def setUp(self):
......@@ -152,18 +170,15 @@ class TestCourseHomePage(CourseHomePageTestCase):
"""
Verify that the course home page handles start dates correctly.
"""
now = datetime.datetime.now(pytz.UTC)
tomorrow = now + datetime.timedelta(days=1)
self.course.start = tomorrow
# The course home page should 404 for a course starting in the future
url = course_home_url(self.course)
future_course = self.create_future_course(datetime(2030, 1, 1, tzinfo=UTC))
url = course_home_url(future_course)
response = self.client.get(url)
self.assertRedirects(response, '/dashboard?notlive=Jan+01%2C+2030')
# With the Waffle flag enabled, the course should be visible
with override_flag(COURSE_PRE_START_ACCESS_FLAG.namespaced_flag_name, True):
url = course_home_url(self.course)
url = course_home_url(future_course)
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
......@@ -272,11 +287,12 @@ class TestCourseHomePageAccess(CourseHomePageTestCase):
Ensure that a user accessing a non-live course sees a redirect to
the student dashboard, not a 404.
"""
self.user = self.create_user_for_course(self.course, CourseUserType.ENROLLED)
future_course = self.create_future_course()
self.user = self.create_user_for_course(future_course, CourseUserType.ENROLLED)
url = course_home_url(self.course)
url = course_home_url(future_course)
response = self.client.get(url)
start_date = strftime_localized(self.course.start, 'SHORT_DATE')
start_date = strftime_localized(future_course.start, 'SHORT_DATE')
expected_params = QueryDict(mutable=True)
expected_params['notlive'] = start_date
expected_url = '{url}?{params}'.format(
......@@ -292,12 +308,13 @@ class TestCourseHomePageAccess(CourseHomePageTestCase):
Ensure that a user accessing a non-live course sees a redirect to
the student dashboard, not a 404, even if the localized date is unicode
"""
self.user = self.create_user_for_course(self.course, CourseUserType.ENROLLED)
future_course = self.create_future_course()
self.user = self.create_user_for_course(future_course, CourseUserType.ENROLLED)
fake_unicode_start_time = u"üñîçø∂é_ßtå®t_tîµé"
mock_strftime_localized.return_value = fake_unicode_start_time
url = course_home_url(self.course)
url = course_home_url(future_course)
response = self.client.get(url)
expected_params = QueryDict(mutable=True)
expected_params['notlive'] = fake_unicode_start_time
......@@ -316,3 +333,44 @@ class TestCourseHomePageAccess(CourseHomePageTestCase):
url = course_home_url_from_string('not/a/course')
response = self.client.get(url)
self.assertEqual(response.status_code, 404)
@override_waffle_flag(UNIFIED_COURSE_TAB_FLAG, active=True)
@override_waffle_flag(COURSE_PRE_START_ACCESS_FLAG, active=True)
def test_course_messaging(self):
"""
Ensure that the following four use cases work as expected
1) Anonymous users are shown a course message linking them to the login page
2) Unenrolled users are shown a course message allowing them to enroll
3) Enrolled users who show up on the course page after the course has begun
are not shown a course message.
4) Enrolled users who show up on the course page before the course begins
are shown a message explaining when the course starts as well as a call to
action button that allows them to add a calendar event.
"""
# Verify that anonymous users are shown a login link in the course message
url = course_home_url(self.course)
response = self.client.get(url)
self.assertContains(response, TEST_COURSE_HOME_MESSAGE)
self.assertContains(response, TEST_COURSE_HOME_MESSAGE_ANONYMOUS)
# Verify that unenrolled users are shown an enroll call to action message
self.user = self.create_user_for_course(self.course, CourseUserType.UNENROLLED)
url = course_home_url(self.course)
response = self.client.get(url)
self.assertContains(response, TEST_COURSE_HOME_MESSAGE)
self.assertContains(response, TEST_COURSE_HOME_MESSAGE_UNENROLLED)
# Verify that enrolled users are not shown a message when enrolled and course has begun
CourseEnrollment.enroll(self.user, self.course.id)
url = course_home_url(self.course)
response = self.client.get(url)
self.assertNotContains(response, TEST_COURSE_HOME_MESSAGE)
# Verify that enrolled users are shown 'days until start' message before start date
future_course = self.create_future_course()
CourseEnrollment.enroll(self.user, future_course.id)
url = course_home_url(future_course)
response = self.client.get(url)
self.assertContains(response, TEST_COURSE_HOME_MESSAGE)
self.assertContains(response, TEST_COURSE_HOME_MESSAGE_PRE_START)
......@@ -26,6 +26,7 @@ from web_fragments.fragment import Fragment
from ..utils import get_course_outline_block_tree
from .course_dates import CourseDatesFragmentView
from .course_home_messages import CourseHomeMessageFragmentView
from .course_outline import CourseOutlineFragmentView
from .course_sock import CourseSockFragmentView
from .welcome_message import WelcomeMessageFragmentView
......@@ -113,9 +114,12 @@ class CourseHomeFragmentView(EdxFragmentView):
# Render the full content to enrolled users, as well as to course and global staff.
# Unenrolled users who are not course or global staff are given only a subset.
is_enrolled = CourseEnrollment.is_enrolled(request.user, course_key)
is_staff = has_access(request.user, 'staff', course_key)
if is_enrolled or is_staff:
user_access = {
'is_anonymous': request.user.is_anonymous(),
'is_enrolled': CourseEnrollment.is_enrolled(request.user, course_key),
'is_staff': has_access(request.user, 'staff', course_key),
}
if user_access['is_enrolled'] or user_access['is_staff']:
outline_fragment = CourseOutlineFragmentView().render_to_fragment(request, course_id=course_id, **kwargs)
welcome_message_fragment = WelcomeMessageFragmentView().render_to_fragment(
request, course_id=course_id, **kwargs
......@@ -141,6 +145,11 @@ class CourseHomeFragmentView(EdxFragmentView):
# Get the course tools enabled for this user and course
course_tools = CourseToolsPluginManager.get_enabled_course_tools(request, course_key)
# Grab the course home messages fragment to render any relevant django messages
course_home_message_fragment = CourseHomeMessageFragmentView().render_to_fragment(
request, course_id=course_id, user_access=user_access, **kwargs
)
# Render the course home fragment
context = {
'request': request,
......@@ -149,6 +158,7 @@ class CourseHomeFragmentView(EdxFragmentView):
'course_key': course_key,
'outline_fragment': outline_fragment,
'handouts_html': handouts_html,
'course_home_message_fragment': course_home_message_fragment,
'has_visited_course': has_visited_course,
'resume_course_url': resume_course_url,
'course_tools': course_tools,
......
"""
View logic for handling course messages.
"""
from babel.dates import format_date, format_timedelta
from datetime import datetime
from courseware.courses import get_course_with_access
from django.template.loader import render_to_string
from django.utils.http import urlquote_plus
from django.utils.timezone import UTC
from django.utils.translation import get_language, to_locale
from django.utils.translation import ugettext as _
from openedx.core.djangolib.markup import Text, HTML
from opaque_keys.edx.keys import CourseKey
from web_fragments.fragment import Fragment
from openedx.core.djangoapps.plugin_api.views import EdxFragmentView
from openedx.features.course_experience import CourseHomeMessages
class CourseHomeMessageFragmentView(EdxFragmentView):
"""
A fragment that displays a course message with an alert and call
to action for three types of users:
1) Not logged in users are given a link to sign in or register.
2) Unenrolled users are given a link to enroll.
3) Enrolled users who get to the page before the course start date
are given the option to add the start date to their calendar.
This fragment requires a user_access map as follows:
user_access = {
'is_anonymous': True if the user is logged in, False otherwise
'is_enrolled': True if the user is enrolled in the course, False otherwise
'is_staff': True if the user is a staff member of the course, False otherwise
}
"""
def render_to_fragment(self, request, course_id, user_access, **kwargs):
"""
Renders a course message fragment for the specified course.
"""
course_key = CourseKey.from_string(course_id)
course = get_course_with_access(request.user, 'load', course_key)
# Get time until the start date, if already started, or no start date, value will be zero or negative
now = datetime.now(UTC())
already_started = course.start and now > course.start
days_until_start_string = "started" if already_started else format_timedelta(course.start - now, locale=to_locale(get_language()))
course_start_data = {
'course_start_date': format_date(course.start, locale=to_locale(get_language())),
'already_started': already_started,
'days_until_start_string': days_until_start_string
}
# Register the course home messages to be loaded on the page
self.register_course_home_messages(request, course, user_access, course_start_data)
# Grab the relevant messages
course_home_messages = list(CourseHomeMessages.user_messages(request))
# Return None if user is enrolled and course has begun
if user_access['is_enrolled'] and already_started:
return None
# Grab the logo
image_src = "course_experience/images/home_message_author.png"
context = {
'course_home_messages': course_home_messages,
'image_src': image_src,
}
html = render_to_string('course_experience/course-messages-fragment.html', context)
return Fragment(html)
@staticmethod
def register_course_home_messages(request, course, user_access, course_start_data):
"""
Register messages to be shown in the course home content page.
"""
if user_access['is_anonymous']:
CourseHomeMessages.register_info_message(
request,
Text(_(
" {sign_in_link} or {register_link} and then enroll in this course."
)).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),
)
),
title='You must be enrolled in the course to see course content.'
)
if not user_access['is_anonymous'] and not user_access['is_staff'] and not user_access['is_enrolled']:
CourseHomeMessages.register_info_message(
request,
Text(_(
"{open_enroll_link} Enroll now{close_enroll_link} to access the full course."
)).format(
open_enroll_link='',
close_enroll_link=''
),
title=Text('Welcome to {course_display_name}').format(
course_display_name=course.display_name
)
)
if user_access['is_enrolled'] and not course_start_data['already_started']:
CourseHomeMessages.register_info_message(
request,
Text(_(
"{add_reminder_open_tag}Don't forget to add a calendar reminder!{add_reminder_close_tag}."
)).format(
add_reminder_open_tag='',
add_reminder_close_tag=''
),
title=Text("Course starts in {days_until_start_string} on {course_start_date}.").format(
days_until_start_string=course_start_data['days_until_start_string'],
course_start_date=course_start_data['course_start_date']
)
)
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