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.
"""
from django.utils.translation import ugettext as _, ugettext_noop
import re
from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_noop
from plinth import cfg, menu
from plinth.utils import is_user_admin
@ -35,6 +37,9 @@ def common(request):
# the brand name 'FreedomBox' itself to be translated.
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)]
active_menu_urls = [request.path[:index + 1] for index in slash_indices]
return {
@ -42,5 +47,7 @@ def common(request):
'submenu': menu.main_menu.active_item(request),
'active_menu_urls': active_menu_urls,
'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>
</button>
<ul class="nav navbar-nav">
{% include "notifications-dropdown.html" %}
</ul>
{% block mainmenu_left %}
<a href="{% url 'index' %}" class="navbar-brand
{% if not submenu.url %} menu_link_active {% else %}
@ -143,7 +148,11 @@
{% block mainmenu_right %}
{% if user.is_authenticated %}
{% include "notifications-dropdown.html" %}
{% include "help-menu.html" %}
<li class="dropdown">
<a href="{% url 'users:edit' request.user.username %}"
class="dropdown-toggle" data-toggle="dropdown"
@ -215,8 +224,9 @@
{% endif %}
{% endblock %}
</ul>
</div>
{% include "notifications.html" %}
</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.
"""
from unittest.mock import MagicMock, Mock
from unittest.mock import MagicMock, Mock, patch
import pytest
from django.http import HttpRequest
@ -34,7 +34,8 @@ def fixture_menu():
menu_module.init()
def test_common():
@patch('plinth.notification.Notification')
def test_common(Notification):
"""Verify that the common() function returns the correct values."""
cfg.read() # initialize config settings
@ -62,7 +63,8 @@ def test_common():
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."""
request = HttpRequest()
request.path = ''

View File

@ -609,3 +609,77 @@ a.menu_link_active {
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;
}
}