signals.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. from django.contrib.contenttypes.models import ContentType
  2. from django.db.models.signals import m2m_changed, post_save, pre_delete
  3. from django.dispatch import receiver
  4. from core.events import *
  5. from core.signals import job_end, job_start
  6. from extras.events import process_event_rules
  7. from extras.models import EventRule, Notification, Subscription
  8. from netbox.config import get_config
  9. from netbox.models.features import has_feature
  10. from netbox.signals import post_clean
  11. from utilities.exceptions import AbortRequest
  12. from .models import CustomField, TaggedItem
  13. from .utils import run_validators
  14. #
  15. # Custom fields
  16. #
  17. def handle_cf_added_obj_types(instance, action, pk_set, **kwargs):
  18. """
  19. Handle the population of default/null values when a CustomField is added to one or more ContentTypes.
  20. """
  21. if action == 'post_add':
  22. instance.populate_initial_data(ContentType.objects.filter(pk__in=pk_set))
  23. def handle_cf_removed_obj_types(instance, action, pk_set, **kwargs):
  24. """
  25. Handle the cleanup of old custom field data when a CustomField is removed from one or more ContentTypes.
  26. """
  27. if action == 'post_remove':
  28. instance.remove_stale_data(ContentType.objects.filter(pk__in=pk_set))
  29. def handle_cf_renamed(instance, created, **kwargs):
  30. """
  31. Handle the renaming of custom field data on objects when a CustomField is renamed.
  32. """
  33. if not created and instance.name != instance._name:
  34. instance.rename_object_data(old_name=instance._name, new_name=instance.name)
  35. def handle_cf_deleted(instance, **kwargs):
  36. """
  37. Handle the cleanup of old custom field data when a CustomField is deleted.
  38. """
  39. instance.remove_stale_data(instance.object_types.all())
  40. post_save.connect(handle_cf_renamed, sender=CustomField)
  41. pre_delete.connect(handle_cf_deleted, sender=CustomField)
  42. m2m_changed.connect(handle_cf_added_obj_types, sender=CustomField.object_types.through)
  43. m2m_changed.connect(handle_cf_removed_obj_types, sender=CustomField.object_types.through)
  44. #
  45. # Custom validation
  46. #
  47. @receiver(post_clean)
  48. def run_save_validators(sender, instance, **kwargs):
  49. """
  50. Run any custom validation rules for the model prior to calling save().
  51. """
  52. model_name = f'{sender._meta.app_label}.{sender._meta.model_name}'
  53. validators = get_config().CUSTOM_VALIDATORS.get(model_name, [])
  54. run_validators(instance, validators)
  55. #
  56. # Tags
  57. #
  58. @receiver(m2m_changed, sender=TaggedItem)
  59. def validate_assigned_tags(sender, instance, action, model, pk_set, **kwargs):
  60. """
  61. Validate that any Tags being assigned to the instance are not restricted to non-applicable object types.
  62. """
  63. if action != 'pre_add':
  64. return
  65. ct = ContentType.objects.get_for_model(instance)
  66. # Retrieve any applied Tags that are restricted to certain object types
  67. for tag in model.objects.filter(pk__in=pk_set, object_types__isnull=False).prefetch_related('object_types'):
  68. if ct not in tag.object_types.all():
  69. raise AbortRequest(f"Tag {tag} cannot be assigned to {ct.model} objects.")
  70. #
  71. # Event rules
  72. #
  73. @receiver(job_start)
  74. def process_job_start_event_rules(sender, **kwargs):
  75. """
  76. Process event rules for jobs starting.
  77. """
  78. event_rules = EventRule.objects.filter(
  79. event_types__contains=[JOB_STARTED],
  80. enabled=True,
  81. object_types=sender.object_type
  82. )
  83. username = sender.user.username if sender.user else None
  84. process_event_rules(
  85. event_rules=event_rules,
  86. object_type=sender.object_type,
  87. event_type=JOB_STARTED,
  88. data=sender.data,
  89. username=username
  90. )
  91. @receiver(job_end)
  92. def process_job_end_event_rules(sender, **kwargs):
  93. """
  94. Process event rules for jobs terminating.
  95. """
  96. event_rules = EventRule.objects.filter(
  97. event_types__contains=[JOB_COMPLETED],
  98. enabled=True,
  99. object_types=sender.object_type
  100. )
  101. username = sender.user.username if sender.user else None
  102. process_event_rules(
  103. event_rules=event_rules,
  104. object_type=sender.object_type,
  105. event_type=JOB_COMPLETED,
  106. data=sender.data,
  107. username=username
  108. )
  109. #
  110. # Notifications
  111. #
  112. @receiver((post_save, pre_delete))
  113. def notify_object_changed(sender, instance, **kwargs):
  114. # Skip for newly-created objects
  115. if kwargs.get('created'):
  116. return
  117. # Determine event type
  118. if 'created' in kwargs:
  119. event_type = OBJECT_UPDATED
  120. else:
  121. event_type = OBJECT_DELETED
  122. # Skip unsupported object types
  123. if not has_feature(instance, 'notifications'):
  124. return
  125. ct = ContentType.objects.get_for_model(instance)
  126. # Find all subscribed Users
  127. subscribed_users = Subscription.objects.filter(
  128. object_type=ct,
  129. object_id=instance.pk
  130. ).values_list('user', flat=True)
  131. if not subscribed_users:
  132. return
  133. # Delete any existing Notifications for the object
  134. Notification.objects.filter(
  135. object_type=ct,
  136. object_id=instance.pk,
  137. user__in=subscribed_users
  138. ).delete()
  139. # Create Notifications for Subscribers
  140. Notification.objects.bulk_create([
  141. Notification(
  142. user_id=user,
  143. object=instance,
  144. object_repr=Notification.get_object_repr(instance),
  145. event_type=event_type
  146. )
  147. for user in subscribed_users
  148. ])