signals.py 5.2 KB

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