mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-17 05:41:25 +02:00
Merge pull request #1829 from modoboa/feature/mail_notif_when_update_avail
Send email notification when new update is available.
This commit is contained in:
@@ -318,6 +318,21 @@ class GeneralParametersForm(param_forms.AdminParametersForm):
|
||||
"Automatically checks if a newer version is available")
|
||||
)
|
||||
|
||||
send_new_versions_email = YesNoField(
|
||||
label=ugettext_lazy("Send an email when new versions are found"),
|
||||
initial=False,
|
||||
help_text=ugettext_lazy(
|
||||
"Send an email to notify admins about new versions"
|
||||
)
|
||||
)
|
||||
new_versions_email_rcpt = lib_fields.UTF8EmailField(
|
||||
label=_("Recipient"),
|
||||
initial="postmaster@yourdomain.test",
|
||||
help_text=_(
|
||||
"Recipient of new versions notification emails."
|
||||
)
|
||||
)
|
||||
|
||||
send_statistics = YesNoField(
|
||||
label=ugettext_lazy("Send statistics"),
|
||||
initial=True,
|
||||
@@ -385,6 +400,8 @@ class GeneralParametersForm(param_forms.AdminParametersForm):
|
||||
"ldap_groups_search_base": "authentication_type=ldap",
|
||||
"check_new_versions": "enable_api_communication=True",
|
||||
"send_statistics": "enable_api_communication=True",
|
||||
"send_new_versions_email": "check_new_versions=True",
|
||||
"new_versions_email_rcpt": "check_new_versions=True"
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
@@ -122,7 +122,7 @@ def check_for_new_versions(sender, include_all, **kwargs):
|
||||
request = get_request()
|
||||
if not request.user.is_superuser:
|
||||
return []
|
||||
status, extensions = utils.check_for_updates(request)
|
||||
status, extensions = utils.check_for_updates()
|
||||
if not status:
|
||||
return [{"id": "newversionavailable"}] if include_all else []
|
||||
return [{
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
"""Management command to communicate with public API."""
|
||||
|
||||
from django.core.mail import EmailMessage
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from modoboa.admin import models as admin_models
|
||||
from modoboa.core.extensions import exts_pool
|
||||
from modoboa.lib import api_client
|
||||
from ... import models
|
||||
from ... import utils
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
@@ -13,6 +17,40 @@ class Command(BaseCommand):
|
||||
|
||||
help = "Communicate with Modoboa public API." # NOQA:A003
|
||||
|
||||
def send_notification(self, local_config, extensions):
|
||||
"""Send email notification about updates."""
|
||||
updates = {}
|
||||
to_create = []
|
||||
for extension in extensions:
|
||||
if "update" not in extension:
|
||||
continue
|
||||
qset = models.ExtensionUpdateHistory.objects.filter(
|
||||
extension=extension["name"], version=extension["last_version"]
|
||||
)
|
||||
if not qset.exists():
|
||||
updates[extension["name"]] = extension["last_version"]
|
||||
to_create.append(
|
||||
models.ExtensionUpdateHistory(
|
||||
extension=extension["name"],
|
||||
version=extension["last_version"]
|
||||
)
|
||||
)
|
||||
if not updates:
|
||||
return
|
||||
content = render_to_string(
|
||||
"core/notifications/update_available.html", {
|
||||
"updates": updates
|
||||
})
|
||||
subject = _("[modoboa] Update(s) available")
|
||||
sender = local_config.parameters.get_value("sender_address")
|
||||
recipient = local_config.parameters.get_value(
|
||||
"new_versions_email_rcpt")
|
||||
msg = EmailMessage(
|
||||
subject, content.strip(), sender, [recipient],
|
||||
)
|
||||
msg.send()
|
||||
models.ExtensionUpdateHistory.objects.bulk_create(to_create)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
"""Command entry point."""
|
||||
local_config = (
|
||||
@@ -34,6 +72,11 @@ class Command(BaseCommand):
|
||||
|
||||
local_config.save()
|
||||
|
||||
if local_config.parameters.get_value("send_new_versions_email"):
|
||||
update_avail, extensions = utils.check_for_updates()
|
||||
if update_avail:
|
||||
self.send_notification(local_config, extensions)
|
||||
|
||||
if not local_config.parameters.get_value("send_statistics"):
|
||||
return
|
||||
extensions = [ext["name"] for ext in exts_pool.list_all()]
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.18 on 2020-01-21 15:59
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0017_auto_20190705_1045'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ExtensionUpdateHistory',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('extension', models.CharField(max_length=100)),
|
||||
('version', models.CharField(max_length=30)),
|
||||
],
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='extensionupdatehistory',
|
||||
unique_together=set([('extension', 'version')]),
|
||||
),
|
||||
]
|
||||
@@ -39,3 +39,15 @@ def modo_api_versions(url, request):
|
||||
{"name": "modoboa", "version": "9.0.0", "url": ""},
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@httmock.urlmatch(
|
||||
netloc=r"api\.modoboa\.org$", path=r"^/1/versions/", method="get")
|
||||
def modo_api_versions_no_update(url, request):
|
||||
"""Simulate versions check."""
|
||||
return {
|
||||
"status_code": 200,
|
||||
"content": [
|
||||
{"name": "modoboa", "version": "0.0.0", "url": ""},
|
||||
]
|
||||
}
|
||||
|
||||
@@ -448,3 +448,16 @@ class LocalConfig(models.Model):
|
||||
"""Load parameter manager."""
|
||||
super(LocalConfig, self).__init__(*args, **kwargs)
|
||||
self.parameters = param_tools.Manager("global", self._parameters)
|
||||
|
||||
|
||||
class ExtensionUpdateHistory(models.Model):
|
||||
"""Keeps track of update notifications."""
|
||||
|
||||
extension = models.CharField(max_length=100)
|
||||
version = models.CharField(max_length=30)
|
||||
|
||||
class Meta:
|
||||
unique_together = [("extension", "version")]
|
||||
|
||||
def __str__(self):
|
||||
return "{}: {}".format(self.extension, self.name)
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
{% load i18n %}
|
||||
|
||||
{% blocktrans count counter=updates|length %}
|
||||
The following update is available:
|
||||
{% plural %}
|
||||
The following updates are available:
|
||||
{% endblocktrans %}
|
||||
|
||||
{% for extension, version in updates.items %}
|
||||
{{ extension }}: {{ version }}
|
||||
{% endfor %}
|
||||
@@ -5,6 +5,7 @@ from io import StringIO
|
||||
import httmock
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from django.core import mail
|
||||
from django.core import management
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
@@ -198,7 +199,37 @@ class APICommunicationTestCase(ModoTestCase):
|
||||
mocks.modo_api_versions):
|
||||
management.call_command("communicate_with_public_api")
|
||||
self.assertEqual(models.LocalConfig.objects.first().api_pk, 100)
|
||||
self.assertEqual(len(mail.outbox), 0)
|
||||
|
||||
url = reverse("core:information")
|
||||
response = self.ajax_request("get", url)
|
||||
self.assertIn("9.0.0", response["content"])
|
||||
|
||||
# Enable notifications
|
||||
self.set_global_parameter("send_new_versions_email", True)
|
||||
with httmock.HTTMock(
|
||||
mocks.modo_api_instance_search,
|
||||
mocks.modo_api_instance_create,
|
||||
mocks.modo_api_instance_update,
|
||||
mocks.modo_api_versions):
|
||||
management.call_command("communicate_with_public_api")
|
||||
self.assertEqual(len(mail.outbox), 1)
|
||||
|
||||
# Call once again and check no new notification has been sent
|
||||
self.set_global_parameter("send_new_versions_email", True)
|
||||
with httmock.HTTMock(
|
||||
mocks.modo_api_instance_search,
|
||||
mocks.modo_api_instance_create,
|
||||
mocks.modo_api_instance_update,
|
||||
mocks.modo_api_versions):
|
||||
management.call_command("communicate_with_public_api")
|
||||
self.assertEqual(len(mail.outbox), 1)
|
||||
|
||||
# Make sure no new notification is sent when no updates
|
||||
with httmock.HTTMock(
|
||||
mocks.modo_api_instance_search,
|
||||
mocks.modo_api_instance_create,
|
||||
mocks.modo_api_instance_update,
|
||||
mocks.modo_api_versions_no_update):
|
||||
management.call_command("communicate_with_public_api")
|
||||
self.assertEqual(len(mail.outbox), 1)
|
||||
|
||||
@@ -74,7 +74,9 @@ SETTINGS_SAMPLE = {
|
||||
"core-ldap_sync_bind_dn": "",
|
||||
"core-ldap_sync_bind_password": "",
|
||||
"core-ldap_sync_account_dn_template": "",
|
||||
"core-ldap_sync_delete_remote_account": False
|
||||
"core-ldap_sync_delete_remote_account": False,
|
||||
"core-send_new_versions_email": False,
|
||||
"core-new_versions_email_rcpt": "postmaster@domain.test"
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ def parse_map_file(path):
|
||||
return content
|
||||
|
||||
|
||||
def check_for_updates(request):
|
||||
def check_for_updates():
|
||||
"""Check if a new version of Modoboa is available."""
|
||||
local_config = models.LocalConfig.objects.first()
|
||||
client = ModoAPIClient()
|
||||
|
||||
@@ -53,7 +53,7 @@ def parameters(request, tplname="core/parameters.html"):
|
||||
@login_required
|
||||
@user_passes_test(lambda u: u.is_superuser)
|
||||
def information(request, tplname="core/information.html"):
|
||||
status, extensions = check_for_updates(request)
|
||||
status, extensions = check_for_updates()
|
||||
return render_to_json_response({
|
||||
"content": render_to_string(tplname, {
|
||||
"update_avail": status,
|
||||
|
||||
Reference in New Issue
Block a user