From 06447ac637b254e9b9c1f27ad8decbe5a7615aa6 Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Wed, 26 Nov 2025 11:48:50 -0500 Subject: [PATCH] Optimize replication of port mappings from DeviceType --- netbox/dcim/models/devices.py | 13 ++++------- netbox/dcim/tests/test_models.py | 2 ++ netbox/dcim/utils.py | 37 ++++++++++++++++++++++---------- 3 files changed, 32 insertions(+), 20 deletions(-) diff --git a/netbox/dcim/models/devices.py b/netbox/dcim/models/devices.py index 2679b7296..423265751 100644 --- a/netbox/dcim/models/devices.py +++ b/netbox/dcim/models/devices.py @@ -1,8 +1,7 @@ import decimal -import yaml - from functools import cached_property +import yaml from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ValidationError @@ -25,17 +24,15 @@ from extras.querysets import ConfigContextModelQuerySet from netbox.choices import ColorChoices from netbox.config import ConfigItem from netbox.models import NestedGroupModel, OrganizationalModel, PrimaryModel -from netbox.models.mixins import WeightMixin from netbox.models.features import ContactsMixin, ImageAttachmentsMixin +from netbox.models.mixins import WeightMixin from utilities.fields import ColorField, CounterCacheField from utilities.prefetch import get_prefetchable_fields from utilities.tracking import TrackingModelMixin -from . import PortTemplateMapping from .device_components import * from .mixins import RenderConfigMixin from .modules import Module - __all__ = ( 'Device', 'DeviceRole', @@ -1004,6 +1001,8 @@ class Device( self._instantiate_components(self.device_type.interfacetemplates.all()) self._instantiate_components(self.device_type.rearporttemplates.all()) self._instantiate_components(self.device_type.frontporttemplates.all()) + # Replicate any front/rear port mappings from the DeviceType + create_port_mappings(self, self.device_type) # Disable bulk_create to accommodate MPTT self._instantiate_components(self.device_type.modulebaytemplates.all(), bulk_create=False) self._instantiate_components(self.device_type.devicebaytemplates.all()) @@ -1011,10 +1010,6 @@ class Device( self._instantiate_components(self.device_type.inventoryitemtemplates.all(), bulk_create=False) # Interface bridges have to be set after interface instantiation update_interface_bridges(self, self.device_type.interfacetemplates.all()) - # Replicate any front/rear port mappings from the DeviceType - create_port_mappings(self, PortTemplateMapping.objects.filter( - front_port__device_type=self.device_type - )) # Update Site and Rack assignment for any child Devices devices = Device.objects.filter(parent_bay__device=self) diff --git a/netbox/dcim/tests/test_models.py b/netbox/dcim/tests/test_models.py index cd5ff0936..1f332ed21 100644 --- a/netbox/dcim/tests/test_models.py +++ b/netbox/dcim/tests/test_models.py @@ -537,6 +537,8 @@ class DeviceTestCase(TestCase): ) self.assertEqual(frontport.cf['cf1'], 'foo') + self.assertTrue(PortMapping.objects.filter(front_port=frontport, rear_port=rearport).exists()) + modulebay = ModuleBay.objects.get( device=device, name='Module Bay 1' diff --git a/netbox/dcim/utils.py b/netbox/dcim/utils.py index 625003843..f5b94829e 100644 --- a/netbox/dcim/utils.py +++ b/netbox/dcim/utils.py @@ -85,20 +85,35 @@ def update_interface_bridges(device, interface_templates, module=None): interface.save() -def create_port_mappings(device, templates, module=None): +def create_port_mappings(device, device_type, module=None): """ Replicate all front/rear port mappings from a DeviceType to the given device. """ - from dcim.models.device_components import FrontPort, PortMapping, RearPort + from dcim.models import FrontPort, PortMapping, PortTemplateMapping, RearPort + templates = PortTemplateMapping.objects.filter( + front_port__device_type=device_type + ).prefetch_related('front_port', 'rear_port') + + # Cache front & rear ports for efficient lookups by name + front_ports = { + fp.name: fp for fp in FrontPort.objects.filter(device=device) + } + rear_ports = { + rp.name: rp for rp in RearPort.objects.filter(device=device) + } + + # Replicate PortMappings + mappings = [] for template in templates: - front_port = FrontPort.objects.get(device=device, name=template.front_port.resolve_name(module=module)) - rear_port = RearPort.objects.get(device=device, name=template.rear_port.resolve_name(module=module)) - - assignment = PortMapping( - front_port=front_port, - front_port_position=template.front_port_position, - rear_port=rear_port, - rear_port_position=template.rear_port_position, + front_port = front_ports.get(template.front_port.resolve_name(module=module)) + rear_port = rear_ports.get(template.rear_port.resolve_name(module=module)) + mappings.append( + PortMapping( + front_port=front_port, + front_port_position=template.front_port_position, + rear_port=rear_port, + rear_port_position=template.rear_port_position, + ) ) - assignment.save() + PortMapping.objects.bulk_create(mappings)