diff --git a/common/djangoapps/status/models.py b/common/djangoapps/status/models.py
index 894891f1b1..a4eb3bf161 100644
--- a/common/djangoapps/status/models.py
+++ b/common/djangoapps/status/models.py
@@ -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"{}
{}".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"{}
{}".format(msg, course_home_message.message)
except CourseMessage.DoesNotExist:
# We don't have a course-specific message, so pass.
pass
diff --git a/lms/djangoapps/courseware/views/views.py b/lms/djangoapps/courseware/views/views.py
index 426beaa225..7ce53d650b 100644
--- a/lms/djangoapps/courseware/views/views.py
+++ b/lms/djangoapps/courseware/views/views.py
@@ -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('{sign_in_label}').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('{enroll_link_label}').format(
diff --git a/lms/static/sass/features/_course-experience.scss b/lms/static/sass/features/_course-experience.scss
index 84464da698..e552f6a685 100644
--- a/lms/static/sass/features/_course-experience.scss
+++ b/lms/static/sass/features/_course-experience.scss
@@ -1,3 +1,69 @@
+// ------------------------------
+// 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;
diff --git a/lms/static/sass/shared-v2/_variables.scss b/lms/static/sass/shared-v2/_variables.scss
index 9603ac1136..e9e84343ff 100644
--- a/lms/static/sass/shared-v2/_variables.scss
+++ b/lms/static/sass/shared-v2/_variables.scss
@@ -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
// ----------------------------
diff --git a/lms/templates/page_banner.html b/lms/templates/page_banner.html
index bf1e906107..5e6dddd653 100644
--- a/lms/templates/page_banner.html
+++ b/lms/templates/page_banner.html
@@ -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:
diff --git a/openedx/core/djangoapps/debug/views.py b/openedx/core/djangoapps/debug/views.py
index 983ecda81a..674f7464d2 100644
--- a/openedx/core/djangoapps/debug/views.py
+++ b/openedx/core/djangoapps/debug/views.py
@@ -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:
diff --git a/openedx/core/djangoapps/util/tests/test_user_messages.py b/openedx/core/djangoapps/util/tests/test_user_messages.py
index 6be513578c..f4376c7b80 100644
--- a/openedx/core/djangoapps/util/tests/test_user_messages.py
+++ b/openedx/core/djangoapps/util/tests/test_user_messages.py
@@ -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)
diff --git a/openedx/core/djangoapps/util/user_messages.py b/openedx/core/djangoapps/util/user_messages.py
index 251b4e9339..961d6288d4 100644
--- a/openedx/core/djangoapps/util/user_messages.py
+++ b/openedx/core/djangoapps/util/user_messages.py
@@ -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)
-
-
-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)
-
-
-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)
-
-
-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)
-
-
-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)
-
-
-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.
- """
- def _get_message_type_for_level(level):
+ @classmethod
+ @abstractmethod
+ def get_namespace(self):
"""
- 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)
+ Returns the namespace of the message collection.
- def _create_user_message(message):
+ The name is used to namespace the subset of django messages.
+ For example, return 'course_home_messages'.
"""
- Creates a user message from a Django message.
- """
- return UserMessage(
- type=_get_message_type_for_level(message.level),
- message_html=unicode(message.message),
- )
+ raise NotImplementedError('Subclasses must define a namespace for messages.')
- django_messages = messages.get_messages(request)
- return (_create_user_message(message) for message in django_messages if EDX_USER_MESSAGE_TAG in message.tags)
+ @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('