mirror of
https://github.com/netbox-community/netbox.git
synced 2025-12-20 20:32:25 -06:00
* Initial work on #15621 * Signal receiver should ignore models which don't support notifications * Flesh out NotificationGroup functionality * Add NotificationGroup filters for users & groups * Separate read & dimiss actions * Enable one-click dismissals from notifications list * Include total notification count in dropdown * Drop 'kind' field from Notification model * Register event types in the registry; add colors & icons * Enable event rules to target notification groups * Define dynamic choices for Notification.event_name * Move event registration to core * Add more job events * Misc cleanup * Misc cleanup * Correct absolute URLs for notifications & subscriptions * Optimize subscriber notifications * Use core event types when queuing events * Standardize queued event attribute to event_type; change content_type to object_type * Rename Notification.event_name to event_type * Restore NotificationGroupBulkEditView * Add API tests * Add view & filterset tests * Add model documentation * Fix tests * Update notification bell when notifications have been cleared * Ensure subscribe button appears only on relevant models * Notifications/subscriptions cannot be ordered by object * Misc cleanup * Add event icon & type to notifications table * Adjust icon sizing * Mute color of read notifications * Misc cleanup
This commit is contained in:
@@ -8,7 +8,7 @@ from django.utils.module_loading import import_string
|
||||
from django.utils.translation import gettext as _
|
||||
from django_rq import get_queue
|
||||
|
||||
from core.choices import ObjectChangeActionChoices
|
||||
from core.events import *
|
||||
from core.models import Job
|
||||
from netbox.config import get_config
|
||||
from netbox.constants import RQ_QUEUE_DEFAULT
|
||||
@@ -35,12 +35,12 @@ def serialize_for_event(instance):
|
||||
return serializer.data
|
||||
|
||||
|
||||
def get_snapshots(instance, action):
|
||||
def get_snapshots(instance, event_type):
|
||||
snapshots = {
|
||||
'prechange': getattr(instance, '_prechange_snapshot', None),
|
||||
'postchange': None,
|
||||
}
|
||||
if action != ObjectChangeActionChoices.ACTION_DELETE:
|
||||
if event_type != OBJECT_DELETED:
|
||||
# Use model's serialize_object() method if defined; fall back to serialize_object() utility function
|
||||
if hasattr(instance, 'serialize_object'):
|
||||
snapshots['postchange'] = instance.serialize_object()
|
||||
@@ -50,7 +50,7 @@ def get_snapshots(instance, action):
|
||||
return snapshots
|
||||
|
||||
|
||||
def enqueue_object(queue, instance, user, request_id, action):
|
||||
def enqueue_event(queue, instance, user, request_id, event_type):
|
||||
"""
|
||||
Enqueue a serialized representation of a created/updated/deleted object for the processing of
|
||||
events once the request has completed.
|
||||
@@ -65,27 +65,24 @@ def enqueue_object(queue, instance, user, request_id, action):
|
||||
key = f'{app_label}.{model_name}:{instance.pk}'
|
||||
if key in queue:
|
||||
queue[key]['data'] = serialize_for_event(instance)
|
||||
queue[key]['snapshots']['postchange'] = get_snapshots(instance, action)['postchange']
|
||||
queue[key]['snapshots']['postchange'] = get_snapshots(instance, event_type)['postchange']
|
||||
# If the object is being deleted, update any prior "update" event to "delete"
|
||||
if action == ObjectChangeActionChoices.ACTION_DELETE:
|
||||
queue[key]['event'] = action
|
||||
if event_type == OBJECT_DELETED:
|
||||
queue[key]['event_type'] = event_type
|
||||
else:
|
||||
queue[key] = {
|
||||
'content_type': ContentType.objects.get_for_model(instance),
|
||||
'object_type': ContentType.objects.get_for_model(instance),
|
||||
'object_id': instance.pk,
|
||||
'event': action,
|
||||
'event_type': event_type,
|
||||
'data': serialize_for_event(instance),
|
||||
'snapshots': get_snapshots(instance, action),
|
||||
'snapshots': get_snapshots(instance, event_type),
|
||||
'username': user.username,
|
||||
'request_id': request_id
|
||||
}
|
||||
|
||||
|
||||
def process_event_rules(event_rules, model_name, event, data, username=None, snapshots=None, request_id=None):
|
||||
if username:
|
||||
user = get_user_model().objects.get(username=username)
|
||||
else:
|
||||
user = None
|
||||
def process_event_rules(event_rules, object_type, event_type, data, username=None, snapshots=None, request_id=None):
|
||||
user = get_user_model().objects.get(username=username) if username else None
|
||||
|
||||
for event_rule in event_rules:
|
||||
|
||||
@@ -103,8 +100,8 @@ def process_event_rules(event_rules, model_name, event, data, username=None, sna
|
||||
# Compile the task parameters
|
||||
params = {
|
||||
"event_rule": event_rule,
|
||||
"model_name": model_name,
|
||||
"event": event,
|
||||
"model_name": object_type.model,
|
||||
"event_type": event_type,
|
||||
"data": data,
|
||||
"snapshots": snapshots,
|
||||
"timestamp": timezone.now().isoformat(),
|
||||
@@ -136,6 +133,15 @@ def process_event_rules(event_rules, model_name, event, data, username=None, sna
|
||||
data=data
|
||||
)
|
||||
|
||||
# Notification groups
|
||||
elif event_rule.action_type == EventRuleActionChoices.NOTIFICATION:
|
||||
# Bulk-create notifications for all members of the notification group
|
||||
event_rule.action_object.notify(
|
||||
object_type=object_type,
|
||||
object_id=data['id'],
|
||||
event_type=event_type
|
||||
)
|
||||
|
||||
else:
|
||||
raise ValueError(_("Unknown action type for an event rule: {action_type}").format(
|
||||
action_type=event_rule.action_type
|
||||
@@ -151,27 +157,39 @@ def process_event_queue(events):
|
||||
'type_update': {},
|
||||
'type_delete': {},
|
||||
}
|
||||
event_actions = {
|
||||
# TODO: Add EventRule support for dynamically registered event types
|
||||
OBJECT_CREATED: 'type_create',
|
||||
OBJECT_UPDATED: 'type_update',
|
||||
OBJECT_DELETED: 'type_delete',
|
||||
JOB_STARTED: 'type_job_start',
|
||||
JOB_COMPLETED: 'type_job_end',
|
||||
# Map failed & errored jobs to type_job_end
|
||||
JOB_FAILED: 'type_job_end',
|
||||
JOB_ERRORED: 'type_job_end',
|
||||
}
|
||||
|
||||
for data in events:
|
||||
action_flag = {
|
||||
ObjectChangeActionChoices.ACTION_CREATE: 'type_create',
|
||||
ObjectChangeActionChoices.ACTION_UPDATE: 'type_update',
|
||||
ObjectChangeActionChoices.ACTION_DELETE: 'type_delete',
|
||||
}[data['event']]
|
||||
content_type = data['content_type']
|
||||
for event in events:
|
||||
action_flag = event_actions[event['event_type']]
|
||||
object_type = event['object_type']
|
||||
|
||||
# Cache applicable Event Rules
|
||||
if content_type not in events_cache[action_flag]:
|
||||
events_cache[action_flag][content_type] = EventRule.objects.filter(
|
||||
if object_type not in events_cache[action_flag]:
|
||||
events_cache[action_flag][object_type] = EventRule.objects.filter(
|
||||
**{action_flag: True},
|
||||
object_types=content_type,
|
||||
object_types=object_type,
|
||||
enabled=True
|
||||
)
|
||||
event_rules = events_cache[action_flag][content_type]
|
||||
event_rules = events_cache[action_flag][object_type]
|
||||
|
||||
process_event_rules(
|
||||
event_rules, content_type.model, data['event'], data['data'], data['username'],
|
||||
snapshots=data['snapshots'], request_id=data['request_id']
|
||||
event_rules=event_rules,
|
||||
object_type=object_type,
|
||||
event_type=event['event_type'],
|
||||
data=event['data'],
|
||||
username=event['username'],
|
||||
snapshots=event['snapshots'],
|
||||
request_id=event['request_id']
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user