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:
Antoine Nguyen
2020-01-22 09:34:45 +01:00
committed by GitHub
11 changed files with 160 additions and 4 deletions
+17
View File
@@ -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):
+1 -1
View File
@@ -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')]),
),
]
+12
View File
@@ -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": ""},
]
}
+13
View File
@@ -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 %}
+31
View File
@@ -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)
+3 -1
View File
@@ -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"
}
+1 -1
View File
@@ -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()
+1 -1
View File
@@ -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,