From a57c937aaa565222c21ae8629103070bd5f43c45 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Mon, 14 Nov 2022 13:46:32 -0500 Subject: [PATCH] #10694: Emit post_save signal when creating/updating device components in bulk (#10900) * Emit post_save signal when creating/updating device components in bulk * Fix post_save for bulk_update() --- netbox/dcim/models/devices.py | 97 +++++++++++++++++++++++------------ 1 file changed, 64 insertions(+), 33 deletions(-) diff --git a/netbox/dcim/models/devices.py b/netbox/dcim/models/devices.py index b71a185b7..c89dd67ef 100644 --- a/netbox/dcim/models/devices.py +++ b/netbox/dcim/models/devices.py @@ -10,6 +10,7 @@ from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models from django.db.models import F, ProtectedError from django.db.models.functions import Lower +from django.db.models.signals import post_save from django.urls import reverse from django.utils.safestring import mark_safe from django.utils.translation import gettext as _ @@ -769,6 +770,32 @@ class Device(PrimaryModel, ConfigContextModel): 'vc_position': "A device assigned to a virtual chassis must have its position defined." }) + def _instantiate_components(self, queryset, bulk_create=True): + """ + Instantiate components for the device from the specified component templates. + + Args: + bulk_create: If True, bulk_create() will be called to create all components in a single query + (default). Otherwise, save() will be called on each instance individually. + """ + components = [obj.instantiate(device=self) for obj in queryset] + if components and bulk_create: + model = components[0]._meta.model + model.objects.bulk_create(components) + # Manually send the post_save signal for each of the newly created components + for component in components: + post_save.send( + sender=model, + instance=component, + created=True, + raw=False, + using='default', + update_fields=None + ) + elif components: + for component in components: + component.save() + def save(self, *args, **kwargs): is_new = not bool(self.pk) @@ -778,38 +805,19 @@ class Device(PrimaryModel, ConfigContextModel): super().save(*args, **kwargs) - # If this is a new Device, instantiate all of the related components per the DeviceType definition + # If this is a new Device, instantiate all the related components per the DeviceType definition if is_new: - ConsolePort.objects.bulk_create( - [x.instantiate(device=self) for x in self.device_type.consoleporttemplates.all()] - ) - ConsoleServerPort.objects.bulk_create( - [x.instantiate(device=self) for x in self.device_type.consoleserverporttemplates.all()] - ) - PowerPort.objects.bulk_create( - [x.instantiate(device=self) for x in self.device_type.powerporttemplates.all()] - ) - PowerOutlet.objects.bulk_create( - [x.instantiate(device=self) for x in self.device_type.poweroutlettemplates.all()] - ) - Interface.objects.bulk_create( - [x.instantiate(device=self) for x in self.device_type.interfacetemplates.all()] - ) - RearPort.objects.bulk_create( - [x.instantiate(device=self) for x in self.device_type.rearporttemplates.all()] - ) - FrontPort.objects.bulk_create( - [x.instantiate(device=self) for x in self.device_type.frontporttemplates.all()] - ) - ModuleBay.objects.bulk_create( - [x.instantiate(device=self) for x in self.device_type.modulebaytemplates.all()] - ) - DeviceBay.objects.bulk_create( - [x.instantiate(device=self) for x in self.device_type.devicebaytemplates.all()] - ) - # Avoid bulk_create to handle MPTT - for x in self.device_type.inventoryitemtemplates.all(): - x.instantiate(device=self).save() + self._instantiate_components(self.device_type.consoleporttemplates.all()) + self._instantiate_components(self.device_type.consoleserverporttemplates.all()) + self._instantiate_components(self.device_type.powerporttemplates.all()) + self._instantiate_components(self.device_type.poweroutlettemplates.all()) + self._instantiate_components(self.device_type.interfacetemplates.all()) + self._instantiate_components(self.device_type.rearporttemplates.all()) + self._instantiate_components(self.device_type.frontporttemplates.all()) + self._instantiate_components(self.device_type.modulebaytemplates.all()) + self._instantiate_components(self.device_type.devicebaytemplates.all()) + # Disable bulk_create to accommodate MPTT + self._instantiate_components(self.device_type.inventoryitemtemplates.all(), bulk_create=False) # Update Site and Rack assignment for any child Devices devices = Device.objects.filter(parent_bay__device=self) @@ -980,7 +988,8 @@ class Module(PrimaryModel, ConfigContextModel): # Prefetch installed components installed_components = { - component.name: component for component in getattr(self.device, component_attribute).filter(module__isnull=True) + component.name: component + for component in getattr(self.device, component_attribute).filter(module__isnull=True) } # Get the template for the module type. @@ -1002,7 +1011,29 @@ class Module(PrimaryModel, ConfigContextModel): create_instances.append(template_instance) component_model.objects.bulk_create(create_instances) - component_model.objects.bulk_update(update_instances, ['module']) + # Emit the post_save signal for each newly created object + for component in create_instances: + post_save.send( + sender=component_model, + instance=component, + created=True, + raw=False, + using='default', + update_fields=None + ) + + update_fields = ['module'] + component_model.objects.bulk_update(update_instances, update_fields) + # Emit the post_save signal for each updated object + for component in update_instances: + post_save.send( + sender=component_model, + instance=component, + created=False, + raw=False, + using='default', + update_fields=update_fields + ) #