mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-14 09:51:22 -06:00
#15621: Support notifications for deletion of a subscribed object
This commit is contained in:
parent
ca63aed9ee
commit
57fe2071a4
@ -26,7 +26,7 @@ JOB_ERRORED = 'job_errored'
|
|||||||
# Register core events
|
# Register core events
|
||||||
EventType(OBJECT_CREATED, _('Object created')).register()
|
EventType(OBJECT_CREATED, _('Object created')).register()
|
||||||
EventType(OBJECT_UPDATED, _('Object updated')).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_STARTED, _('Job started')).register()
|
||||||
EventType(JOB_COMPLETED, _('Job completed'), kind=EVENT_TYPE_KIND_SUCCESS).register()
|
EventType(JOB_COMPLETED, _('Job completed'), kind=EVENT_TYPE_KIND_SUCCESS).register()
|
||||||
EventType(JOB_FAILED, _('Job failed'), kind=EVENT_TYPE_KIND_WARNING).register()
|
EventType(JOB_FAILED, _('Job failed'), kind=EVENT_TYPE_KIND_WARNING).register()
|
||||||
|
@ -54,6 +54,7 @@ class Migration(migrations.Migration):
|
|||||||
('object_id', models.PositiveBigIntegerField()),
|
('object_id', models.PositiveBigIntegerField()),
|
||||||
('event_type', models.CharField(max_length=50)),
|
('event_type', models.CharField(max_length=50)),
|
||||||
('object_type', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='contenttypes.contenttype')),
|
('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)),
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notifications', to=settings.AUTH_USER_MODEL)),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
|
@ -58,6 +58,10 @@ class Notification(models.Model):
|
|||||||
ct_field='object_type',
|
ct_field='object_type',
|
||||||
fk_field='object_id'
|
fk_field='object_id'
|
||||||
)
|
)
|
||||||
|
object_repr = models.CharField(
|
||||||
|
max_length=200,
|
||||||
|
editable=False
|
||||||
|
)
|
||||||
event_type = models.CharField(
|
event_type = models.CharField(
|
||||||
verbose_name=_('event'),
|
verbose_name=_('event'),
|
||||||
max_length=50,
|
max_length=50,
|
||||||
@ -81,9 +85,7 @@ class Notification(models.Model):
|
|||||||
verbose_name_plural = _('notifications')
|
verbose_name_plural = _('notifications')
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
if self.object:
|
return self.object_repr
|
||||||
return str(self.object)
|
|
||||||
return super().__str__()
|
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('account:notifications')
|
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)
|
_("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
|
@cached_property
|
||||||
def event(self):
|
def event(self):
|
||||||
"""
|
"""
|
||||||
@ -104,6 +113,10 @@ class Notification(models.Model):
|
|||||||
"""
|
"""
|
||||||
return registry['event_types'].get(self.event_type)
|
return registry['event_types'].get(self.event_type)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_object_repr(cls, obj):
|
||||||
|
return str(obj)[:200]
|
||||||
|
|
||||||
|
|
||||||
class NotificationGroup(ChangeLoggedModel):
|
class NotificationGroup(ChangeLoggedModel):
|
||||||
"""
|
"""
|
||||||
|
@ -137,11 +137,18 @@ def process_job_end_event_rules(sender, **kwargs):
|
|||||||
# Notifications
|
# Notifications
|
||||||
#
|
#
|
||||||
|
|
||||||
@receiver(post_save)
|
@receiver((post_save, pre_delete))
|
||||||
def notify_object_changed(sender, instance, created, raw, **kwargs):
|
def notify_object_changed(sender, instance, **kwargs):
|
||||||
if created or raw:
|
# Skip for newly-created objects
|
||||||
|
if kwargs.get('created'):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Determine event type
|
||||||
|
if 'created' in kwargs:
|
||||||
|
event_type = OBJECT_UPDATED
|
||||||
|
else:
|
||||||
|
event_type = OBJECT_DELETED
|
||||||
|
|
||||||
# Skip unsupported object types
|
# Skip unsupported object types
|
||||||
ct = ContentType.objects.get_for_model(instance)
|
ct = ContentType.objects.get_for_model(instance)
|
||||||
if ct.model not in registry['model_features']['notifications'].get(ct.app_label, []):
|
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
|
# Create Notifications for Subscribers
|
||||||
Notification.objects.bulk_create([
|
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
|
for user in subscribed_users
|
||||||
])
|
])
|
||||||
|
@ -44,6 +44,14 @@ NOTIFICATION_ICON = """
|
|||||||
<span class="text-{{ value.color }} fs-3"><i class="{{ value.icon }}"></i></span>
|
<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):
|
class CustomFieldTable(NetBoxTable):
|
||||||
name = tables.Column(
|
name = tables.Column(
|
||||||
@ -314,12 +322,9 @@ class NotificationTable(NetBoxTable):
|
|||||||
object_type = columns.ContentTypeColumn(
|
object_type = columns.ContentTypeColumn(
|
||||||
verbose_name=_('Object Type'),
|
verbose_name=_('Object Type'),
|
||||||
)
|
)
|
||||||
object = tables.Column(
|
object = columns.TemplateColumn(
|
||||||
verbose_name=_('Object'),
|
verbose_name=_('Object'),
|
||||||
linkify={
|
template_code=NOTIFICATION_LINK,
|
||||||
'viewname': 'extras:notification_read',
|
|
||||||
'args': [tables.A('pk')],
|
|
||||||
},
|
|
||||||
orderable=False
|
orderable=False
|
||||||
)
|
)
|
||||||
created = columns.DateTimeColumn(
|
created = columns.DateTimeColumn(
|
||||||
|
@ -45,10 +45,12 @@ class EventType:
|
|||||||
name: The unique name under which the event is registered.
|
name: The unique name under which the event is registered.
|
||||||
text: The human-friendly event name. This should support translation.
|
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.
|
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
|
name: str
|
||||||
text: str
|
text: str
|
||||||
kind: str = EVENT_TYPE_KIND_INFO
|
kind: str = EVENT_TYPE_KIND_INFO
|
||||||
|
destructive: bool = False
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.text
|
return self.text
|
||||||
@ -58,6 +60,7 @@ class EventType:
|
|||||||
raise Exception(f"An event type named {self.name} has already been registered!")
|
raise Exception(f"An event type named {self.name} has already been registered!")
|
||||||
registry['event_types'][self.name] = self
|
registry['event_types'][self.name] = self
|
||||||
|
|
||||||
|
@property
|
||||||
def color(self):
|
def color(self):
|
||||||
return {
|
return {
|
||||||
EVENT_TYPE_KIND_INFO: 'blue',
|
EVENT_TYPE_KIND_INFO: 'blue',
|
||||||
@ -66,6 +69,7 @@ class EventType:
|
|||||||
EVENT_TYPE_KIND_DANGER: 'red',
|
EVENT_TYPE_KIND_DANGER: 'red',
|
||||||
}.get(self.kind)
|
}.get(self.kind)
|
||||||
|
|
||||||
|
@property
|
||||||
def icon(self):
|
def icon(self):
|
||||||
return {
|
return {
|
||||||
EVENT_TYPE_KIND_INFO: 'mdi mdi-information',
|
EVENT_TYPE_KIND_INFO: 'mdi mdi-information',
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
<i class="{{ notification.event.icon }}"></i>
|
<i class="{{ notification.event.icon }}"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="col text-truncate">
|
<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 class="d-block text-secondary fs-5">{{ notification.event }} {{ notification.created|timesince }} {% trans "ago" %}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
|
Loading…
Reference in New Issue
Block a user