Compare commits
4
Commits
3599dc703e
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
32d62f26b2 | ||
|
|
35ddffc903 | ||
|
|
919b2d01e5 | ||
|
|
157c891cde |
@@ -6,6 +6,9 @@
|
||||
|
||||
* Make the required single platform destination explicit when creating an installation
|
||||
* Fix installation form submission on NetBox v4.6.5 when form mixins return no cleaned-data value
|
||||
* Prevent the notification log creation flag from shadowing Django's translation function during email delivery
|
||||
* Use NetBox's configured `EMAIL['FROM_EMAIL']` value as the SMTP sender instead of Django's localhost fallback
|
||||
* Register the expiration event with a concrete string so NetBox 4.6 can render the notification dropdown
|
||||
|
||||
### Added
|
||||
|
||||
@@ -26,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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -15,9 +15,9 @@ limitations under the License.
|
||||
"""
|
||||
|
||||
from netbox.plugins import PluginConfig
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.utils.translation import gettext, gettext_lazy as _
|
||||
|
||||
__version__ = "1.12.1"
|
||||
__version__ = "1.13.0"
|
||||
|
||||
|
||||
class SLMConfig(PluginConfig):
|
||||
@@ -42,7 +42,7 @@ class SLMConfig(PluginConfig):
|
||||
from netbox.events import EVENT_TYPE_KIND_WARNING, EventType
|
||||
from netbox_slm.events import LICENSE_EXPIRING
|
||||
|
||||
EventType(LICENSE_EXPIRING, _("License expiring"), kind=EVENT_TYPE_KIND_WARNING).register()
|
||||
EventType(LICENSE_EXPIRING, gettext("License expiring"), kind=EVENT_TYPE_KIND_WARNING).register()
|
||||
|
||||
from netbox_slm import jobs # noqa: F401
|
||||
|
||||
|
||||
+20
-19
@@ -39,31 +39,31 @@ class LicenseExpirationNotificationJob(JobRunner):
|
||||
self._notify(license, user, max(due_dates))
|
||||
|
||||
def _notify(self, license, user, notification_date):
|
||||
log, _ = SoftwareLicenseNotificationLog.objects.get_or_create(
|
||||
log = SoftwareLicenseNotificationLog.objects.get_or_create(
|
||||
license=license,
|
||||
user=user,
|
||||
expiration_date=license.expiration_date,
|
||||
notification_date=notification_date,
|
||||
)
|
||||
)[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,
|
||||
@@ -81,7 +81,8 @@ class LicenseExpirationNotificationJob(JobRunner):
|
||||
body += "\n" + _("Provider portal: %(url)s") % {"url": license.provider_portal_url}
|
||||
|
||||
try:
|
||||
send_mail(subject, body, settings.DEFAULT_FROM_EMAIL, [user.email], fail_silently=False)
|
||||
from_email = settings.SERVER_EMAIL or settings.EMAIL_HOST_USER
|
||||
send_mail(subject, body, from_email, [user.email], fail_silently=False)
|
||||
except Exception:
|
||||
self.logger.exception(f"Failed to send expiration email for license {license.pk} to user {user}")
|
||||
else:
|
||||
|
||||
@@ -4,6 +4,7 @@ from unittest.mock import Mock, patch
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.test import override_settings
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext, override
|
||||
from extras.models import Notification, Subscription
|
||||
@@ -161,8 +162,9 @@ class ModelTestCase(SlmBaseTestCase):
|
||||
license.get_notification_dates(),
|
||||
)
|
||||
|
||||
@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",
|
||||
@@ -187,9 +189,11 @@ class ModelTestCase(SlmBaseTestCase):
|
||||
runner.run()
|
||||
runner.run()
|
||||
|
||||
self.assertEqual(1, Notification.objects.filter(user=user, object_id=license.pk).count())
|
||||
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):
|
||||
with override("de"):
|
||||
|
||||
Reference in New Issue
Block a user