diff --git a/doc/extending/events.rst b/doc/extending/events.rst index 2741bd421..90bcef511 100644 --- a/doc/extending/events.rst +++ b/doc/extending/events.rst @@ -736,16 +736,30 @@ this user is disabled). TopNotifications ================ -Let extensions add custom content into the top bar. +Lets extensions subscribe to the global notification service (located +inside the top bar). *Callback prototype*:: - def callback(user): pass + def callback(user, include_all): pass * ``user`` is a ``User`` instance corresponding to the currently logged in user +* ``include_all`` is a boolean indicating if empty notifications must + be included into the result or not -Callbacks listening to this event must return a list of string. +Callbacks listening to this event must return a list of dictionary, +each dictionary containing at least the following entries:: + + {"id": "", + "url": "", + "text": ""} + +If your notification needs a counter, you can specify it by adding the +two following entries in the dictionary: + + {"counter": , + "level": ""} UserLogin ========= diff --git a/modoboa/core/__init__.py b/modoboa/core/__init__.py index ec4208b38..bd9ba2318 100644 --- a/modoboa/core/__init__.py +++ b/modoboa/core/__init__.py @@ -1,7 +1,12 @@ -from django.utils.translation import ugettext_lazy +import os + +from django.core.urlresolvers import reverse +from django.utils.translation import ugettext as _, ugettext_lazy + +from modoboa.core.utils import new_version_available from modoboa.lib import parameters, events -base_events = [ +BASE_EVENTS = [ "CanCreate", "AccountCreated", @@ -51,7 +56,7 @@ def load_core_settings(): parameters.register(GeneralParametersForm, ugettext_lazy("General")) parameters.register(UserSettings, ugettext_lazy("General")) - events.declare(base_events) + events.declare(BASE_EVENTS) @events.observe("ExtDisabled") @@ -63,3 +68,20 @@ def unset_default_topredirection(extension): topredirection = parameters.get_admin("DEFAULT_TOP_REDIRECTION") if topredirection == extension.name: parameters.save_admin("DEFAULT_TOP_REDIRECTION", "core") + + +@events.observe("TopNotifications") +def check_for_new_version(user, include_all): + """ + Check if a new version of Modoboa is available. + """ + if not user.is_superuser: + return [] + if new_version_available() is None: + return [{"id": "newversionavailable"}] if include_all else [] + return [{ + "id": "newversionavailable", + "url": reverse("admin_index") + "#info/", + "text": _("New Modoboa version available"), + "level": "info", + }] diff --git a/modoboa/core/app_settings.py b/modoboa/core/app_settings.py index 3b3a77df6..1b01fe34e 100644 --- a/modoboa/core/app_settings.py +++ b/modoboa/core/app_settings.py @@ -171,6 +171,19 @@ class GeneralParametersForm(parameters.AdminParametersForm): sep3 = SeparatorField(label=ugettext_lazy("Miscellaneous")) + top_notifications_check_interval = forms.IntegerField( + label=_("Top notifications check interval"), + initial=30, + help_text=_("Interval between two top notification checks") + ) + + check_new_versions = YesNoField( + label=ugettext_lazy("Check for new versions"), + initial="yes", + help_text=ugettext_lazy( + "Automatically checks if newer versions are available") + ) + log_maximum_age = forms.IntegerField( label=ugettext_lazy("Maximum log record age"), initial=365, diff --git a/modoboa/core/commands/templates/settings.py.tpl b/modoboa/core/commands/templates/settings.py.tpl index c0df35122..07e01a342 100644 --- a/modoboa/core/commands/templates/settings.py.tpl +++ b/modoboa/core/commands/templates/settings.py.tpl @@ -103,6 +103,18 @@ TEMPLATE_LOADERS = ( # 'django.template.loaders.eggs.Loader', ) +TEMPLATE_CONTEXT_PROCESSORS = ( + 'django.contrib.auth.context_processors.auth', + 'django.core.context_processors.debug', + 'django.core.context_processors.i18n', + 'django.core.context_processors.media', + 'django.core.context_processors.static', + 'django.core.context_processors.tz', + 'django.contrib.messages.context_processors.messages', + + 'modoboa.core.context_processors.top_notifications', +) + MIDDLEWARE_CLASSES = ( 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', @@ -174,6 +186,8 @@ SOUTH_TESTS_MIGRATE = False #DOVECOT_LOOKUP_PATH = ('/path/to/dovecot', ) +MODOBOA_API_URL = 'https://api.modoboa.org/1/' + AUTHENTICATION_BACKENDS = ( 'modoboa.lib.authbackends.SimpleBackend', ) diff --git a/modoboa/core/context_processors.py b/modoboa/core/context_processors.py new file mode 100644 index 000000000..218d6bf6e --- /dev/null +++ b/modoboa/core/context_processors.py @@ -0,0 +1,18 @@ +""" +Custom context processors. +""" +from modoboa.lib import events, parameters + + +def top_notifications(request): + """ + A context processor to include top notifications. + """ + if request.user.is_anonymous(): + return {} + return { + "notifications_check_interval": + int(parameters.get_admin("TOP_NOTIFICATIONS_CHECK_INTERVAL")) * 1000, + "top_notifications": events.raiseQueryEvent( + "TopNotifications", request.user, False) + } diff --git a/modoboa/core/templates/core/information.html b/modoboa/core/templates/core/information.html index f74c13139..23f6264c2 100644 --- a/modoboa/core/templates/core/information.html +++ b/modoboa/core/templates/core/information.html @@ -1,5 +1,13 @@ {% load i18n core_tags lib_tags %}

{% trans "Information" %} {% trans "Various information about Modoboa" %}


+{% if new_version %} +
+

{% trans "An update is available!" %}

+ {% blocktrans with version=new_version.version changelog_url=new_version.changelog_url %} + Modoboa {{ version }} is available for download. The list of all changes is available here. + {% endblocktrans %} +
+{% endif %}
Modoboa {% get_version %} {% trans "is currently in use." %}

{% trans "Active users" %}

diff --git a/modoboa/core/templatetags/core_tags.py b/modoboa/core/templatetags/core_tags.py index e1b5ebce1..73aac4899 100644 --- a/modoboa/core/templatetags/core_tags.py +++ b/modoboa/core/templatetags/core_tags.py @@ -211,12 +211,6 @@ def load_optionalmenu(user): ) -@register.simple_tag -def load_notifications(user): - content = events.raiseQueryEvent("TopNotifications", user) - return "".join(content) - - @register.simple_tag def display_messages(msgs): text = "" diff --git a/modoboa/core/urls.py b/modoboa/core/urls.py index c677f73c4..5db970d7c 100644 --- a/modoboa/core/urls.py +++ b/modoboa/core/urls.py @@ -1,4 +1,4 @@ -from django.conf.urls import patterns +from django.conf.urls import patterns, url urlpatterns = patterns( @@ -7,13 +7,18 @@ urlpatterns = patterns( (r'^accounts/login/$', 'modoboa.core.views.auth.dologin'), (r'^accounts/logout/$', 'modoboa.core.views.auth.dologout'), - (r'^core/$', 'modoboa.core.views.admin.viewsettings'), + url(r'^core/$', 'modoboa.core.views.admin.viewsettings', + name="admin_index"), (r'^core/parameters/$', 'modoboa.core.views.admin.viewparameters'), (r'^core/parameters/save/$', 'modoboa.core.views.admin.saveparameters'), (r'^core/extensions/$', 'modoboa.core.views.admin.viewextensions'), (r'^core/extensions/save/$', 'modoboa.core.views.admin.saveextensions'), - (r'^core/info/$', 'modoboa.core.views.admin.information'), + url(r'^core/info/$', 'modoboa.core.views.admin.information', + name="information"), (r'^core/logs/$', 'modoboa.core.views.admin.logs'), + url(r'^core/top_notifications/check/$', + 'modoboa.core.views.admin.check_top_notifications', + name='check_top_notifications'), (r'^user/$', 'modoboa.core.views.user.index'), (r'^user/preferences/$', 'modoboa.core.views.user.preferences'), diff --git a/modoboa/core/utils.py b/modoboa/core/utils.py new file mode 100644 index 000000000..200bd41e9 --- /dev/null +++ b/modoboa/core/utils.py @@ -0,0 +1,32 @@ +""" +Utility functions. +""" +import os +import pkg_resources + +import requests + +from django.conf import settings + +from versionfield.constants import DEFAULT_NUMBER_BITS +from versionfield.version import Version + +from modoboa.lib import parameters + + +def new_version_available(): + """Check if a new version of Modoboa is available.""" + if parameters.get_admin("CHECK_NEW_VERSIONS") == "no": + return None + local_version = pkg_resources.get_distribution("modoboa").version + url = os.path.join(settings.MODOBOA_API_URL, "current_version/") + resp = requests.get(url, params={ + "client_version": pkg_resources.get_distribution("modoboa").version + }) + if resp.status_code != 200: + return None + resp = resp.json() + version = Version(resp["version"], DEFAULT_NUMBER_BITS) + if version <= Version(local_version, DEFAULT_NUMBER_BITS): + return None + return resp diff --git a/modoboa/core/views/admin.py b/modoboa/core/views/admin.py index 26f97e0a9..e2b12671b 100644 --- a/modoboa/core/views/admin.py +++ b/modoboa/core/views/admin.py @@ -4,12 +4,13 @@ from django.utils.translation import ugettext as _ from django.contrib.auth.decorators import ( login_required, user_passes_test ) -from modoboa.lib import parameters +from modoboa.lib import events, parameters from modoboa.lib.webutils import ( _render_to_string, render_to_json_response ) from modoboa.lib.listing import get_sort_order, get_listing_page from modoboa.core.models import Extension, Log +from modoboa.core.utils import new_version_available from modoboa.core.tables import ExtensionsTable @@ -94,7 +95,9 @@ def saveextensions(request): @user_passes_test(lambda u: u.is_superuser) def information(request, tplname="core/information.html"): return render_to_json_response({ - "content": render_to_string(tplname, {}) + "content": render_to_string(tplname, { + "new_version": new_version_available() + }), }) @@ -118,3 +121,12 @@ def logs(request, tplname="core/logs.html"): "page": page.number, "paginbar": pagination_bar(page), }) + +@login_required +def check_top_notifications(request): + """ + AJAX service to check for new top notifications to display. + """ + return render_to_json_response( + events.raiseQueryEvent("TopNotifications", request.user, True) + ) diff --git a/modoboa/extensions/amavis/app_settings.py b/modoboa/extensions/amavis/app_settings.py index 9d933476c..9e777e281 100644 --- a/modoboa/extensions/amavis/app_settings.py +++ b/modoboa/extensions/amavis/app_settings.py @@ -17,7 +17,7 @@ class ParametersForm(AdminParametersForm): ) sep1 = SeparatorField(label=_("Messages releasing")) - + released_msgs_cleanup = YesNoField( label=_("Remove released messages"), initial="no", @@ -49,12 +49,6 @@ class ParametersForm(AdminParametersForm): initial="/var/amavis/amavisd.sock", help_text=_("Path to the PDP server socket (if unix mode)") ) - - check_requests_interval = forms.IntegerField( - label=_("Check requests interval"), - initial=30, - help_text=_("Interval between two release requests checks") - ) user_can_release = YesNoField( label=_("Allow direct release"), diff --git a/modoboa/extensions/amavis/general_callbacks.py b/modoboa/extensions/amavis/general_callbacks.py index 1246c7202..b86ab7cf8 100644 --- a/modoboa/extensions/amavis/general_callbacks.py +++ b/modoboa/extensions/amavis/general_callbacks.py @@ -53,35 +53,6 @@ def extra_static_content(caller, user): if user.group == "SimpleUsers": return [] - if caller == 'top' and parameters.get_admin("USER_CAN_RELEASE") == 'no': - tpl = Template(""" -""") - return [tpl.render( - Context({ - 'url': reverse("modoboa.extensions.amavis.views.nbrequests"), - 'interval': int(parameters.get_admin("CHECK_REQUESTS_INTERVAL")) * 1000, - 'text': _("pending requests"), - }) - )] - if caller == 'domains': tpl = Template(""" + {% url "django.views.i18n.javascript_catalog" as catalog_url %} {% extra_static_content "top" user %} {% block html_head %}{% endblock %} @@ -46,7 +55,7 @@ -
{% load_notifications user %}
+
{% include "common/top_notifications.html" %}
diff --git a/requirements.txt b/requirements.txt index 9c7b48790..01c6c8f36 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,3 +10,4 @@ argparse rfc6266 factory-boy passlib +-e git+https://github.com/tonioo/django-versionfield.git#egg=django-versionfield