notification: Show a drop down from main navbar for notifications

Closes: #1042.

Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org>
Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
Sunil Mohan Adapa 2020-01-30 13:26:45 -08:00 committed by James Valleroy
parent afe179d91d
commit 8529022f63
No known key found for this signature in database
GPG Key ID: 77C0C75E7B650808
6 changed files with 210 additions and 6 deletions

View File

@ -18,9 +18,11 @@
Django context processors to provide common data to templates. Django context processors to provide common data to templates.
""" """
from django.utils.translation import ugettext as _, ugettext_noop
import re import re
from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_noop
from plinth import cfg, menu from plinth import cfg, menu
from plinth.utils import is_user_admin from plinth.utils import is_user_admin
@ -35,6 +37,9 @@ def common(request):
# the brand name 'FreedomBox' itself to be translated. # the brand name 'FreedomBox' itself to be translated.
ugettext_noop('FreedomBox') ugettext_noop('FreedomBox')
from plinth.notification import Notification
notifications_context = Notification.get_display_context(user=request.user)
slash_indices = [match.start() for match in re.finditer('/', request.path)] slash_indices = [match.start() for match in re.finditer('/', request.path)]
active_menu_urls = [request.path[:index + 1] for index in slash_indices] active_menu_urls = [request.path[:index + 1] for index in slash_indices]
return { return {
@ -42,5 +47,7 @@ def common(request):
'submenu': menu.main_menu.active_item(request), 'submenu': menu.main_menu.active_item(request),
'active_menu_urls': active_menu_urls, 'active_menu_urls': active_menu_urls,
'box_name': _(cfg.box_name), 'box_name': _(cfg.box_name),
'user_is_admin': is_user_admin(request, True) 'user_is_admin': is_user_admin(request, True),
'notifications': notifications_context['notifications'],
'notifications_max_severity': notifications_context['max_severity']
} }

View File

@ -98,6 +98,11 @@
<span class="icon-bar"></span> <span class="icon-bar"></span>
<span class="icon-bar"></span> <span class="icon-bar"></span>
</button> </button>
<ul class="nav navbar-nav">
{% include "notifications-dropdown.html" %}
</ul>
{% block mainmenu_left %} {% block mainmenu_left %}
<a href="{% url 'index' %}" class="navbar-brand <a href="{% url 'index' %}" class="navbar-brand
{% if not submenu.url %} menu_link_active {% else %} {% if not submenu.url %} menu_link_active {% else %}
@ -143,7 +148,11 @@
{% block mainmenu_right %} {% block mainmenu_right %}
{% if user.is_authenticated %} {% if user.is_authenticated %}
{% include "notifications-dropdown.html" %}
{% include "help-menu.html" %} {% include "help-menu.html" %}
<li class="dropdown"> <li class="dropdown">
<a href="{% url 'users:edit' request.user.username %}" <a href="{% url 'users:edit' request.user.username %}"
class="dropdown-toggle" data-toggle="dropdown" class="dropdown-toggle" data-toggle="dropdown"
@ -215,8 +224,9 @@
{% endif %} {% endif %}
{% endblock %} {% endblock %}
</ul> </ul>
</div> </div>
{% include "notifications.html" %}
</div> </div>
</div> </div>

View File

