signals.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  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 EventContext, 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. event = EventContext(
  84. event_type=JOB_STARTED,
  85. data=sender.data,
  86. user=sender.user,
  87. )
  88. process_event_rules(event_rules, sender.object_type, event)
  89. @receiver(job_end)
  90. def process_job_end_event_rules(sender, **kwargs):
  91. """
  92. Process event rules for jobs terminating.
  93. """
  94. event_rules = EventRule.objects.filter(
  95. event_types__contains=[JOB_COMPLETED],
  96. enabled=True,
  97. object_types=sender.object_type
  98. )
  99. event = EventContext(
  100. event_type=JOB_COMPLETED,
  101. data=sender.data,
  102. user=sender.user,
  103. )
  104. process_event_rules(event_rules, sender.object_type, event)
  105. #
  106. # Notifications
  107. #
  108. @receiver((post_save, pre_delete))
  109. def notify_object_changed(sender, instance, **kwargs):
  110. # Skip for newly-created objects
  111. if kwargs.get('created'):
  112. return
  113. # Determine event type
  114. if 'created' in kwargs:
  115. event_type = OBJECT_UPDATED
  116. else:
  117. event_type = OBJECT_DELETED
  118. # Skip unsupported object types
  119. if not has_feature(instance, 'notifications'):
  120. return
  121. ct = ContentType.objects.get_for_model(instance)
  122. # Find all subscribed Users
  123. subscribed_users = Subscription.objects.filter(
  124. object_type=ct,
  125. object_id=instance.pk
  126. ).values_list('user', flat=True)
  127. if not subscribed_users:
  128. return
  129. # Delete any existing Notifications for the object
  130. Notification.objects.filter(
  131. object_type=ct,
  132. object_id=instance.pk,
  133. user__in=subscribed_users
  134. ).delete()
  135. # Create Notifications for Subscribers
  136. Notification.objects.bulk_create([
  137. Notification(
  138. user_id=user,
  139. object=instance,
  140. object_repr=Notification.get_object_repr(instance),
  141. event_type=event_type
  142. )
  143. for user in subscribed_users
  144. ])