From 32d62f26b2ddf3f0807c3095a128d5cbc3c44f18 Mon Sep 17 00:00:00 2001 From: Louis Date: Wed, 29 Jul 2026 12:44:22 +0200 Subject: [PATCH] feat: repeat license expiration reminders daily --- CHANGELOG.md | 1 + README.md | 7 ++++--- netbox_slm/__init__.py | 2 +- netbox_slm/jobs.py | 32 ++++++++++++++++---------------- netbox_slm/tests/test_models.py | 4 ++-- 5 files changed, 24 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f73fba0..ae02240 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ * Make the license type field optional * Show the plugin under Licences in the main menu * Restrict SMTP notification activation to administrators and explicitly authorized users +* Repeat NetBox and optional email reminders on every daily job run from the first due interval through expiration ## [1.9.0](https://github.com/ICTU/netbox_slm/releases/tag/1.9.0) - 2026-06-25 diff --git a/README.md b/README.md index 04974ec..d2dba84 100644 --- a/README.md +++ b/README.md @@ -86,9 +86,10 @@ Die Aktivierung des E-Mail-Versands darf nur durch Administratoren oder Benutzer `netbox_slm.manage_softwarelicense_email_notifications` geändert werden. Die eigentlichen SMTP-Zugangsdaten werden ausschließlich in der NetBox-Serverkonfiguration gepflegt und sind im Plugin nicht einsehbar. -Die Prüfung läuft einmal täglich als NetBox-Systemjob. Der NetBox-RQ-Worker muss daher mit Scheduler-Unterstützung -laufen; dies ist bei der regulären NetBox-Installation mit `rqworker` standardmäßig der Fall. Das interne -Versandprotokoll verhindert doppelte Nachrichten. +Die Prüfung läuft einmal täglich als NetBox-Systemjob. Sobald der erste konfigurierte Erinnerungstermin erreicht ist, +werden die Abonnenten bei jedem Joblauf erneut informiert: immer über NetBox und bei aktivierter E-Mail-Option +zusätzlich per SMTP. Dies wird bis einschließlich des Ablaufdatums wiederholt. Der NetBox-RQ-Worker muss dafür mit +Scheduler-Unterstützung laufen; dies ist bei der regulären NetBox-Installation mit `rqworker` standardmäßig der Fall. ### 3. Plugin in NetBox aktivieren diff --git a/netbox_slm/__init__.py b/netbox_slm/__init__.py index 8342845..2033879 100644 --- a/netbox_slm/__init__.py +++ b/netbox_slm/__init__.py @@ -17,7 +17,7 @@ limitations under the License. from netbox.plugins import PluginConfig from django.utils.translation import gettext, gettext_lazy as _ -__version__ = "1.12.4" +__version__ = "1.13.0" class SLMConfig(PluginConfig): diff --git a/netbox_slm/jobs.py b/netbox_slm/jobs.py index 051da6e..5dd64d5 100644 --- a/netbox_slm/jobs.py +++ b/netbox_slm/jobs.py @@ -47,23 +47,23 @@ class LicenseExpirationNotificationJob(JobRunner): )[0] now = timezone.now() - if log.netbox_sent_at is None: - object_type = ContentType.objects.get_for_model(license) - Notification.objects.update_or_create( - object_type=object_type, - object_id=license.pk, - user=user, - defaults={ - "object_repr": Notification.get_object_repr(license), - "event_type": LICENSE_EXPIRING, - "read": None, - }, - ) - log.netbox_sent_at = now - log.save(update_fields=("netbox_sent_at",)) - self.logger.info(f"Created NetBox expiration notification for license {license.pk} and user {user}") + object_type = ContentType.objects.get_for_model(license) + Notification.objects.update_or_create( + object_type=object_type, + object_id=license.pk, + user=user, + defaults={ + "created": now, + "object_repr": Notification.get_object_repr(license), + "event_type": LICENSE_EXPIRING, + "read": None, + }, + ) + log.netbox_sent_at = now + log.save(update_fields=("netbox_sent_at",)) + self.logger.info(f"Created NetBox expiration notification for license {license.pk} and user {user}") - if license.email_notifications and user.email and log.email_sent_at is None: + if license.email_notifications and user.email: subject = _("License %(license)s expires on %(date)s") % { "license": license, "date": license.expiration_date, diff --git a/netbox_slm/tests/test_models.py b/netbox_slm/tests/test_models.py index b978e4c..07e96b6 100644 --- a/netbox_slm/tests/test_models.py +++ b/netbox_slm/tests/test_models.py @@ -164,7 +164,7 @@ class ModelTestCase(SlmBaseTestCase): @override_settings(SERVER_EMAIL="netbox@example.com", EMAIL_HOST_USER="smtp-user") @patch("netbox_slm.jobs.send_mail") - def test_expiration_job_notifies_subscribers_once(self, send_mail): + def test_expiration_job_notifies_subscribers_on_every_run(self, send_mail): user = get_user_model().objects.create_user( username="license-recipient", email="recipient@example.com", @@ -192,7 +192,7 @@ class ModelTestCase(SlmBaseTestCase): notification = Notification.objects.get(user=user, object_id=license.pk) self.assertIsInstance(str(notification.event), str) self.assertEqual(1, SoftwareLicenseNotificationLog.objects.filter(user=user, license=license).count()) - send_mail.assert_called_once() + self.assertEqual(2, send_mail.call_count) self.assertEqual("netbox@example.com", send_mail.call_args.args[2]) def test_german_translation(self):