feat: Add feature to display a global maintenance banner (#28673)
This is intended for use in the Studio OAuth transition (ARCHBOM-1887) but may be useful in the future as well.
This commit is contained in:
@@ -3,6 +3,8 @@ Unit tests for user messages.
|
||||
"""
|
||||
|
||||
|
||||
import warnings
|
||||
|
||||
import ddt
|
||||
from django.contrib.messages.middleware import MessageMiddleware
|
||||
from django.test import RequestFactory, TestCase
|
||||
@@ -76,3 +78,59 @@ class UserMessagesTestCase(TestCase):
|
||||
messages = list(PageLevelMessages.user_messages(self.request))
|
||||
assert len(messages) == 1
|
||||
assert messages[0].type == expected_message_type
|
||||
|
||||
def global_message_count(self):
|
||||
"""
|
||||
Count the number of times the global message appears in the user messages.
|
||||
"""
|
||||
expected_html = """<div class="message-content">I <3 HTML-escaping</div>"""
|
||||
messages = list(PageLevelMessages.user_messages(self.request))
|
||||
return len(list(msg for msg in messages if expected_html in msg.message_html))
|
||||
|
||||
def test_global_message_off_by_default(self):
|
||||
"""Verifies feature toggle."""
|
||||
with self.settings(
|
||||
GLOBAL_NOTICE_ENABLED=False,
|
||||
GLOBAL_NOTICE_MESSAGE="I <3 HTML-escaping",
|
||||
GLOBAL_NOTICE_TYPE='WARNING'
|
||||
):
|
||||
# Missing when feature disabled
|
||||
assert self.global_message_count() == 0
|
||||
|
||||
def test_global_message_persistent(self):
|
||||
"""Verifies global message is always included, when enabled."""
|
||||
with self.settings(
|
||||
GLOBAL_NOTICE_ENABLED=True,
|
||||
GLOBAL_NOTICE_MESSAGE="I <3 HTML-escaping",
|
||||
GLOBAL_NOTICE_TYPE='WARNING'
|
||||
):
|
||||
# Present with no other setup
|
||||
assert self.global_message_count() == 1
|
||||
|
||||
# Present when other messages are present
|
||||
PageLevelMessages.register_user_message(self.request, UserMessageType.INFO, "something else")
|
||||
assert self.global_message_count() == 1
|
||||
|
||||
def test_global_message_error_isolation(self):
|
||||
"""Verifies that any setting errors don't break the page, or other messages."""
|
||||
with self.settings(
|
||||
GLOBAL_NOTICE_ENABLED=True,
|
||||
GLOBAL_NOTICE_MESSAGE=ThrowingMarkup(), # force an error
|
||||
GLOBAL_NOTICE_TYPE='invalid'
|
||||
):
|
||||
PageLevelMessages.register_user_message(self.request, UserMessageType.WARNING, "something else")
|
||||
# Doesn't throw, or even interfere with other messages,
|
||||
# when given invalid settings
|
||||
with warnings.catch_warnings(record=True) as w:
|
||||
warnings.simplefilter('always')
|
||||
messages = list(PageLevelMessages.user_messages(self.request))
|
||||
assert len(w) == 1
|
||||
assert str(w[0].message) == "Could not register global notice: Exception('Some random error')"
|
||||
assert len(messages) == 1
|
||||
assert "something else" in messages[0].message_html
|
||||
|
||||
|
||||
class ThrowingMarkup:
|
||||
"""Class that raises an exception if markupsafe tries to get HTML from it."""
|
||||
def __html__(self):
|
||||
raise Exception("Some random error")
|
||||
|
||||
@@ -15,11 +15,14 @@ There are two common use cases:
|
||||
"""
|
||||
|
||||
|
||||
import warnings
|
||||
from abc import abstractmethod
|
||||
from enum import Enum
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.utils.translation import ugettext as _
|
||||
from edx_toggles.toggles import SettingToggle
|
||||
|
||||
from openedx.core.djangolib.markup import HTML, Text
|
||||
|
||||
@@ -181,12 +184,54 @@ class UserMessageCollection():
|
||||
return (_create_user_message(message) for message in django_messages if self.get_namespace() in message.tags)
|
||||
|
||||
|
||||
# .. toggle_name: GLOBAL_NOTICE_ENABLED
|
||||
# .. toggle_implementation: SettingToggle
|
||||
# .. toggle_default: False
|
||||
# .. toggle_description: When enabled, show the contents of GLOBAL_NOTICE_MESSAGE
|
||||
# as a message on every page. This is intended to be used as a way of
|
||||
# communicating an upcoming or currently active maintenance window or to
|
||||
# warn of known site issues. HTML is not supported for the message content,
|
||||
# only plaintext. Message styling can be controlled with GLOBAL_NOTICE_TYPE,
|
||||
# set to one of INFO, SUCCESS, WARNING, or ERROR (defaulting to INFO). Also
|
||||
# see openedx.core.djangoapps.util.maintenance_banner.add_maintenance_banner
|
||||
# for a variation that only shows a message on specific views.
|
||||
# .. toggle_use_cases: open_edx
|
||||
# .. toggle_creation_date: 2021-09-08
|
||||
GLOBAL_NOTICE_ENABLED = SettingToggle('GLOBAL_NOTICE_ENABLED', default=False)
|
||||
|
||||
|
||||
class PageLevelMessages(UserMessageCollection):
|
||||
"""
|
||||
This set of messages appears as top page level messages.
|
||||
"""
|
||||
NAMESPACE = 'page_level_messages'
|
||||
|
||||
@classmethod
|
||||
def user_messages(cls, request):
|
||||
"""
|
||||
Returns outstanding user messages, along with any persistent site-wide messages.
|
||||
"""
|
||||
msgs = list(super().user_messages(request))
|
||||
|
||||
# Add a global notice message to the returned list, if enabled.
|
||||
try:
|
||||
if GLOBAL_NOTICE_ENABLED.is_enabled():
|
||||
if notice_message := getattr(settings, 'GLOBAL_NOTICE_MESSAGE', None):
|
||||
notice_type_str = getattr(settings, 'GLOBAL_NOTICE_TYPE', None)
|
||||
# If an invalid type is given, better to show a
|
||||
# message with the default type than to fail to
|
||||
# show it at all.
|
||||
notice_type = getattr(UserMessageType, notice_type_str, UserMessageType.INFO)
|
||||
|
||||
msgs.append(UserMessage(
|
||||
type=notice_type,
|
||||
message_html=str(cls.get_message_html(Text(notice_message))),
|
||||
))
|
||||
except BaseException as e:
|
||||
warnings.warn(f"Could not register global notice: {e!r}", UserWarning)
|
||||
|
||||
return msgs
|
||||
|
||||
@classmethod
|
||||
def get_message_html(cls, body_html, title=None, dismissable=False, **kwargs):
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user