@ -0,0 +1,36 @@
{% comment %}
#
# This file is part of FreedomBox.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
{% endcomment %}
{# Template to display a drop down button for notifications in the navbar #}
{% load i18n %}
{% if notifications %}
<li class="dropdown notifications-dropdown">
<a href="#" title="{% trans "Notifications" %}" class="dropdown-toggle"
data-toggle="dropdown" role="button" aria-expanded="false"
data-target=".notifications">
<span class="fa fa-bell nav-icon"></span>
<span class="badge badge-{{ notifications_max_severity }}">
{{ notifications|length }}
</span>
<span class="caret"></span>
</a>
</li>
{% endif %}

View File

@ -0,0 +1,75 @@
{% comment %}
#
# This file is part of FreedomBox.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
{% endcomment %}
{# Template to display notifications under the navbar #}
{% load i18n %}
{% load static %}
{% if notifications %}
<div class="notifications dropdown">
<ul class="dropdown-menu" role="menu">
{% for note in notifications %}
<li class="notification">
{% if note.data.app_name %}
<div class="app-name">
{% if note.data.app_icon %}
<div class="app-icon fa {{ note.data.app_icon }}"></div>
{% elif note.data.app_icon_filename %}
<img src="{% static 'theme/icons/' %}{{ note.data.app_icon_filename }}.svg"
alt="{{ note.data.app_name }}"
class="notification-icon" />
{% endif %}
{{ note.data.app_name }}
</div>
{% endif %}
{% if note.body %}
{{ note.body.content.decode|safe }}
{% else %}
<div class="notification-title">{{ note.title }}</div>
{% if note.message %}
<p>{{ note.message }}</p>
{% endif %}
{% if note.actions %}
<p>
{% for action in note.actions %}
{% if action.type == "dismiss" %}
<a href="{% url 'notification_dismiss' id=note.id %}?next={{ request.path|iriencode }}"
role="button" class="btn btn-default">
{% trans "Dismiss" %}
</a>
{% else %}
<a href="{% url action.url %}" role="button"
class="btn btn-{{ action.class|default:'default' }}">
{{ action.text }}
</a>
{% endif %}
{% endfor %}
</p>
{% endif %}
{% endif %}
</li>
{% endfor %}
</ul>
</div>
{% endif %}

View File

@ -18,7 +18,7 @@
Test module for custom context processors. Test module for custom context processors.
""" """
from unittest.mock import MagicMock, Mock from unittest.mock import MagicMock, Mock, patch
import pytest import pytest
from django.http import HttpRequest from django.http import HttpRequest
@ -34,7 +34,8 @@ def fixture_menu():
menu_module.init() menu_module.init()
def test_common(): @patch('plinth.notification.Notification')
def test_common(Notification):
"""Verify that the common() function returns the correct values.""" """Verify that the common() function returns the correct values."""
cfg.read() # initialize config settings cfg.read() # initialize config settings
@ -62,7 +63,8 @@ def test_common():
assert response['user_is_admin'] assert response['user_is_admin']
def test_common_border_conditions(): @patch('plinth.notification.Notification')
def test_common_border_conditions(Notification):
"""Verify that the common() function works for border conditions.""" """Verify that the common() function works for border conditions."""
request = HttpRequest() request = HttpRequest()
request.path = '' request.path = ''

View File

@ -609,3 +609,77 @@ a.menu_link_active {
transform: scale(1.2); transform: scale(1.2);
} }
} }
/*
* Notifications
*/
.notifications {
margin-left: -15px;
margin-right: -15px;
}
.notifications .dropdown-menu {
width: 100%;
padding: 0;
float: none;
border-top-left-radius: 0;
border-top-right-radius: 0;
margin-top: 0;
}
/* Style for individual notification */
.notification {
padding: 1rem 1.5rem;
font-size: 15px;
}
.notification:not(:first-child) {
border-top: 1px solid #ddd;
}
.notification-title {
font-weight: bold;
}
img.notification-icon {
display: inline-block;
width: 1.4rem;
height: 1.4rem;
margin-top: -0.4rem;
}
/* Show badge with various colors and overlap it onto icon */
.notifications-dropdown .badge {
padding: 2px 5px;
margin-left: -12px;
color: #fff;
}
.badge-exception, .badge-error {
background-color: #d9534f;
}
.badge-warning {
background-color: #ec971f;
}
.badge-info, badge-debug {
background-color: #5bc0de;
}
/* Don't collapse notifications on small screens */
.collapsing .notifications-dropdown,
.collapse.in .notifications-dropdown {
display: none;
}
.navbar-header .navbar-nav {
float: right;
margin: 6px 10px;
}
@media(min-width:768px) {
.navbar-header .navbar-nav {
display: none;
}
}