Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨(plugin) add notifications to courses #2239

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ Versioning](https://semver.org/spec/v2.0.0.html).

## [Unrealeased]

### Added

- Notification plugin for Course Detail pages

### Changed

- Switch from setup.cfg to pyproject.toml
Expand Down
1 change: 1 addition & 0 deletions sandbox/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,7 @@ class Base(StyleguideMixin, DRFMixin, RichieCoursesConfigurationMixin, Configura
"richie.plugins.simple_picture",
"richie.plugins.simple_text_ckeditor",
"richie.plugins.lti_consumer",
"richie.plugins.notification",
"richie",
# Third party apps
"dj_pagination",
Expand Down
9 changes: 9 additions & 0 deletions src/frontend/scss/colors/_theme.scss
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,15 @@ $r-theme: (
content-color: r-color(purplish-grey),
title-color: r-color('charcoal'),
),
notification-plugin: (
info-background-color: r-color('info-300'),
text-color: r-color('black'),
warn-background-color: r-color('warning-400'),
border-radius: 10px,
icon-stroke-color: r-color('black'),
icon-width: 24px,
icon-height: 24px,
),
) !default;

// On a Richie child project you can easily change a specific value using:
Expand Down
1 change: 1 addition & 0 deletions src/frontend/scss/components/_index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@

@import './templates/courses/plugins/category_plugin';
@import './templates/courses/plugins/licence_plugin';
@import './templates/courses/plugins/notifications_plugin';
@import './templates/richie/section/section';
@import './templates/richie/large_banner/large_banner';
@import './templates/richie/nesteditem/nesteditem';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// change measurement units to rem
.notification-alert {
&__wrapper {
display: flex;
padding: 1rem;
border-radius: r-theme-val(notification-plugin, border-radius);
width: 100%;
margin-block-end: 1rem;
}

&__icon {
display: flex;
align-items: center;
padding: 1rem;
margin-inline-end: 0.4rem;

& svg {
fill: none !important;
stroke: r-theme-val(notification-plugin, icon-stroke-color);
height: r-theme-val(notification-plugin, icon-height);
width: r-theme-val(notification-plugin, icon-width);
flex-shrink: 0;
}
}

&__content {
h2 {
margin-block-start: 0.6rem;
font-size: 0.9rem;
}

p {
margin-block-end: 0.6rem;
font-size: 0.9rem;
}
}
}

.info {
background-color: r-theme-val(notification-plugin, info-background-color);
color: r-theme-val(notification-plugin, text-color);
}

.warning {
background-color: r-theme-val(notification-plugin, warn-background-color);
color: r-theme-val(notification-plugin, text-color);
}
4 changes: 4 additions & 0 deletions src/richie/apps/courses/settings/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,10 @@ def richie_placeholder_conf(name):
"name": _("Assessment and Certification"),
"plugins": ["CKEditorPlugin"],
},
"courses/cms/course_detail.html course_notifications": {
"name": _("Notifications"),
"plugins": ["NotificationPlugin"],
},
# Organization detail
"courses/cms/organization_detail.html banner": {
"name": _("Banner"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,14 @@ <h1 class="subheader__title" property="name">{% render_model current_page "title
{% endif %}
{% endblock title %}

{% block notifications %}
{% if current_page.publisher_is_draft %}
<h2 class="course-detail__title">{% trans 'Notifications' %}</h2>
{% endif %}
{% placeholder "course_notifications" %}
{% endblock notifications %}


{% block categories %}
{% if current_page.publisher_is_draft or not current_page|is_empty_placeholder:"course_icons" or not current_page|is_empty_placeholder:"course_categories" %}
<div class="category-badge-list subheader__badges">
Expand Down Expand Up @@ -226,6 +234,7 @@ <h1 class="subheader__title" property="name">{% render_model current_page "title
{% endif %}
{% endblock snapshot %}


{% block cover %}
{% placeholder_as_plugins "course_cover" as cover_plugins %}
<meta property="image" content="{% thumbnail cover_plugins.0.picture 300x170 replace_alpha='#FFFFFF' crop upscale subject_location=cover_plugins.0.picture.subject_location %}" />
Expand Down
Empty file.
35 changes: 35 additions & 0 deletions src/richie/plugins/notification/cms_plugins.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""
Notification CMS plugin
"""
from django.utils.translation import gettext_lazy as _

from cms.plugin_base import CMSPluginBase
from cms.plugin_pool import plugin_pool

from richie.apps.core.defaults import PLUGINS_GROUP

from .models import Notification


@plugin_pool.register_plugin
class NotificationPlugin(CMSPluginBase):
"""
A plugin to add plain text.
"""

allow_children = False
cache = True
disable_child_plugins = True
fieldsets = ((None, {"fields": ["title", "message", "template"]}),)
model = Notification
module = PLUGINS_GROUP
name = _("Notification")
render_template = "richie/notification/notification.html"

def render(self, context, instance, placeholder):
"""
Build plugin context passed to its template to perform rendering
"""
context = super().render(context, instance, placeholder)
context.update({"instance": instance})
return context
19 changes: 19 additions & 0 deletions src/richie/plugins/notification/factories.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"""
PlainText CMS plugin factories
"""
import factory

from .models import Notification


class NotificationFactory(factory.django.DjangoModelFactory):
"""
Factory to create random instances of Notification for testing.
"""

class Meta:
model = Notification

title = factory.Faker("text", max_nb_chars=42)
message = factory.Faker("text", max_nb_chars=42)
template = Notification.NOTIFICATION_TYPES[1][0]
47 changes: 47 additions & 0 deletions src/richie/plugins/notification/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Generated by Django 3.2.23 on 2024-01-15 14:58

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):
initial = True

dependencies = [
("cms", "0022_auto_20180620_1551"),
]

operations = [
migrations.CreateModel(
name="Notification",
fields=[
(
"cmsplugin_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
related_name="notification_notification",
serialize=False,
to="cms.cmsplugin",
),
),
("title", models.CharField(blank=True, max_length=255)),
("message", models.CharField(max_length=255, verbose_name="Message")),
(
"template",
models.CharField(
choices=[("info", "Information"), ("warning", "Warning")],
default=("info", "Information"),
max_length=16,
verbose_name="Type",
),
),
],
options={
"abstract": False,
},
bases=("cms.cmsplugin",),
),
]
Empty file.
29 changes: 29 additions & 0 deletions src/richie/plugins/notification/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""
Notification plugin models
"""
from django.db import models
from django.utils.translation import gettext_lazy as _

from cms.models.pluginmodel import CMSPlugin


class Notification(CMSPlugin):
"""
Notification plugin model.

To be user to output notification messages on course pages.
"""

NOTIFICATION_TYPES = (
("info", _("Information")),
("warning", _("Warning")),
)

title = models.CharField(max_length=255, blank=True)
message = models.CharField(_("Message"), max_length=255)
template = models.CharField(
_("Type"),
choices=NOTIFICATION_TYPES,
default=NOTIFICATION_TYPES[0],
max_length=16,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{% load i18n %}

<div class="notification-alert__wrapper {{instance.template}}">
<div class="notification-alert__icon">
{% if instance.template == 'info' %}
<svg xmlns="http://www.w3.org/2000/svg" id="info-icon" fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
{% else %}
<svg xmlns="http://www.w3.org/2000/svg" id="warn-icon" fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /></path></svg>
{% endif %}
</div>
<div class="notification-alert__content">
{% trans 'Warning' as transl_title %}
<h2>{{ instance.title|default_if_none:transl_title }}</h2>
<p>{{ instance.message }}</p>
</div>
</div>
Empty file.
115 changes: 115 additions & 0 deletions tests/plugins/notification/test_cms_plugins.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
"""Testing DjangoCMS plugin declaration for Richie's notifications plugin."""
from django.test import TestCase
from django.test.client import RequestFactory

from cms.api import add_plugin
from cms.models import Placeholder
from cms.plugin_rendering import ContentRenderer

from richie.plugins.notification.cms_plugins import NotificationPlugin
from richie.plugins.notification.factories import NotificationFactory


class NotificationPluginTestCase(TestCase):
"""Test suite for the notification plugin."""

def test_cms_plugins_notification_context_and_html(self):
"""
Instanciating this plugin with an instance should populate the context
and render in the template.
"""
placeholder = Placeholder.objects.create(slot="test")

# Create random values for parameters with a factory
notification = NotificationFactory()
fields_list = [
"title",
"message",
"template",
]

model_instance = add_plugin(
placeholder,
NotificationPlugin,
"en",
**{field: getattr(notification, field) for field in fields_list},
)
plugin_instance = model_instance.get_plugin_class_instance()
context = plugin_instance.render({}, model_instance, None)

# Check if "instance" is in context
self.assertIn("instance", context)

# Check if parameters, generated by the factory, are correctly set in "instance" of context
self.assertEqual(context["instance"].title, notification.title)
self.assertEqual(context["instance"].message, notification.message)
self.assertEqual(context["instance"].template, notification.template)

# Get generated html for plain text body
renderer = ContentRenderer(request=RequestFactory())
html = renderer.render_plugin(model_instance, {})

# Check rendered body is correct after save and sanitize
self.assertIn(notification.title, html)
self.assertIn(notification.message, html)
self.assertIn(notification.template, html)

def test_cms_plugins_notification_info_template(self):
"""
Instanciating this plugin with an instance to populate the context
"""
placeholder = Placeholder.objects.create(slot="test")

# Create random values for parameters with a factory with an info template
notification = NotificationFactory(template="info")
fields_list = [
"title",
"message",
"template",
]

model_instance = add_plugin(
placeholder,
NotificationPlugin,
"en",
**{field: getattr(notification, field) for field in fields_list},
)

# Get the generated html
renderer = ContentRenderer(request=RequestFactory())
html = renderer.render_plugin(model_instance, {})

# Check that all expected elements are in the html
self.assertIn('class="notification-alert__wrapper info"', html)
self.assertIn('class="notification-alert__icon"', html)
self.assertTrue('id="info-icon"' in html)

def test_cms_plugins_notification_warn_template(self):
"""
Instanciating this plugin with an instance to populate the context
"""
placeholder = Placeholder.objects.create(slot="test")

# Create random values for parameters with a factory with a warning template
notification = NotificationFactory(template="warning")
fields_list = [
"title",
"message",
"template",
]

model_instance = add_plugin(
placeholder,
NotificationPlugin,
"en",
**{field: getattr(notification, field) for field in fields_list},
)

# Get the generated html
renderer = ContentRenderer(request=RequestFactory())
html = renderer.render_plugin(model_instance, {})

# Check that all expected elements are in the html
self.assertIn('class="notification-alert__wrapper warning"', html)
self.assertIn('class="notification-alert__icon"', html)
self.assertTrue('id="warn-icon"' in html)