diff --git a/netbox/core/events.py b/netbox/core/events.py index 98a1ea18f..384b61fd4 100644 --- a/netbox/core/events.py +++ b/netbox/core/events.py @@ -26,7 +26,7 @@ JOB_ERRORED = 'job_errored' # Register core events EventType(OBJECT_CREATED, _('Object created')).register() EventType(OBJECT_UPDATED, _('Object updated')).register() -EventType(OBJECT_DELETED, _('Object deleted')).register() +EventType(OBJECT_DELETED, _('Object deleted'), destructive=True).register() EventType(JOB_STARTED, _('Job started')).register() EventType(JOB_COMPLETED, _('Job completed'), kind=EVENT_TYPE_KIND_SUCCESS).register() EventType(JOB_FAILED, _('Job failed'), kind=EVENT_TYPE_KIND_WARNING).register() diff --git a/netbox/extras/migrations/0118_notifications.py b/netbox/extras/migrations/0118_notifications.py index 08904ebb5..22b042019 100644 --- a/netbox/extras/migrations/0118_notifications.py +++ b/netbox/extras/migrations/0118_notifications.py @@ -54,6 +54,7 @@ class Migration(migrations.Migration): ('object_id', models.PositiveBigIntegerField()), ('event_type', models.CharField(max_length=50)), ('object_type', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='contenttypes.contenttype')), + ('object_repr', models.CharField(editable=False, max_length=200)), ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notifications', to=settings.AUTH_USER_MODEL)), ], options={ diff --git a/netbox/extras/models/notifications.py b/netbox/extras/models/notifications.py index a15dfe9aa..7fe03147c 100644 --- a/netbox/extras/models/notifications.py +++ b/netbox/extras/models/notifications.py @@ -58,6 +58,10 @@ class Notification(models.Model): ct_field='object_type', fk_field='object_id' ) + object_repr = models.CharField( + max_length=200, + editable=False + ) event_type = models.CharField( verbose_name=_('event'), max_length=50, @@ -81,9 +85,7 @@ class Notification(models.Model): verbose_name_plural = _('notifications') def __str__(self): - if self.object: - return str(self.object) - return super().__str__() + return self.object_repr def get_absolute_url(self): return reverse('account:notifications') @@ -97,6 +99,13 @@ class Notification(models.Model): _("Objects of this type ({type}) do not support notifications.").format(type=self.object_type) ) + def save(self, *args, **kwargs): + # Record a string representation of the associated object + if self.object: + self.object_repr = self.get_object_repr(self.object) + + super().save(*args, **kwargs) + @cached_property def event(self): """ @@ -104,6 +113,10 @@ class Notification(models.Model): """ return registry['event_types'].get(self.event_type) + @classmethod + def get_object_repr(cls, obj): + return str(obj)[:200] + class NotificationGroup(ChangeLoggedModel): """ diff --git a/netbox/extras/signals.py b/netbox/extras/signals.py index eae9c02a0..10c3f73c5 100644 --- a/netbox/extras/signals.py +++ b/netbox/extras/signals.py @@ -137,11 +137,18 @@ def process_job_end_event_rules(sender, **kwargs): # Notifications # -@receiver(post_save) -def notify_object_changed(sender, instance, created, raw, **kwargs): - if created or raw: +@receiver((post_save, pre_delete)) +def notify_object_changed(sender, instance, **kwargs): + # Skip for newly-created objects + if kwargs.get('created'): return + # Determine event type + if 'created' in kwargs: + event_type = OBJECT_UPDATED + else: + event_type = OBJECT_DELETED + # Skip unsupported object types ct = ContentType.objects.get_for_model(instance) if ct.model not in registry['model_features']['notifications'].get(ct.app_label, []): @@ -157,6 +164,11 @@ def notify_object_changed(sender, instance, created, raw, **kwargs): # Create Notifications for Subscribers Notification.objects.bulk_create([ - Notification(user_id=user, object=instance, event_type=OBJECT_UPDATED) + Notification( + user_id=user, + object=instance, + object_repr=Notification.get_object_repr(instance), + event_type=event_type + ) for user in subscribed_users ]) diff --git a/netbox/extras/tables/tables.py b/netbox/extras/tables/tables.py index e8284801a..8a4e80209 100644 --- a/netbox/extras/tables/tables.py +++ b/netbox/extras/tables/tables.py @@ -44,6 +44,14 @@ NOTIFICATION_ICON = """ """ +NOTIFICATION_LINK = """ +{% if not record.event.destructive %} + {{ record.object_repr }} +{% else %} + {{ record.object_repr }} +{% endif %} +""" + class CustomFieldTable(NetBoxTable): name = tables.Column( @@ -314,12 +322,9 @@ class NotificationTable(NetBoxTable): object_type = columns.ContentTypeColumn( verbose_name=_('Object Type'), ) - object = tables.Column( + object = columns.TemplateColumn( verbose_name=_('Object'), - linkify={ - 'viewname': 'extras:notification_read', - 'args': [tables.A('pk')], - }, + template_code=NOTIFICATION_LINK, orderable=False ) created = columns.DateTimeColumn( diff --git a/netbox/netbox/events.py b/netbox/netbox/events.py index 2fa4b6e78..e48c32815 100644 --- a/netbox/netbox/events.py +++ b/netbox/netbox/events.py @@ -45,10 +45,12 @@ class EventType: name: The unique name under which the event is registered. text: The human-friendly event name. This should support translation. kind: The event's classification (info, success, warning, or danger). The default type is info. + destructive: Indicates that the associated object was destroyed as a result of the event (default: False). """ name: str text: str kind: str = EVENT_TYPE_KIND_INFO + destructive: bool = False def __str__(self): return self.text @@ -58,6 +60,7 @@ class EventType: raise Exception(f"An event type named {self.name} has already been registered!") registry['event_types'][self.name] = self + @property def color(self): return { EVENT_TYPE_KIND_INFO: 'blue', @@ -66,6 +69,7 @@ class EventType: EVENT_TYPE_KIND_DANGER: 'red', }.get(self.kind) + @property def icon(self): return { EVENT_TYPE_KIND_INFO: 'mdi mdi-information', diff --git a/netbox/templates/htmx/notifications.html b/netbox/templates/htmx/notifications.html index bd2de4e6e..23660e4c5 100644 --- a/netbox/templates/htmx/notifications.html +++ b/netbox/templates/htmx/notifications.html @@ -7,7 +7,7 @@
- {{ notification.object }} + {{ notification.object_repr }}
{{ notification.event }} {{ notification.created|timesince }} {% trans "ago" %}