Просмотр исходного кода

#15621: Support notifications for deletion of a subscribed object

Jeremy Stretch 1 год назад
Родитель
Сommit
57fe2071a4

+ 1 - 1
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()

+ 1 - 0
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={

+ 16 - 3
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):
     """

+ 16 - 4
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
     ])

+ 10 - 5
netbox/extras/tables/tables.py

@@ -44,6 +44,14 @@ NOTIFICATION_ICON = """
 <span class="text-{{ value.color }} fs-3"><i class="{{ value.icon }}"></i></span>
 """
 
+NOTIFICATION_LINK = """
+{% if not record.event.destructive %}
+  <a href="{% url 'extras:notification_read' pk=record.pk %}">{{ record.object_repr }}</a>
+{% 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(

+ 4 - 0
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',

+ 1 - 1
netbox/templates/htmx/notifications.html

@@ -7,7 +7,7 @@
           <i class="{{ notification.event.icon }}"></i>
         </div>
         <div class="col text-truncate">
-          <a href="{% url 'extras:notification_read' pk=notification.pk %}" class="text-body d-block">{{ notification.object }}</a>
+          <a href="{% url 'extras:notification_read' pk=notification.pk %}" class="text-body d-block">{{ notification.object_repr }}</a>
           <div class="d-block text-secondary fs-5">{{ notification.event }} {{ notification.created|timesince }} {% trans "ago" %}</div>
         </div>
         <div class="col-auto">