| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244 |
- from functools import cached_property
- from django.conf import settings
- from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
- from django.core.exceptions import ValidationError
- from django.db import models
- from django.urls import reverse
- from django.utils.translation import gettext_lazy as _
- from extras.querysets import NotificationQuerySet
- from netbox.models import ChangeLoggedModel
- from netbox.models.features import has_feature
- from netbox.registry import registry
- from users.models import User
- from utilities.querysets import RestrictedQuerySet
- __all__ = (
- 'Notification',
- 'NotificationGroup',
- 'Subscription',
- )
- def get_event_type_choices():
- """
- Compile a list of choices from all registered event types
- """
- return [
- (name, event.text)
- for name, event in registry['event_types'].items()
- ]
- class Notification(models.Model):
- """
- A notification message for a User relating to a specific object in NetBox.
- """
- created = models.DateTimeField(
- verbose_name=_('created'),
- auto_now_add=True
- )
- read = models.DateTimeField(
- verbose_name=_('read'),
- null=True,
- blank=True
- )
- user = models.ForeignKey(
- to=settings.AUTH_USER_MODEL,
- on_delete=models.CASCADE,
- related_name='notifications'
- )
- object_type = models.ForeignKey(
- to='contenttypes.ContentType',
- on_delete=models.PROTECT
- )
- object_id = models.PositiveBigIntegerField()
- object = GenericForeignKey(
- 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,
- choices=get_event_type_choices
- )
- objects = NotificationQuerySet.as_manager()
- class Meta:
- ordering = ('-created', 'pk')
- indexes = (
- models.Index(fields=('object_type', 'object_id')),
- )
- constraints = (
- models.UniqueConstraint(
- fields=('object_type', 'object_id', 'user'),
- name='%(app_label)s_%(class)s_unique_per_object_and_user'
- ),
- )
- verbose_name = _('notification')
- verbose_name_plural = _('notifications')
- def __str__(self):
- return self.object_repr
- def get_absolute_url(self):
- return reverse('account:notifications')
- def clean(self):
- super().clean()
- # Validate the assigned object type
- if not has_feature(self.object_type, 'notifications'):
- raise ValidationError(
- _("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):
- """
- Returns the registered Event which triggered this Notification.
- """
- return registry['event_types'].get(self.event_type)
- @classmethod
- def get_object_repr(cls, obj):
- return str(obj)[:200]
- class NotificationGroup(ChangeLoggedModel):
- """
- A collection of users and/or groups to be informed for certain notifications.
- """
- name = models.CharField(
- verbose_name=_('name'),
- max_length=100,
- unique=True
- )
- description = models.CharField(
- verbose_name=_('description'),
- max_length=200,
- blank=True
- )
- groups = models.ManyToManyField(
- to='users.Group',
- verbose_name=_('groups'),
- blank=True,
- related_name='notification_groups'
- )
- users = models.ManyToManyField(
- to='users.User',
- verbose_name=_('users'),
- blank=True,
- related_name='notification_groups'
- )
- event_rules = GenericRelation(
- to='extras.EventRule',
- content_type_field='action_object_type',
- object_id_field='action_object_id',
- related_query_name='+'
- )
- objects = RestrictedQuerySet.as_manager()
- class Meta:
- ordering = ('name',)
- verbose_name = _('notification group')
- verbose_name_plural = _('notification groups')
- def __str__(self):
- return self.name
- def get_absolute_url(self):
- return reverse('extras:notificationgroup', args=[self.pk])
- @cached_property
- def members(self):
- """
- Return all Users who belong to this notification group.
- """
- return self.users.union(
- User.objects.filter(groups__in=self.groups.all())
- ).order_by('username')
- def notify(self, object_type, object_id, **kwargs):
- """
- Bulk-create Notifications for all members of this group.
- """
- for user in self.members:
- Notification.objects.update_or_create(
- object_type=object_type,
- object_id=object_id,
- user=user,
- defaults=kwargs
- )
- notify.alters_data = True
- class Subscription(models.Model):
- """
- A User's subscription to a particular object, to be notified of changes.
- """
- created = models.DateTimeField(
- verbose_name=_('created'),
- auto_now_add=True
- )
- user = models.ForeignKey(
- to=settings.AUTH_USER_MODEL,
- on_delete=models.CASCADE,
- related_name='subscriptions'
- )
- object_type = models.ForeignKey(
- to='contenttypes.ContentType',
- on_delete=models.PROTECT
- )
- object_id = models.PositiveBigIntegerField()
- object = GenericForeignKey(
- ct_field='object_type',
- fk_field='object_id'
- )
- objects = RestrictedQuerySet.as_manager()
- class Meta:
- ordering = ('-created', 'user')
- indexes = (
- models.Index(fields=('object_type', 'object_id')),
- )
- constraints = (
- models.UniqueConstraint(
- fields=('object_type', 'object_id', 'user'),
- name='%(app_label)s_%(class)s_unique_per_object_and_user'
- ),
- )
- verbose_name = _('subscription')
- verbose_name_plural = _('subscriptions')
- def __str__(self):
- if self.object:
- return str(self.object)
- return super().__str__()
- def get_absolute_url(self):
- return reverse('account:subscriptions')
- def clean(self):
- super().clean()
- # Validate the assigned object type
- if not has_feature(self.object_type, 'notifications'):
- raise ValidationError(
- _("Objects of this type ({type}) do not support notifications.").format(type=self.object_type)
- )
|