diff --git a/docs/additional-features/reports.md b/docs/additional-features/reports.md index 33c3d95ae..4b8b77840 100644 --- a/docs/additional-features/reports.md +++ b/docs/additional-features/reports.md @@ -43,7 +43,7 @@ class DeviceConnectionsReport(Report): def test_console_connection(self): # Check that every console port for every active device has a connection defined. - for console_port in ConsolePort.objects.select_related('device').filter(device__status=DEVICE_STATUS_ACTIVE): + for console_port in ConsolePort.objects.prefetch_related('device').filter(device__status=DEVICE_STATUS_ACTIVE): if console_port.connected_endpoint is None: self.log_failure( console_port.device, diff --git a/docs/development/extending-models.md b/docs/development/extending-models.md index 1fde8067b..0070c5545 100644 --- a/docs/development/extending-models.md +++ b/docs/development/extending-models.md @@ -38,7 +38,7 @@ Add the name of the new field to `csv_headers` and included a CSV-friendly repre ### 4. Update relevant querysets -If you're adding a relational field (e.g. `ForeignKey`) and intend to include the data when retreiving a list of objects, be sure to include the field using `select_related()` or `prefetch_related()` as appropriate. This will optimize the view and avoid excessive database lookups. +If you're adding a relational field (e.g. `ForeignKey`) and intend to include the data when retreiving a list of objects, be sure to include the field using `prefetch_related()` as appropriate. This will optimize the view and avoid excessive database lookups. ### 5. Update API serializer diff --git a/netbox/circuits/api/views.py b/netbox/circuits/api/views.py index ad48174e6..dc1133c5e 100644 --- a/netbox/circuits/api/views.py +++ b/netbox/circuits/api/views.py @@ -62,7 +62,7 @@ class CircuitTypeViewSet(ModelViewSet): # class CircuitViewSet(CustomFieldModelViewSet): - queryset = Circuit.objects.select_related('type', 'tenant', 'provider').prefetch_related('tags') + queryset = Circuit.objects.prefetch_related('type', 'tenant', 'provider').prefetch_related('tags') serializer_class = serializers.CircuitSerializer filterset_class = filters.CircuitFilter @@ -72,7 +72,7 @@ class CircuitViewSet(CustomFieldModelViewSet): # class CircuitTerminationViewSet(ModelViewSet): - queryset = CircuitTermination.objects.select_related( + queryset = CircuitTermination.objects.prefetch_related( 'circuit', 'site', 'connected_endpoint__device', 'cable' ) serializer_class = serializers.CircuitTerminationSerializer diff --git a/netbox/circuits/models.py b/netbox/circuits/models.py index 0297790b5..b70a2969c 100644 --- a/netbox/circuits/models.py +++ b/netbox/circuits/models.py @@ -295,6 +295,6 @@ class CircuitTermination(CableTermination): def get_peer_termination(self): peer_side = 'Z' if self.term_side == 'A' else 'A' try: - return CircuitTermination.objects.select_related('site').get(circuit=self.circuit, term_side=peer_side) + return CircuitTermination.objects.prefetch_related('site').get(circuit=self.circuit, term_side=peer_side) except CircuitTermination.DoesNotExist: return None diff --git a/netbox/circuits/signals.py b/netbox/circuits/signals.py index bdfe8c0b6..cfe9d65e4 100644 --- a/netbox/circuits/signals.py +++ b/netbox/circuits/signals.py @@ -10,4 +10,4 @@ def update_circuit(instance, **kwargs): """ When a CircuitTermination has been modified, update the last_updated time of its parent Circuit. """ - Circuit.objects.filter(pk=instance.circuit_id).update(last_updated=timezone.now()) + Circuit.objects.filter(pk=instance.circuit_id).invalidated_update(last_updated=timezone.now()) diff --git a/netbox/circuits/views.py b/netbox/circuits/views.py index 2f3881818..655b714d7 100644 --- a/netbox/circuits/views.py +++ b/netbox/circuits/views.py @@ -35,11 +35,7 @@ class ProviderView(PermissionRequiredMixin, View): def get(self, request, slug): provider = get_object_or_404(Provider, slug=slug) - circuits = Circuit.objects.filter(provider=provider).select_related( - 'type', 'tenant' - ).prefetch_related( - 'terminations__site' - ) + circuits = Circuit.objects.filter(provider=provider).prefetch_related('type', 'tenant', 'terminations__site') show_graphs = Graph.objects.filter(type=GRAPH_TYPE_PROVIDER).exists() return render(request, 'circuits/provider.html', { @@ -134,10 +130,8 @@ class CircuitTypeBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): class CircuitListView(PermissionRequiredMixin, ObjectListView): permission_required = 'circuits.view_circuit' _terminations = CircuitTermination.objects.filter(circuit=OuterRef('pk')) - queryset = Circuit.objects.select_related( - 'provider', 'type', 'tenant' - ).prefetch_related( - 'terminations__site' + queryset = Circuit.objects.prefetch_related( + 'provider', 'type', 'tenant', 'terminations__site' ).annotate( a_side=Subquery(_terminations.filter(term_side='A').values('site__name')[:1]), z_side=Subquery(_terminations.filter(term_side='Z').values('site__name')[:1]), @@ -153,13 +147,13 @@ class CircuitView(PermissionRequiredMixin, View): def get(self, request, pk): - circuit = get_object_or_404(Circuit.objects.select_related('provider', 'type', 'tenant__group'), pk=pk) - termination_a = CircuitTermination.objects.select_related( + circuit = get_object_or_404(Circuit.objects.prefetch_related('provider', 'type', 'tenant__group'), pk=pk) + termination_a = CircuitTermination.objects.prefetch_related( 'site__region', 'connected_endpoint__device' ).filter( circuit=circuit, term_side=TERM_SIDE_A ).first() - termination_z = CircuitTermination.objects.select_related( + termination_z = CircuitTermination.objects.prefetch_related( 'site__region', 'connected_endpoint__device' ).filter( circuit=circuit, term_side=TERM_SIDE_Z @@ -199,7 +193,7 @@ class CircuitBulkImportView(PermissionRequiredMixin, BulkImportView): class CircuitBulkEditView(PermissionRequiredMixin, BulkEditView): permission_required = 'circuits.change_circuit' - queryset = Circuit.objects.select_related('provider', 'type', 'tenant').prefetch_related('terminations__site') + queryset = Circuit.objects.prefetch_related('provider', 'type', 'tenant').prefetch_related('terminations__site') filter = filters.CircuitFilter table = tables.CircuitTable form = forms.CircuitBulkEditForm @@ -208,7 +202,7 @@ class CircuitBulkEditView(PermissionRequiredMixin, BulkEditView): class CircuitBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'circuits.delete_circuit' - queryset = Circuit.objects.select_related('provider', 'type', 'tenant').prefetch_related('terminations__site') + queryset = Circuit.objects.prefetch_related('provider', 'type', 'tenant').prefetch_related('terminations__site') filter = filters.CircuitFilter table = tables.CircuitTable default_return_url = 'circuits:circuit_list' diff --git a/netbox/dcim/api/views.py b/netbox/dcim/api/views.py index af5ccae4a..4ddee7337 100644 --- a/netbox/dcim/api/views.py +++ b/netbox/dcim/api/views.py @@ -109,10 +109,8 @@ class RegionViewSet(ModelViewSet): # class SiteViewSet(CustomFieldModelViewSet): - queryset = Site.objects.select_related( - 'region', 'tenant' - ).prefetch_related( - 'tags' + queryset = Site.objects.prefetch_related( + 'region', 'tenant', 'tags' ).annotate( device_count=get_subquery(Device, 'site'), rack_count=get_subquery(Rack, 'site'), @@ -140,7 +138,7 @@ class SiteViewSet(CustomFieldModelViewSet): # class RackGroupViewSet(ModelViewSet): - queryset = RackGroup.objects.select_related('site').annotate( + queryset = RackGroup.objects.prefetch_related('site').annotate( rack_count=Count('racks') ) serializer_class = serializers.RackGroupSerializer @@ -164,10 +162,8 @@ class RackRoleViewSet(ModelViewSet): # class RackViewSet(CustomFieldModelViewSet): - queryset = Rack.objects.select_related( - 'site', 'group__site', 'role', 'tenant' - ).prefetch_related( - 'tags' + queryset = Rack.objects.prefetch_related( + 'site', 'group__site', 'role', 'tenant', 'tags' ).annotate( device_count=get_subquery(Device, 'rack'), powerfeed_count=get_subquery(PowerFeed, 'rack') @@ -206,7 +202,7 @@ class RackViewSet(CustomFieldModelViewSet): # class RackReservationViewSet(ModelViewSet): - queryset = RackReservation.objects.select_related('rack', 'user', 'tenant') + queryset = RackReservation.objects.prefetch_related('rack', 'user', 'tenant') serializer_class = serializers.RackReservationSerializer filterset_class = filters.RackReservationFilter @@ -234,7 +230,7 @@ class ManufacturerViewSet(ModelViewSet): # class DeviceTypeViewSet(CustomFieldModelViewSet): - queryset = DeviceType.objects.select_related('manufacturer').prefetch_related('tags').annotate( + queryset = DeviceType.objects.prefetch_related('manufacturer').prefetch_related('tags').annotate( device_count=Count('instances') ) serializer_class = serializers.DeviceTypeSerializer @@ -246,49 +242,49 @@ class DeviceTypeViewSet(CustomFieldModelViewSet): # class ConsolePortTemplateViewSet(ModelViewSet): - queryset = ConsolePortTemplate.objects.select_related('device_type__manufacturer') + queryset = ConsolePortTemplate.objects.prefetch_related('device_type__manufacturer') serializer_class = serializers.ConsolePortTemplateSerializer filterset_class = filters.ConsolePortTemplateFilter class ConsoleServerPortTemplateViewSet(ModelViewSet): - queryset = ConsoleServerPortTemplate.objects.select_related('device_type__manufacturer') + queryset = ConsoleServerPortTemplate.objects.prefetch_related('device_type__manufacturer') serializer_class = serializers.ConsoleServerPortTemplateSerializer filterset_class = filters.ConsoleServerPortTemplateFilter class PowerPortTemplateViewSet(ModelViewSet): - queryset = PowerPortTemplate.objects.select_related('device_type__manufacturer') + queryset = PowerPortTemplate.objects.prefetch_related('device_type__manufacturer') serializer_class = serializers.PowerPortTemplateSerializer filterset_class = filters.PowerPortTemplateFilter class PowerOutletTemplateViewSet(ModelViewSet): - queryset = PowerOutletTemplate.objects.select_related('device_type__manufacturer') + queryset = PowerOutletTemplate.objects.prefetch_related('device_type__manufacturer') serializer_class = serializers.PowerOutletTemplateSerializer filterset_class = filters.PowerOutletTemplateFilter class InterfaceTemplateViewSet(ModelViewSet): - queryset = InterfaceTemplate.objects.select_related('device_type__manufacturer') + queryset = InterfaceTemplate.objects.prefetch_related('device_type__manufacturer') serializer_class = serializers.InterfaceTemplateSerializer filterset_class = filters.InterfaceTemplateFilter class FrontPortTemplateViewSet(ModelViewSet): - queryset = FrontPortTemplate.objects.select_related('device_type__manufacturer') + queryset = FrontPortTemplate.objects.prefetch_related('device_type__manufacturer') serializer_class = serializers.FrontPortTemplateSerializer filterset_class = filters.FrontPortTemplateFilter class RearPortTemplateViewSet(ModelViewSet): - queryset = RearPortTemplate.objects.select_related('device_type__manufacturer') + queryset = RearPortTemplate.objects.prefetch_related('device_type__manufacturer') serializer_class = serializers.RearPortTemplateSerializer filterset_class = filters.RearPortTemplateFilter class DeviceBayTemplateViewSet(ModelViewSet): - queryset = DeviceBayTemplate.objects.select_related('device_type__manufacturer') + queryset = DeviceBayTemplate.objects.prefetch_related('device_type__manufacturer') serializer_class = serializers.DeviceBayTemplateSerializer filterset_class = filters.DeviceBayTemplateFilter @@ -324,11 +320,9 @@ class PlatformViewSet(ModelViewSet): # class DeviceViewSet(CustomFieldModelViewSet): - queryset = Device.objects.select_related( + queryset = Device.objects.prefetch_related( 'device_type__manufacturer', 'device_role', 'tenant', 'platform', 'site', 'rack', 'parent_bay', - 'virtual_chassis__master', - ).prefetch_related( - 'primary_ip4__nat_outside', 'primary_ip6__nat_outside', 'tags', + 'virtual_chassis__master', 'primary_ip4__nat_outside', 'primary_ip6__nat_outside', 'tags', ) filterset_class = filters.DeviceFilter @@ -429,52 +423,36 @@ class DeviceViewSet(CustomFieldModelViewSet): # class ConsolePortViewSet(CableTraceMixin, ModelViewSet): - queryset = ConsolePort.objects.select_related( - 'device', 'connected_endpoint__device', 'cable' - ).prefetch_related( - 'tags' - ) + queryset = ConsolePort.objects.prefetch_related('device', 'connected_endpoint__device', 'cable', 'tags') serializer_class = serializers.ConsolePortSerializer filterset_class = filters.ConsolePortFilter class ConsoleServerPortViewSet(CableTraceMixin, ModelViewSet): - queryset = ConsoleServerPort.objects.select_related( - 'device', 'connected_endpoint__device', 'cable' - ).prefetch_related( - 'tags' - ) + queryset = ConsoleServerPort.objects.prefetch_related('device', 'connected_endpoint__device', 'cable', 'tags') serializer_class = serializers.ConsoleServerPortSerializer filterset_class = filters.ConsoleServerPortFilter class PowerPortViewSet(CableTraceMixin, ModelViewSet): - queryset = PowerPort.objects.select_related( - 'device', '_connected_poweroutlet__device', '_connected_powerfeed', 'cable' - ).prefetch_related( - 'tags' + queryset = PowerPort.objects.prefetch_related( + 'device', '_connected_poweroutlet__device', '_connected_powerfeed', 'cable', 'tags' ) serializer_class = serializers.PowerPortSerializer filterset_class = filters.PowerPortFilter class PowerOutletViewSet(CableTraceMixin, ModelViewSet): - queryset = PowerOutlet.objects.select_related( - 'device', 'connected_endpoint__device', 'cable' - ).prefetch_related( - 'tags' - ) + queryset = PowerOutlet.objects.prefetch_related('device', 'connected_endpoint__device', 'cable', 'tags') serializer_class = serializers.PowerOutletSerializer filterset_class = filters.PowerOutletFilter class InterfaceViewSet(CableTraceMixin, ModelViewSet): - queryset = Interface.objects.filter( + queryset = Interface.objects.prefetch_related( + 'device', '_connected_interface', '_connected_circuittermination', 'cable', 'ip_addresses', 'tags' + ).filter( device__isnull=False - ).select_related( - 'device', '_connected_interface', '_connected_circuittermination', 'cable' - ).prefetch_related( - 'ip_addresses', 'tags' ) serializer_class = serializers.InterfaceSerializer filterset_class = filters.InterfaceFilter @@ -491,33 +469,25 @@ class InterfaceViewSet(CableTraceMixin, ModelViewSet): class FrontPortViewSet(ModelViewSet): - queryset = FrontPort.objects.select_related( - 'device__device_type__manufacturer', 'rear_port', 'cable' - ).prefetch_related( - 'tags' - ) + queryset = FrontPort.objects.prefetch_related('device__device_type__manufacturer', 'rear_port', 'cable', 'tags') serializer_class = serializers.FrontPortSerializer filterset_class = filters.FrontPortFilter class RearPortViewSet(ModelViewSet): - queryset = RearPort.objects.select_related( - 'device__device_type__manufacturer', 'cable' - ).prefetch_related( - 'tags' - ) + queryset = RearPort.objects.prefetch_related('device__device_type__manufacturer', 'cable', 'tags') serializer_class = serializers.RearPortSerializer filterset_class = filters.RearPortFilter class DeviceBayViewSet(ModelViewSet): - queryset = DeviceBay.objects.select_related('installed_device').prefetch_related('tags') + queryset = DeviceBay.objects.prefetch_related('installed_device').prefetch_related('tags') serializer_class = serializers.DeviceBaySerializer filterset_class = filters.DeviceBayFilter class InventoryItemViewSet(ModelViewSet): - queryset = InventoryItem.objects.select_related('device', 'manufacturer').prefetch_related('tags') + queryset = InventoryItem.objects.prefetch_related('device', 'manufacturer').prefetch_related('tags') serializer_class = serializers.InventoryItemSerializer filterset_class = filters.InventoryItemFilter @@ -527,7 +497,7 @@ class InventoryItemViewSet(ModelViewSet): # class ConsoleConnectionViewSet(ListModelMixin, GenericViewSet): - queryset = ConsolePort.objects.select_related( + queryset = ConsolePort.objects.prefetch_related( 'device', 'connected_endpoint__device' ).filter( connected_endpoint__isnull=False @@ -537,7 +507,7 @@ class ConsoleConnectionViewSet(ListModelMixin, GenericViewSet): class PowerConnectionViewSet(ListModelMixin, GenericViewSet): - queryset = PowerPort.objects.select_related( + queryset = PowerPort.objects.prefetch_related( 'device', 'connected_endpoint__device' ).filter( _connected_poweroutlet__isnull=False @@ -547,7 +517,7 @@ class PowerConnectionViewSet(ListModelMixin, GenericViewSet): class InterfaceConnectionViewSet(ListModelMixin, GenericViewSet): - queryset = Interface.objects.select_related( + queryset = Interface.objects.prefetch_related( 'device', '_connected_interface__device' ).filter( # Avoid duplicate connections by only selecting the lower PK in a connected pair @@ -587,7 +557,7 @@ class VirtualChassisViewSet(ModelViewSet): # class PowerPanelViewSet(ModelViewSet): - queryset = PowerPanel.objects.select_related( + queryset = PowerPanel.objects.prefetch_related( 'site', 'rack_group' ).annotate( powerfeed_count=Count('powerfeeds') @@ -601,11 +571,7 @@ class PowerPanelViewSet(ModelViewSet): # class PowerFeedViewSet(CustomFieldModelViewSet): - queryset = PowerFeed.objects.select_related( - 'power_panel', 'rack' - ).prefetch_related( - 'tags' - ) + queryset = PowerFeed.objects.prefetch_related('power_panel', 'rack', 'tags') serializer_class = serializers.PowerFeedSerializer filterset_class = filters.PowerFeedFilter diff --git a/netbox/dcim/forms.py b/netbox/dcim/forms.py index db3b78dd3..4abbcdd71 100644 --- a/netbox/dcim/forms.py +++ b/netbox/dcim/forms.py @@ -632,7 +632,7 @@ class RackFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm): ) group_id = ChainedModelChoiceField( label='Rack group', - queryset=RackGroup.objects.select_related('site'), + queryset=RackGroup.objects.prefetch_related('site'), chains=( ('site', 'site'), ), @@ -745,7 +745,7 @@ class RackReservationFilterForm(BootstrapMixin, TenancyFilterForm): ) ) group_id = FilterChoiceField( - queryset=RackGroup.objects.select_related('site'), + queryset=RackGroup.objects.prefetch_related('site'), label='Rack group', null_label='-- None --', widget=APISelectMultiple( @@ -1391,14 +1391,14 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldForm): interface_ids = self.instance.vc_interfaces.values('pk') # Collect interface IPs - interface_ips = IPAddress.objects.select_related('interface').filter( + interface_ips = IPAddress.objects.prefetch_related('interface').filter( family=family, interface_id__in=interface_ids ) if interface_ips: ip_list = [(ip.id, '{} ({})'.format(ip.address, ip.interface)) for ip in interface_ips] ip_choices.append(('Interface IPs', ip_list)) # Collect NAT IPs - nat_ips = IPAddress.objects.select_related('nat_inside').filter( + nat_ips = IPAddress.objects.prefetch_related('nat_inside').filter( family=family, nat_inside__interface__in=interface_ids ) if nat_ips: @@ -1710,7 +1710,7 @@ class DeviceFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm) ) ) rack_group_id = FilterChoiceField( - queryset=RackGroup.objects.select_related( + queryset=RackGroup.objects.prefetch_related( 'site' ), label='Rack group', @@ -1749,7 +1749,7 @@ class DeviceFilterForm(BootstrapMixin, TenancyFilterForm, CustomFieldFilterForm) ) ) device_type_id = FilterChoiceField( - queryset=DeviceType.objects.select_related( + queryset=DeviceType.objects.prefetch_related( 'manufacturer' ), label='Model', diff --git a/netbox/dcim/models.py b/netbox/dcim/models.py index 4c22c9549..d4a54010c 100644 --- a/netbox/dcim/models.py +++ b/netbox/dcim/models.py @@ -607,7 +607,7 @@ class Rack(ChangeLoggedModel, CustomFieldModel): # Update racked devices if the assigned Site has been changed. if _site_id is not None and self.site_id != _site_id: - Device.objects.filter(rack=self).update(site_id=self.site.pk) + Device.objects.filter(rack=self).invalidated_update(site_id=self.site.pk) def to_csv(self): return ( @@ -664,7 +664,7 @@ class Rack(ChangeLoggedModel, CustomFieldModel): # Add devices to rack units list if self.pk: - for device in Device.objects.select_related('device_type__manufacturer', 'device_role')\ + for device in Device.objects.prefetch_related('device_type__manufacturer', 'device_role')\ .annotate(devicebay_count=Count('device_bays'))\ .exclude(pk=exclude)\ .filter(rack=self, position__gt=0)\ @@ -697,7 +697,7 @@ class Rack(ChangeLoggedModel, CustomFieldModel): """ # Gather all devices which consume U space within the rack - devices = self.devices.select_related('device_type').filter(position__gte=1).exclude(pk__in=exclude) + devices = self.devices.prefetch_related('device_type').filter(position__gte=1).exclude(pk__in=exclude) # Initialize the rack unit skeleton units = list(range(1, self.u_height + 1)) @@ -1738,7 +1738,7 @@ class Device(ChangeLoggedModel, ConfigContextModel, CustomFieldModel): ) # Update Site and Rack assignment for any child Devices - Device.objects.filter(parent_bay__device=self).update(site=self.site, rack=self.rack) + Device.objects.filter(parent_bay__device=self).invalidated_update(site=self.site, rack=self.rack) def to_csv(self): return ( diff --git a/netbox/dcim/signals.py b/netbox/dcim/signals.py index 67479262b..c603c9896 100644 --- a/netbox/dcim/signals.py +++ b/netbox/dcim/signals.py @@ -10,7 +10,7 @@ def assign_virtualchassis_master(instance, created, **kwargs): When a VirtualChassis is created, automatically assign its master device to the VC. """ if created: - Device.objects.filter(pk=instance.master.pk).update(virtual_chassis=instance, vc_position=None) + Device.objects.filter(pk=instance.master.pk).invalidated_update(virtual_chassis=instance, vc_position=None) @receiver(pre_delete, sender=VirtualChassis) @@ -18,7 +18,7 @@ def clear_virtualchassis_members(instance, **kwargs): """ When a VirtualChassis is deleted, nullify the vc_position and vc_priority fields of its prior members. """ - Device.objects.filter(virtual_chassis=instance.pk).update(vc_position=None, vc_priority=None) + Device.objects.filter(virtual_chassis=instance.pk).invalidated_update(vc_position=None, vc_priority=None) @receiver(post_save, sender=Cable) diff --git a/netbox/dcim/tests/test_api.py b/netbox/dcim/tests/test_api.py index 9c873c886..6b9cb47b1 100644 --- a/netbox/dcim/tests/test_api.py +++ b/netbox/dcim/tests/test_api.py @@ -3430,11 +3430,11 @@ class VirtualChassisTest(APITestCase): # Create two VirtualChassis with three members each self.vc1 = VirtualChassis.objects.create(master=self.device1, domain='test-domain-1') - Device.objects.filter(pk=self.device2.pk).update(virtual_chassis=self.vc1, vc_position=2) - Device.objects.filter(pk=self.device3.pk).update(virtual_chassis=self.vc1, vc_position=3) + Device.objects.filter(pk=self.device2.pk).invalidated_update(virtual_chassis=self.vc1, vc_position=2) + Device.objects.filter(pk=self.device3.pk).invalidated_update(virtual_chassis=self.vc1, vc_position=3) self.vc2 = VirtualChassis.objects.create(master=self.device4, domain='test-domain-2') - Device.objects.filter(pk=self.device5.pk).update(virtual_chassis=self.vc2, vc_position=2) - Device.objects.filter(pk=self.device6.pk).update(virtual_chassis=self.vc2, vc_position=3) + Device.objects.filter(pk=self.device5.pk).invalidated_update(virtual_chassis=self.vc2, vc_position=2) + Device.objects.filter(pk=self.device6.pk).invalidated_update(virtual_chassis=self.vc2, vc_position=3) def test_get_virtualchassis(self): diff --git a/netbox/dcim/tests/test_views.py b/netbox/dcim/tests/test_views.py index 6e34b8ae9..0cba29a94 100644 --- a/netbox/dcim/tests/test_views.py +++ b/netbox/dcim/tests/test_views.py @@ -442,11 +442,11 @@ class VirtualChassisTestCase(TestCase): # Create three VirtualChassis with two members each vc1 = VirtualChassis.objects.create(master=device1, domain='test-domain-1') - Device.objects.filter(pk=device2.pk).update(virtual_chassis=vc1, vc_position=2) + Device.objects.filter(pk=device2.pk).invalidated_update(virtual_chassis=vc1, vc_position=2) vc2 = VirtualChassis.objects.create(master=device3, domain='test-domain-2') - Device.objects.filter(pk=device4.pk).update(virtual_chassis=vc2, vc_position=2) + Device.objects.filter(pk=device4.pk).invalidated_update(virtual_chassis=vc2, vc_position=2) vc3 = VirtualChassis.objects.create(master=device5, domain='test-domain-3') - Device.objects.filter(pk=device6.pk).update(virtual_chassis=vc3, vc_position=2) + Device.objects.filter(pk=device6.pk).invalidated_update(virtual_chassis=vc3, vc_position=2) def test_virtualchassis_list(self): diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 5ddaf15ed..9d3d748e1 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -185,7 +185,7 @@ class RegionBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): class SiteListView(PermissionRequiredMixin, ObjectListView): permission_required = 'dcim.view_site' - queryset = Site.objects.select_related('region', 'tenant') + queryset = Site.objects.prefetch_related('region', 'tenant') filter = filters.SiteFilter filter_form = forms.SiteFilterForm table = tables.SiteTable @@ -197,7 +197,7 @@ class SiteView(PermissionRequiredMixin, View): def get(self, request, slug): - site = get_object_or_404(Site.objects.select_related('region', 'tenant__group'), slug=slug) + site = get_object_or_404(Site.objects.prefetch_related('region', 'tenant__group'), slug=slug) stats = { 'rack_count': Rack.objects.filter(site=site).count(), 'device_count': Device.objects.filter(site=site).count(), @@ -246,7 +246,7 @@ class SiteBulkImportView(PermissionRequiredMixin, BulkImportView): class SiteBulkEditView(PermissionRequiredMixin, BulkEditView): permission_required = 'dcim.change_site' - queryset = Site.objects.select_related('region', 'tenant') + queryset = Site.objects.prefetch_related('region', 'tenant') filter = filters.SiteFilter table = tables.SiteTable form = forms.SiteBulkEditForm @@ -255,7 +255,7 @@ class SiteBulkEditView(PermissionRequiredMixin, BulkEditView): class SiteBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_site' - queryset = Site.objects.select_related('region', 'tenant') + queryset = Site.objects.prefetch_related('region', 'tenant') filter = filters.SiteFilter table = tables.SiteTable default_return_url = 'dcim:site_list' @@ -267,7 +267,7 @@ class SiteBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): class RackGroupListView(PermissionRequiredMixin, ObjectListView): permission_required = 'dcim.view_rackgroup' - queryset = RackGroup.objects.select_related('site').annotate(rack_count=Count('racks')) + queryset = RackGroup.objects.prefetch_related('site').annotate(rack_count=Count('racks')) filter = filters.RackGroupFilter filter_form = forms.RackGroupFilterForm table = tables.RackGroupTable @@ -294,7 +294,7 @@ class RackGroupBulkImportView(PermissionRequiredMixin, BulkImportView): class RackGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_rackgroup' - queryset = RackGroup.objects.select_related('site').annotate(rack_count=Count('racks')) + queryset = RackGroup.objects.prefetch_related('site').annotate(rack_count=Count('racks')) filter = filters.RackGroupFilter table = tables.RackGroupTable default_return_url = 'dcim:rackgroup_list' @@ -342,10 +342,8 @@ class RackRoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): class RackListView(PermissionRequiredMixin, ObjectListView): permission_required = 'dcim.view_rack' - queryset = Rack.objects.select_related( - 'site', 'group', 'tenant', 'role' - ).prefetch_related( - 'devices__device_type' + queryset = Rack.objects.prefetch_related( + 'site', 'group', 'tenant', 'role', 'devices__device_type' ).annotate( device_count=Count('devices') ) @@ -363,11 +361,7 @@ class RackElevationListView(PermissionRequiredMixin, View): def get(self, request): - racks = Rack.objects.select_related( - 'site', 'group', 'tenant', 'role' - ).prefetch_related( - 'devices__device_type' - ) + racks = Rack.objects.prefetch_related('site', 'group', 'tenant', 'role', 'devices__device_type') racks = filters.RackFilter(request.GET, racks).qs total_count = racks.count() @@ -402,15 +396,18 @@ class RackView(PermissionRequiredMixin, View): def get(self, request, pk): - rack = get_object_or_404(Rack.objects.select_related('site__region', 'tenant__group', 'group', 'role'), pk=pk) + rack = get_object_or_404(Rack.objects.prefetch_related('site__region', 'tenant__group', 'group', 'role'), pk=pk) - nonracked_devices = Device.objects.filter(rack=rack, position__isnull=True, parent_bay__isnull=True) \ - .select_related('device_type__manufacturer') + nonracked_devices = Device.objects.filter( + rack=rack, + position__isnull=True, + parent_bay__isnull=True + ).prefetch_related('device_type__manufacturer') next_rack = Rack.objects.filter(site=rack.site, name__gt=rack.name).order_by('name').first() prev_rack = Rack.objects.filter(site=rack.site, name__lt=rack.name).order_by('-name').first() reservations = RackReservation.objects.filter(rack=rack) - power_feeds = PowerFeed.objects.filter(rack=rack).select_related('power_panel') + power_feeds = PowerFeed.objects.filter(rack=rack).prefetch_related('power_panel') return render(request, 'dcim/rack.html', { 'rack': rack, @@ -451,7 +448,7 @@ class RackBulkImportView(PermissionRequiredMixin, BulkImportView): class RackBulkEditView(PermissionRequiredMixin, BulkEditView): permission_required = 'dcim.change_rack' - queryset = Rack.objects.select_related('site', 'group', 'tenant', 'role') + queryset = Rack.objects.prefetch_related('site', 'group', 'tenant', 'role') filter = filters.RackFilter table = tables.RackTable form = forms.RackBulkEditForm @@ -460,7 +457,7 @@ class RackBulkEditView(PermissionRequiredMixin, BulkEditView): class RackBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_rack' - queryset = Rack.objects.select_related('site', 'group', 'tenant', 'role') + queryset = Rack.objects.prefetch_related('site', 'group', 'tenant', 'role') filter = filters.RackFilter table = tables.RackTable default_return_url = 'dcim:rack_list' @@ -472,7 +469,7 @@ class RackBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): class RackReservationListView(PermissionRequiredMixin, ObjectListView): permission_required = 'dcim.view_rackreservation' - queryset = RackReservation.objects.select_related('rack__site') + queryset = RackReservation.objects.prefetch_related('rack__site') filter = filters.RackReservationFilter filter_form = forms.RackReservationFilterForm table = tables.RackReservationTable @@ -508,7 +505,7 @@ class RackReservationDeleteView(PermissionRequiredMixin, ObjectDeleteView): class RackReservationBulkEditView(PermissionRequiredMixin, BulkEditView): permission_required = 'dcim.change_rackreservation' - queryset = RackReservation.objects.select_related('rack', 'user') + queryset = RackReservation.objects.prefetch_related('rack', 'user') filter = filters.RackReservationFilter table = tables.RackReservationTable form = forms.RackReservationBulkEditForm @@ -517,7 +514,7 @@ class RackReservationBulkEditView(PermissionRequiredMixin, BulkEditView): class RackReservationBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_rackreservation' - queryset = RackReservation.objects.select_related('rack', 'user') + queryset = RackReservation.objects.prefetch_related('rack', 'user') filter = filters.RackReservationFilter table = tables.RackReservationTable default_return_url = 'dcim:rackreservation_list' @@ -569,7 +566,7 @@ class ManufacturerBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): class DeviceTypeListView(PermissionRequiredMixin, ObjectListView): permission_required = 'dcim.view_devicetype' - queryset = DeviceType.objects.select_related('manufacturer').annotate(instance_count=Count('instances')) + queryset = DeviceType.objects.prefetch_related('manufacturer').annotate(instance_count=Count('instances')) filter = filters.DeviceTypeFilter filter_form = forms.DeviceTypeFilterForm table = tables.DeviceTypeTable @@ -666,7 +663,7 @@ class DeviceTypeBulkImportView(PermissionRequiredMixin, BulkImportView): class DeviceTypeBulkEditView(PermissionRequiredMixin, BulkEditView): permission_required = 'dcim.change_devicetype' - queryset = DeviceType.objects.select_related('manufacturer').annotate(instance_count=Count('instances')) + queryset = DeviceType.objects.prefetch_related('manufacturer').annotate(instance_count=Count('instances')) filter = filters.DeviceTypeFilter table = tables.DeviceTypeTable form = forms.DeviceTypeBulkEditForm @@ -675,7 +672,7 @@ class DeviceTypeBulkEditView(PermissionRequiredMixin, BulkEditView): class DeviceTypeBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_devicetype' - queryset = DeviceType.objects.select_related('manufacturer').annotate(instance_count=Count('instances')) + queryset = DeviceType.objects.prefetch_related('manufacturer').annotate(instance_count=Count('instances')) filter = filters.DeviceTypeFilter table = tables.DeviceTypeTable default_return_url = 'dcim:devicetype_list' @@ -907,7 +904,7 @@ class PlatformBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): class DeviceListView(PermissionRequiredMixin, ObjectListView): permission_required = 'dcim.view_device' - queryset = Device.objects.select_related( + queryset = Device.objects.prefetch_related( 'device_type__manufacturer', 'device_role', 'tenant', 'site', 'rack', 'primary_ip4', 'primary_ip6' ) filter = filters.DeviceFilter @@ -921,7 +918,7 @@ class DeviceView(PermissionRequiredMixin, View): def get(self, request, pk): - device = get_object_or_404(Device.objects.select_related( + device = get_object_or_404(Device.objects.prefetch_related( 'site__region', 'rack__group', 'tenant__group', 'device_role', 'platform' ), pk=pk) @@ -934,32 +931,31 @@ class DeviceView(PermissionRequiredMixin, View): vc_members = [] # Console ports - console_ports = device.consoleports.select_related('connected_endpoint__device', 'cable') + console_ports = device.consoleports.prefetch_related('connected_endpoint__device', 'cable') # Console server ports - consoleserverports = device.consoleserverports.select_related('connected_endpoint__device', 'cable') + consoleserverports = device.consoleserverports.prefetch_related('connected_endpoint__device', 'cable') # Power ports - power_ports = device.powerports.select_related('_connected_poweroutlet__device', 'cable') + power_ports = device.powerports.prefetch_related('_connected_poweroutlet__device', 'cable') # Power outlets - poweroutlets = device.poweroutlets.select_related('connected_endpoint__device', 'cable', 'power_port') + poweroutlets = device.poweroutlets.prefetch_related('connected_endpoint__device', 'cable', 'power_port') # Interfaces - interfaces = device.vc_interfaces.select_related( - 'lag', '_connected_interface__device', '_connected_circuittermination__circuit', 'cable' - ).prefetch_related( + interfaces = device.vc_interfaces.prefetch_related( + 'lag', '_connected_interface__device', '_connected_circuittermination__circuit', 'cable', 'cable__termination_a', 'cable__termination_b', 'ip_addresses', 'tags' ) # Front ports - front_ports = device.frontports.select_related('rear_port', 'cable') + front_ports = device.frontports.prefetch_related('rear_port', 'cable') # Rear ports - rear_ports = device.rearports.select_related('cable') + rear_ports = device.rearports.prefetch_related('cable') # Device bays - device_bays = device.device_bays.select_related('installed_device__device_type__manufacturer') + device_bays = device.device_bays.prefetch_related('installed_device__device_type__manufacturer') # Services services = device.services.all() @@ -972,7 +968,7 @@ class DeviceView(PermissionRequiredMixin, View): site=device.site, device_role=device.device_role ).exclude( pk=device.pk - ).select_related( + ).prefetch_related( 'rack', 'device_type__manufacturer' )[:10] @@ -1005,10 +1001,8 @@ class DeviceInventoryView(PermissionRequiredMixin, View): device = get_object_or_404(Device, pk=pk) inventory_items = InventoryItem.objects.filter( device=device, parent=None - ).select_related( - 'manufacturer' ).prefetch_related( - 'child_items' + 'manufacturer', 'child_items' ) return render(request, 'dcim/device_inventory.html', { @@ -1037,7 +1031,7 @@ class DeviceLLDPNeighborsView(PermissionRequiredMixin, View): def get(self, request, pk): device = get_object_or_404(Device, pk=pk) - interfaces = device.vc_interfaces.connectable().select_related( + interfaces = device.vc_interfaces.connectable().prefetch_related( '_connected_interface__device' ) @@ -1114,7 +1108,7 @@ class ChildDeviceBulkImportView(PermissionRequiredMixin, BulkImportView): class DeviceBulkEditView(PermissionRequiredMixin, BulkEditView): permission_required = 'dcim.change_device' - queryset = Device.objects.select_related('tenant', 'site', 'rack', 'device_role', 'device_type__manufacturer') + queryset = Device.objects.prefetch_related('tenant', 'site', 'rack', 'device_role', 'device_type__manufacturer') filter = filters.DeviceFilter table = tables.DeviceTable form = forms.DeviceBulkEditForm @@ -1123,7 +1117,7 @@ class DeviceBulkEditView(PermissionRequiredMixin, BulkEditView): class DeviceBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_device' - queryset = Device.objects.select_related('tenant', 'site', 'rack', 'device_role', 'device_type__manufacturer') + queryset = Device.objects.prefetch_related('tenant', 'site', 'rack', 'device_role', 'device_type__manufacturer') filter = filters.DeviceFilter table = tables.DeviceTable default_return_url = 'dcim:device_list' @@ -1310,7 +1304,7 @@ class InterfaceView(PermissionRequiredMixin, View): # Get assigned IP addresses ipaddress_table = InterfaceIPAddressTable( - data=interface.ip_addresses.select_related('vrf', 'tenant'), + data=interface.ip_addresses.prefetch_related('vrf', 'tenant'), orderable=False ) @@ -1319,7 +1313,7 @@ class InterfaceView(PermissionRequiredMixin, View): if interface.untagged_vlan is not None: vlans.append(interface.untagged_vlan) vlans[0].tagged = False - for vlan in interface.tagged_vlans.select_related('site', 'group', 'tenant', 'role'): + for vlan in interface.tagged_vlans.prefetch_related('site', 'group', 'tenant', 'role'): vlan.tagged = True vlans.append(vlan) vlan_table = InterfaceVLANTable( @@ -1842,7 +1836,7 @@ class CableBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): class ConsoleConnectionsListView(PermissionRequiredMixin, ObjectListView): permission_required = ('dcim.view_consoleport', 'dcim.view_consoleserverport') - queryset = ConsolePort.objects.select_related( + queryset = ConsolePort.objects.prefetch_related( 'device', 'connected_endpoint__device' ).filter( connected_endpoint__isnull=False @@ -1873,7 +1867,7 @@ class ConsoleConnectionsListView(PermissionRequiredMixin, ObjectListView): class PowerConnectionsListView(PermissionRequiredMixin, ObjectListView): permission_required = ('dcim.view_powerport', 'dcim.view_poweroutlet') - queryset = PowerPort.objects.select_related( + queryset = PowerPort.objects.prefetch_related( 'device', '_connected_poweroutlet__device' ).filter( _connected_poweroutlet__isnull=False @@ -1904,7 +1898,7 @@ class PowerConnectionsListView(PermissionRequiredMixin, ObjectListView): class InterfaceConnectionsListView(PermissionRequiredMixin, ObjectListView): permission_required = 'dcim.view_interface' - queryset = Interface.objects.select_related( + queryset = Interface.objects.prefetch_related( 'device', 'cable', '_connected_interface__device' ).filter( # Avoid duplicate connections by only selecting the lower PK in a connected pair @@ -1947,7 +1941,7 @@ class InterfaceConnectionsListView(PermissionRequiredMixin, ObjectListView): class InventoryItemListView(PermissionRequiredMixin, ObjectListView): permission_required = 'dcim.view_inventoryitem' - queryset = InventoryItem.objects.select_related('device', 'manufacturer') + queryset = InventoryItem.objects.prefetch_related('device', 'manufacturer') filter = filters.InventoryItemFilter filter_form = forms.InventoryItemFilterForm table = tables.InventoryItemTable @@ -1982,7 +1976,7 @@ class InventoryItemBulkImportView(PermissionRequiredMixin, BulkImportView): class InventoryItemBulkEditView(PermissionRequiredMixin, BulkEditView): permission_required = 'dcim.change_inventoryitem' - queryset = InventoryItem.objects.select_related('device', 'manufacturer') + queryset = InventoryItem.objects.prefetch_related('device', 'manufacturer') filter = filters.InventoryItemFilter table = tables.InventoryItemTable form = forms.InventoryItemBulkEditForm @@ -1991,7 +1985,7 @@ class InventoryItemBulkEditView(PermissionRequiredMixin, BulkEditView): class InventoryItemBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_inventoryitem' - queryset = InventoryItem.objects.select_related('device', 'manufacturer') + queryset = InventoryItem.objects.prefetch_related('device', 'manufacturer') table = tables.InventoryItemTable template_name = 'dcim/inventoryitem_bulk_delete.html' default_return_url = 'dcim:inventoryitem_list' @@ -2003,7 +1997,7 @@ class InventoryItemBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): class VirtualChassisListView(PermissionRequiredMixin, ObjectListView): permission_required = 'dcim.view_virtualchassis' - queryset = VirtualChassis.objects.select_related('master').annotate(member_count=Count('members')) + queryset = VirtualChassis.objects.prefetch_related('master').annotate(member_count=Count('members')) table = tables.VirtualChassisTable filter = filters.VirtualChassisFilter filter_form = forms.VirtualChassisFilterForm @@ -2023,7 +2017,7 @@ class VirtualChassisCreateView(PermissionRequiredMixin, View): return redirect('dcim:device_list') device_queryset = Device.objects.filter( pk__in=pk_form.cleaned_data.get('pk') - ).select_related('rack').order_by('vc_position') + ).prefetch_related('rack').order_by('vc_position') VCMemberFormSet = modelformset_factory( model=Device, @@ -2077,7 +2071,7 @@ class VirtualChassisEditView(PermissionRequiredMixin, GetReturnURLMixin, View): formset=forms.BaseVCMemberFormSet, extra=0 ) - members_queryset = virtual_chassis.members.select_related('rack').order_by('vc_position') + members_queryset = virtual_chassis.members.prefetch_related('rack').order_by('vc_position') vc_form = forms.VirtualChassisForm(instance=virtual_chassis) vc_form.fields['master'].queryset = members_queryset @@ -2098,7 +2092,7 @@ class VirtualChassisEditView(PermissionRequiredMixin, GetReturnURLMixin, View): formset=forms.BaseVCMemberFormSet, extra=0 ) - members_queryset = virtual_chassis.members.select_related('rack').order_by('vc_position') + members_queryset = virtual_chassis.members.prefetch_related('rack').order_by('vc_position') vc_form = forms.VirtualChassisForm(request.POST, instance=virtual_chassis) vc_form.fields['master'].queryset = members_queryset @@ -2114,7 +2108,7 @@ class VirtualChassisEditView(PermissionRequiredMixin, GetReturnURLMixin, View): # Nullify the vc_position of each member first to allow reordering without raising an IntegrityError on # duplicate positions. Then save each member instance. members = formset.save(commit=False) - Device.objects.filter(pk__in=[m.pk for m in members]).update(vc_position=None) + Device.objects.filter(pk__in=[m.pk for m in members]).invalidated_update(vc_position=None) for member in members: member.save() @@ -2215,12 +2209,13 @@ class VirtualChassisRemoveMemberView(PermissionRequiredMixin, GetReturnURLMixin, if form.is_valid(): - Device.objects.filter(pk=device.pk).update( + Device.objects.filter(pk=device.pk).invalidated_update( virtual_chassis=None, vc_position=None, vc_priority=None ) + msg = 'Removed {} from virtual chassis {}'.format(device, device.virtual_chassis) messages.success(request, msg) @@ -2239,7 +2234,7 @@ class VirtualChassisRemoveMemberView(PermissionRequiredMixin, GetReturnURLMixin, class PowerPanelListView(PermissionRequiredMixin, ObjectListView): permission_required = 'dcim.view_powerpanel' - queryset = PowerPanel.objects.select_related( + queryset = PowerPanel.objects.prefetch_related( 'site', 'rack_group' ).annotate( powerfeed_count=Count('powerfeeds') @@ -2255,9 +2250,9 @@ class PowerPanelView(PermissionRequiredMixin, View): def get(self, request, pk): - powerpanel = get_object_or_404(PowerPanel.objects.select_related('site', 'rack_group'), pk=pk) + powerpanel = get_object_or_404(PowerPanel.objects.prefetch_related('site', 'rack_group'), pk=pk) powerfeed_table = tables.PowerFeedTable( - data=PowerFeed.objects.filter(power_panel=powerpanel).select_related('rack'), + data=PowerFeed.objects.filter(power_panel=powerpanel).prefetch_related('rack'), orderable=False ) powerfeed_table.exclude = ['power_panel'] @@ -2294,7 +2289,7 @@ class PowerPanelBulkImportView(PermissionRequiredMixin, BulkImportView): class PowerPanelBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_powerpanel' - queryset = PowerPanel.objects.select_related( + queryset = PowerPanel.objects.prefetch_related( 'site', 'rack_group' ).annotate( rack_count=Count('powerfeeds') @@ -2310,7 +2305,7 @@ class PowerPanelBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): class PowerFeedListView(PermissionRequiredMixin, ObjectListView): permission_required = 'dcim.view_powerfeed' - queryset = PowerFeed.objects.select_related( + queryset = PowerFeed.objects.prefetch_related( 'power_panel', 'rack' ) filter = filters.PowerFeedFilter @@ -2324,7 +2319,7 @@ class PowerFeedView(PermissionRequiredMixin, View): def get(self, request, pk): - powerfeed = get_object_or_404(PowerFeed.objects.select_related('power_panel', 'rack'), pk=pk) + powerfeed = get_object_or_404(PowerFeed.objects.prefetch_related('power_panel', 'rack'), pk=pk) return render(request, 'dcim/powerfeed.html', { 'powerfeed': powerfeed, @@ -2358,7 +2353,7 @@ class PowerFeedBulkImportView(PermissionRequiredMixin, BulkImportView): class PowerFeedBulkEditView(PermissionRequiredMixin, BulkEditView): permission_required = 'dcim.change_powerfeed' - queryset = PowerFeed.objects.select_related('power_panel', 'rack') + queryset = PowerFeed.objects.prefetch_related('power_panel', 'rack') filter = filters.PowerFeedFilter table = tables.PowerFeedTable form = forms.PowerFeedBulkEditForm @@ -2367,7 +2362,7 @@ class PowerFeedBulkEditView(PermissionRequiredMixin, BulkEditView): class PowerFeedBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_powerfeed' - queryset = PowerFeed.objects.select_related('power_panel', 'rack') + queryset = PowerFeed.objects.prefetch_related('power_panel', 'rack') filter = filters.PowerFeedFilter table = tables.PowerFeedTable default_return_url = 'dcim:powerfeed_list' diff --git a/netbox/extras/api/views.py b/netbox/extras/api/views.py index 44e010cd2..526db20a2 100644 --- a/netbox/extras/api/views.py +++ b/netbox/extras/api/views.py @@ -120,7 +120,7 @@ class ExportTemplateViewSet(ModelViewSet): # class TopologyMapViewSet(ModelViewSet): - queryset = TopologyMap.objects.select_related('site') + queryset = TopologyMap.objects.prefetch_related('site') serializer_class = serializers.TopologyMapSerializer filterset_class = filters.TopologyMapFilter @@ -260,6 +260,6 @@ class ObjectChangeViewSet(ReadOnlyModelViewSet): """ Retrieve a list of recent changes. """ - queryset = ObjectChange.objects.select_related('user') + queryset = ObjectChange.objects.prefetch_related('user') serializer_class = serializers.ObjectChangeSerializer filterset_class = filters.ObjectChangeFilter diff --git a/netbox/extras/forms.py b/netbox/extras/forms.py index 261822d28..eeb7921e1 100644 --- a/netbox/extras/forms.py +++ b/netbox/extras/forms.py @@ -111,8 +111,10 @@ class CustomFieldForm(forms.ModelForm): # If editing an existing object, initialize values for all custom fields if self.instance.pk: - existing_values = CustomFieldValue.objects.filter(obj_type=self.obj_type, obj_id=self.instance.pk)\ - .select_related('field') + existing_values = CustomFieldValue.objects.filter( + obj_type=self.obj_type, + obj_id=self.instance.pk + ).prefetch_related('field') for cfv in existing_values: self.initial['cf_{}'.format(str(cfv.field.name))] = cfv.serialized_value @@ -120,9 +122,11 @@ class CustomFieldForm(forms.ModelForm): for field_name in self.custom_fields: try: - cfv = CustomFieldValue.objects.select_related('field').get(field=self.fields[field_name].model, - obj_type=self.obj_type, - obj_id=self.instance.pk) + cfv = CustomFieldValue.objects.prefetch_related('field').get( + field=self.fields[field_name].model, + obj_type=self.obj_type, + obj_id=self.instance.pk + ) except CustomFieldValue.DoesNotExist: # Skip this field if none exists already and its value is empty if self.cleaned_data[field_name] in [None, '']: diff --git a/netbox/extras/models.py b/netbox/extras/models.py index c5df5c2e5..02dd235b6 100644 --- a/netbox/extras/models.py +++ b/netbox/extras/models.py @@ -569,7 +569,7 @@ class TopologyMap(models.Model): # Add each device to the graph devices = [] for query in device_set.strip(';').split(';'): # Split regexes on semicolons - devices += Device.objects.filter(name__regex=query).select_related('device_role') + devices += Device.objects.filter(name__regex=query).prefetch_related('device_role') # Remove duplicate devices devices = [d for d in devices if d.id not in seen] seen.update([d.id for d in devices]) @@ -607,7 +607,7 @@ class TopologyMap(models.Model): from dcim.models import Interface # Add all interface connections to the graph - connected_interfaces = Interface.objects.select_related( + connected_interfaces = Interface.objects.prefetch_related( '_connected_interface__device' ).filter( Q(device__in=devices) | Q(_connected_interface__device__in=devices), diff --git a/netbox/extras/views.py b/netbox/extras/views.py index 6f4751619..116370f74 100644 --- a/netbox/extras/views.py +++ b/netbox/extras/views.py @@ -47,10 +47,8 @@ class TagView(View): tag = get_object_or_404(Tag, slug=slug) tagged_items = TaggedItem.objects.filter( tag=tag - ).select_related( - 'content_type' ).prefetch_related( - 'content_object' + 'content_type', 'content_object' ) # Generate a table of all items tagged with this Tag @@ -178,7 +176,7 @@ class ObjectConfigContextView(View): class ObjectChangeListView(PermissionRequiredMixin, ObjectListView): permission_required = 'extras.view_objectchange' - queryset = ObjectChange.objects.select_related('user', 'changed_object_type') + queryset = ObjectChange.objects.prefetch_related('user', 'changed_object_type') filter = filters.ObjectChangeFilter filter_form = ObjectChangeFilterForm table = ObjectChangeTable @@ -217,7 +215,7 @@ class ObjectChangeLogView(View): # Gather all changes for this object (and its related objects) content_type = ContentType.objects.get_for_model(model) - objectchanges = ObjectChange.objects.select_related( + objectchanges = ObjectChange.objects.prefetch_related( 'user', 'changed_object_type' ).filter( Q(changed_object_type=content_type, changed_object_id=obj.pk) | diff --git a/netbox/ipam/api/views.py b/netbox/ipam/api/views.py index 95c418a10..e966bc3d1 100644 --- a/netbox/ipam/api/views.py +++ b/netbox/ipam/api/views.py @@ -33,7 +33,7 @@ class IPAMFieldChoicesViewSet(FieldChoicesViewSet): # class VRFViewSet(CustomFieldModelViewSet): - queryset = VRF.objects.select_related('tenant').prefetch_related('tags').annotate( + queryset = VRF.objects.prefetch_related('tenant').prefetch_related('tags').annotate( ipaddress_count=get_subquery(IPAddress, 'vrf'), prefix_count=get_subquery(Prefix, 'vrf') ) @@ -58,7 +58,7 @@ class RIRViewSet(ModelViewSet): # class AggregateViewSet(CustomFieldModelViewSet): - queryset = Aggregate.objects.select_related('rir').prefetch_related('tags') + queryset = Aggregate.objects.prefetch_related('rir').prefetch_related('tags') serializer_class = serializers.AggregateSerializer filterset_class = filters.AggregateFilter @@ -81,11 +81,7 @@ class RoleViewSet(ModelViewSet): # class PrefixViewSet(CustomFieldModelViewSet): - queryset = Prefix.objects.select_related( - 'site', 'vrf__tenant', 'tenant', 'vlan', 'role' - ).prefetch_related( - 'tags' - ) + queryset = Prefix.objects.prefetch_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role', 'tags') serializer_class = serializers.PrefixSerializer filterset_class = filters.PrefixFilter @@ -263,9 +259,8 @@ class PrefixViewSet(CustomFieldModelViewSet): # class IPAddressViewSet(CustomFieldModelViewSet): - queryset = IPAddress.objects.select_related( - 'vrf__tenant', 'tenant', 'nat_inside', 'interface__device__device_type', 'interface__virtual_machine' - ).prefetch_related( + queryset = IPAddress.objects.prefetch_related( + 'vrf__tenant', 'tenant', 'nat_inside', 'interface__device__device_type', 'interface__virtual_machine', 'nat_outside', 'tags', ) serializer_class = serializers.IPAddressSerializer @@ -277,7 +272,7 @@ class IPAddressViewSet(CustomFieldModelViewSet): # class VLANGroupViewSet(ModelViewSet): - queryset = VLANGroup.objects.select_related('site').annotate( + queryset = VLANGroup.objects.prefetch_related('site').annotate( vlan_count=Count('vlans') ) serializer_class = serializers.VLANGroupSerializer @@ -289,10 +284,8 @@ class VLANGroupViewSet(ModelViewSet): # class VLANViewSet(CustomFieldModelViewSet): - queryset = VLAN.objects.select_related( - 'site', 'group', 'tenant', 'role' - ).prefetch_related( - 'tags' + queryset = VLAN.objects.prefetch_related( + 'site', 'group', 'tenant', 'role', 'tags' ).annotate( prefix_count=get_subquery(Prefix, 'role') ) @@ -305,6 +298,6 @@ class VLANViewSet(CustomFieldModelViewSet): # class ServiceViewSet(ModelViewSet): - queryset = Service.objects.select_related('device').prefetch_related('tags') + queryset = Service.objects.prefetch_related('device').prefetch_related('tags') serializer_class = serializers.ServiceSerializer filterset_class = filters.ServiceFilter diff --git a/netbox/ipam/filters.py b/netbox/ipam/filters.py index 49c547d2d..da9362dc9 100644 --- a/netbox/ipam/filters.py +++ b/netbox/ipam/filters.py @@ -360,7 +360,7 @@ class IPAddressFilter(TenancyFilterSet, CustomFieldFilterSet): def filter_device(self, queryset, name, value): try: - device = Device.objects.select_related('device_type').get(**{name: value}) + device = Device.objects.prefetch_related('device_type').get(**{name: value}) vc_interface_ids = [i['id'] for i in device.vc_interfaces.values('id')] return queryset.filter(interface_id__in=vc_interface_ids) except Device.DoesNotExist: diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index d80646bb0..c5b532774 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -115,7 +115,7 @@ def add_available_vlans(vlan_group, vlans): class VRFListView(PermissionRequiredMixin, ObjectListView): permission_required = 'ipam.view_vrf' - queryset = VRF.objects.select_related('tenant') + queryset = VRF.objects.prefetch_related('tenant') filter = filters.VRFFilter filter_form = forms.VRFFilterForm table = tables.VRFTable @@ -163,7 +163,7 @@ class VRFBulkImportView(PermissionRequiredMixin, BulkImportView): class VRFBulkEditView(PermissionRequiredMixin, BulkEditView): permission_required = 'ipam.change_vrf' - queryset = VRF.objects.select_related('tenant') + queryset = VRF.objects.prefetch_related('tenant') filter = filters.VRFFilter table = tables.VRFTable form = forms.VRFBulkEditForm @@ -172,7 +172,7 @@ class VRFBulkEditView(PermissionRequiredMixin, BulkEditView): class VRFBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'ipam.delete_vrf' - queryset = VRF.objects.select_related('tenant') + queryset = VRF.objects.prefetch_related('tenant') filter = filters.VRFFilter table = tables.VRFTable default_return_url = 'ipam:vrf_list' @@ -291,7 +291,7 @@ class RIRBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): class AggregateListView(PermissionRequiredMixin, ObjectListView): permission_required = 'ipam.view_aggregate' - queryset = Aggregate.objects.select_related('rir').extra(select={ + queryset = Aggregate.objects.prefetch_related('rir').extra(select={ 'child_count': 'SELECT COUNT(*) FROM ipam_prefix WHERE ipam_prefix.prefix <<= ipam_aggregate.prefix', }) filter = filters.AggregateFilter @@ -326,7 +326,7 @@ class AggregateView(PermissionRequiredMixin, View): # Find all child prefixes contained by this aggregate child_prefixes = Prefix.objects.filter( prefix__net_contained_or_equal=str(aggregate.prefix) - ).select_related( + ).prefetch_related( 'site', 'role' ).annotate_depth( limit=0 @@ -384,7 +384,7 @@ class AggregateBulkImportView(PermissionRequiredMixin, BulkImportView): class AggregateBulkEditView(PermissionRequiredMixin, BulkEditView): permission_required = 'ipam.change_aggregate' - queryset = Aggregate.objects.select_related('rir') + queryset = Aggregate.objects.prefetch_related('rir') filter = filters.AggregateFilter table = tables.AggregateTable form = forms.AggregateBulkEditForm @@ -393,7 +393,7 @@ class AggregateBulkEditView(PermissionRequiredMixin, BulkEditView): class AggregateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'ipam.delete_aggregate' - queryset = Aggregate.objects.select_related('rir') + queryset = Aggregate.objects.prefetch_related('rir') filter = filters.AggregateFilter table = tables.AggregateTable default_return_url = 'ipam:aggregate_list' @@ -441,7 +441,7 @@ class RoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): class PrefixListView(PermissionRequiredMixin, ObjectListView): permission_required = 'ipam.view_prefix' - queryset = Prefix.objects.select_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role') + queryset = Prefix.objects.prefetch_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role') filter = filters.PrefixFilter filter_form = forms.PrefixFilterForm table = tables.PrefixDetailTable @@ -458,7 +458,7 @@ class PrefixView(PermissionRequiredMixin, View): def get(self, request, pk): - prefix = get_object_or_404(Prefix.objects.select_related( + prefix = get_object_or_404(Prefix.objects.prefetch_related( 'vrf', 'site__region', 'tenant__group', 'vlan__group', 'role' ), pk=pk) @@ -472,7 +472,7 @@ class PrefixView(PermissionRequiredMixin, View): Q(vrf=prefix.vrf) | Q(vrf__isnull=True) ).filter( prefix__net_contains=str(prefix.prefix) - ).select_related( + ).prefetch_related( 'site', 'role' ).annotate_depth() parent_prefix_table = tables.PrefixTable(list(parent_prefixes), orderable=False) @@ -483,7 +483,7 @@ class PrefixView(PermissionRequiredMixin, View): vrf=prefix.vrf, prefix=str(prefix.prefix) ).exclude( pk=prefix.pk - ).select_related( + ).prefetch_related( 'site', 'role' ) duplicate_prefix_table = tables.PrefixTable(list(duplicate_prefixes), orderable=False) @@ -505,7 +505,7 @@ class PrefixPrefixesView(PermissionRequiredMixin, View): prefix = get_object_or_404(Prefix.objects.all(), pk=pk) # Child prefixes table - child_prefixes = prefix.get_child_prefixes().select_related( + child_prefixes = prefix.get_child_prefixes().prefetch_related( 'site', 'vlan', 'role', ).annotate_depth(limit=0) @@ -548,7 +548,7 @@ class PrefixIPAddressesView(PermissionRequiredMixin, View): prefix = get_object_or_404(Prefix.objects.all(), pk=pk) # Find all IPAddresses belonging to this Prefix - ipaddresses = prefix.get_child_ips().select_related( + ipaddresses = prefix.get_child_ips().prefetch_related( 'vrf', 'interface__device', 'primary_ip4_for', 'primary_ip6_for' ) ipaddresses = add_available_ipaddresses(prefix.prefix, ipaddresses, prefix.is_pool) @@ -608,7 +608,7 @@ class PrefixBulkImportView(PermissionRequiredMixin, BulkImportView): class PrefixBulkEditView(PermissionRequiredMixin, BulkEditView): permission_required = 'ipam.change_prefix' - queryset = Prefix.objects.select_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role') + queryset = Prefix.objects.prefetch_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role') filter = filters.PrefixFilter table = tables.PrefixTable form = forms.PrefixBulkEditForm @@ -617,7 +617,7 @@ class PrefixBulkEditView(PermissionRequiredMixin, BulkEditView): class PrefixBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'ipam.delete_prefix' - queryset = Prefix.objects.select_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role') + queryset = Prefix.objects.prefetch_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role') filter = filters.PrefixFilter table = tables.PrefixTable default_return_url = 'ipam:prefix_list' @@ -629,10 +629,8 @@ class PrefixBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): class IPAddressListView(PermissionRequiredMixin, ObjectListView): permission_required = 'ipam.view_ipaddress' - queryset = IPAddress.objects.select_related( - 'vrf__tenant', 'tenant', 'nat_inside' - ).prefetch_related( - 'interface__device', 'interface__virtual_machine' + queryset = IPAddress.objects.prefetch_related( + 'vrf__tenant', 'tenant', 'nat_inside', 'interface__device', 'interface__virtual_machine' ) filter = filters.IPAddressFilter filter_form = forms.IPAddressFilterForm @@ -645,12 +643,12 @@ class IPAddressView(PermissionRequiredMixin, View): def get(self, request, pk): - ipaddress = get_object_or_404(IPAddress.objects.select_related('vrf__tenant', 'tenant'), pk=pk) + ipaddress = get_object_or_404(IPAddress.objects.prefetch_related('vrf__tenant', 'tenant'), pk=pk) # Parent prefixes table parent_prefixes = Prefix.objects.filter( vrf=ipaddress.vrf, prefix__net_contains=str(ipaddress.address.ip) - ).select_related( + ).prefetch_related( 'site', 'role' ) parent_prefixes_table = tables.PrefixTable(list(parent_prefixes), orderable=False) @@ -661,10 +659,8 @@ class IPAddressView(PermissionRequiredMixin, View): vrf=ipaddress.vrf, address=str(ipaddress.address) ).exclude( pk=ipaddress.pk - ).select_related( - 'nat_inside' ).prefetch_related( - 'interface__device' + 'nat_inside', 'interface__device' ) # Exclude anycast IPs if this IP is anycast if ipaddress.role == IPADDRESS_ROLE_ANYCAST: @@ -742,7 +738,7 @@ class IPAddressAssignView(PermissionRequiredMixin, View): if form.is_valid(): - queryset = IPAddress.objects.select_related( + queryset = IPAddress.objects.prefetch_related( 'vrf', 'tenant', 'interface__device', 'interface__virtual_machine' ).filter( vrf=form.cleaned_data['vrf'], @@ -781,7 +777,7 @@ class IPAddressBulkImportView(PermissionRequiredMixin, BulkImportView): class IPAddressBulkEditView(PermissionRequiredMixin, BulkEditView): permission_required = 'ipam.change_ipaddress' - queryset = IPAddress.objects.select_related('vrf__tenant', 'tenant').prefetch_related('interface__device') + queryset = IPAddress.objects.prefetch_related('vrf__tenant', 'tenant').prefetch_related('interface__device') filter = filters.IPAddressFilter table = tables.IPAddressTable form = forms.IPAddressBulkEditForm @@ -790,7 +786,7 @@ class IPAddressBulkEditView(PermissionRequiredMixin, BulkEditView): class IPAddressBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'ipam.delete_ipaddress' - queryset = IPAddress.objects.select_related('vrf__tenant', 'tenant').prefetch_related('interface__device') + queryset = IPAddress.objects.prefetch_related('vrf__tenant', 'tenant').prefetch_related('interface__device') filter = filters.IPAddressFilter table = tables.IPAddressTable default_return_url = 'ipam:ipaddress_list' @@ -802,7 +798,7 @@ class IPAddressBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): class VLANGroupListView(PermissionRequiredMixin, ObjectListView): permission_required = 'ipam.view_vlangroup' - queryset = VLANGroup.objects.select_related('site').annotate(vlan_count=Count('vlans')) + queryset = VLANGroup.objects.prefetch_related('site').annotate(vlan_count=Count('vlans')) filter = filters.VLANGroupFilter filter_form = forms.VLANGroupFilterForm table = tables.VLANGroupTable @@ -829,7 +825,7 @@ class VLANGroupBulkImportView(PermissionRequiredMixin, BulkImportView): class VLANGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'ipam.delete_vlangroup' - queryset = VLANGroup.objects.select_related('site').annotate(vlan_count=Count('vlans')) + queryset = VLANGroup.objects.prefetch_related('site').annotate(vlan_count=Count('vlans')) filter = filters.VLANGroupFilter table = tables.VLANGroupTable default_return_url = 'ipam:vlangroup_list' @@ -878,7 +874,7 @@ class VLANGroupVLANsView(PermissionRequiredMixin, View): class VLANListView(PermissionRequiredMixin, ObjectListView): permission_required = 'ipam.view_vlan' - queryset = VLAN.objects.select_related('site', 'group', 'tenant', 'role').prefetch_related('prefixes') + queryset = VLAN.objects.prefetch_related('site', 'group', 'tenant', 'role').prefetch_related('prefixes') filter = filters.VLANFilter filter_form = forms.VLANFilterForm table = tables.VLANDetailTable @@ -890,10 +886,10 @@ class VLANView(PermissionRequiredMixin, View): def get(self, request, pk): - vlan = get_object_or_404(VLAN.objects.select_related( + vlan = get_object_or_404(VLAN.objects.prefetch_related( 'site__region', 'tenant__group', 'role' ), pk=pk) - prefixes = Prefix.objects.filter(vlan=vlan).select_related('vrf', 'site', 'role') + prefixes = Prefix.objects.filter(vlan=vlan).prefetch_related('vrf', 'site', 'role') prefix_table = tables.PrefixTable(list(prefixes), orderable=False) prefix_table.exclude = ('vlan',) @@ -909,7 +905,7 @@ class VLANMembersView(PermissionRequiredMixin, View): def get(self, request, pk): vlan = get_object_or_404(VLAN.objects.all(), pk=pk) - members = vlan.get_members().select_related('device', 'virtual_machine') + members = vlan.get_members().prefetch_related('device', 'virtual_machine') members_table = tables.VLANMemberTable(members) @@ -953,7 +949,7 @@ class VLANBulkImportView(PermissionRequiredMixin, BulkImportView): class VLANBulkEditView(PermissionRequiredMixin, BulkEditView): permission_required = 'ipam.change_vlan' - queryset = VLAN.objects.select_related('site', 'group', 'tenant', 'role') + queryset = VLAN.objects.prefetch_related('site', 'group', 'tenant', 'role') filter = filters.VLANFilter table = tables.VLANTable form = forms.VLANBulkEditForm @@ -962,7 +958,7 @@ class VLANBulkEditView(PermissionRequiredMixin, BulkEditView): class VLANBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'ipam.delete_vlan' - queryset = VLAN.objects.select_related('site', 'group', 'tenant', 'role') + queryset = VLAN.objects.prefetch_related('site', 'group', 'tenant', 'role') filter = filters.VLANFilter table = tables.VLANTable default_return_url = 'ipam:vlan_list' @@ -974,7 +970,7 @@ class VLANBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): class ServiceListView(PermissionRequiredMixin, ObjectListView): permission_required = 'ipam.view_service' - queryset = Service.objects.select_related('device', 'virtual_machine') + queryset = Service.objects.prefetch_related('device', 'virtual_machine') filter = filters.ServiceFilter filter_form = forms.ServiceFilterForm table = tables.ServiceTable @@ -1021,7 +1017,7 @@ class ServiceDeleteView(PermissionRequiredMixin, ObjectDeleteView): class ServiceBulkEditView(PermissionRequiredMixin, BulkEditView): permission_required = 'ipam.change_service' - queryset = Service.objects.select_related('device', 'virtual_machine') + queryset = Service.objects.prefetch_related('device', 'virtual_machine') filter = filters.ServiceFilter table = tables.ServiceTable form = forms.ServiceBulkEditForm @@ -1030,7 +1026,7 @@ class ServiceBulkEditView(PermissionRequiredMixin, BulkEditView): class ServiceBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'ipam.delete_service' - queryset = Service.objects.select_related('device', 'virtual_machine') + queryset = Service.objects.prefetch_related('device', 'virtual_machine') filter = filters.ServiceFilter table = tables.ServiceTable default_return_url = 'ipam:service_list' diff --git a/netbox/netbox/api.py b/netbox/netbox/api.py index d20ac6563..0e04719f9 100644 --- a/netbox/netbox/api.py +++ b/netbox/netbox/api.py @@ -37,7 +37,7 @@ class TokenAuthentication(authentication.TokenAuthentication): def authenticate_credentials(self, key): model = self.get_model() try: - token = model.objects.select_related('user').get(key=key) + token = model.objects.prefetch_related('user').get(key=key) except model.DoesNotExist: raise exceptions.AuthenticationFailed("Invalid token") diff --git a/netbox/netbox/views.py b/netbox/netbox/views.py index 146bba6db..b26d45db5 100644 --- a/netbox/netbox/views.py +++ b/netbox/netbox/views.py @@ -46,38 +46,38 @@ SEARCH_TYPES = OrderedDict(( 'url': 'circuits:provider_list', }), ('circuit', { - 'queryset': Circuit.objects.select_related('type', 'provider', 'tenant').prefetch_related('terminations__site'), + 'queryset': Circuit.objects.prefetch_related('type', 'provider', 'tenant').prefetch_related('terminations__site'), 'filter': CircuitFilter, 'table': CircuitTable, 'url': 'circuits:circuit_list', }), # DCIM ('site', { - 'queryset': Site.objects.select_related('region', 'tenant'), + 'queryset': Site.objects.prefetch_related('region', 'tenant'), 'filter': SiteFilter, 'table': SiteTable, 'url': 'dcim:site_list', }), ('rack', { - 'queryset': Rack.objects.select_related('site', 'group', 'tenant', 'role'), + 'queryset': Rack.objects.prefetch_related('site', 'group', 'tenant', 'role'), 'filter': RackFilter, 'table': RackTable, 'url': 'dcim:rack_list', }), ('rackgroup', { - 'queryset': RackGroup.objects.select_related('site').annotate(rack_count=Count('racks')), + 'queryset': RackGroup.objects.prefetch_related('site').annotate(rack_count=Count('racks')), 'filter': RackGroupFilter, 'table': RackGroupTable, 'url': 'dcim:rackgroup_list', }), ('devicetype', { - 'queryset': DeviceType.objects.select_related('manufacturer').annotate(instance_count=Count('instances')), + 'queryset': DeviceType.objects.prefetch_related('manufacturer').annotate(instance_count=Count('instances')), 'filter': DeviceTypeFilter, 'table': DeviceTypeTable, 'url': 'dcim:devicetype_list', }), ('device', { - 'queryset': Device.objects.select_related( + 'queryset': Device.objects.prefetch_related( 'device_type__manufacturer', 'device_role', 'tenant', 'site', 'rack', 'primary_ip4', 'primary_ip6', ), 'filter': DeviceFilter, @@ -85,7 +85,7 @@ SEARCH_TYPES = OrderedDict(( 'url': 'dcim:device_list', }), ('virtualchassis', { - 'queryset': VirtualChassis.objects.select_related('master').annotate(member_count=Count('members')), + 'queryset': VirtualChassis.objects.prefetch_related('master').annotate(member_count=Count('members')), 'filter': VirtualChassisFilter, 'table': VirtualChassisTable, 'url': 'dcim:virtualchassis_list', @@ -104,58 +104,58 @@ SEARCH_TYPES = OrderedDict(( }), # IPAM ('vrf', { - 'queryset': VRF.objects.select_related('tenant'), + 'queryset': VRF.objects.prefetch_related('tenant'), 'filter': VRFFilter, 'table': VRFTable, 'url': 'ipam:vrf_list', }), ('aggregate', { - 'queryset': Aggregate.objects.select_related('rir'), + 'queryset': Aggregate.objects.prefetch_related('rir'), 'filter': AggregateFilter, 'table': AggregateTable, 'url': 'ipam:aggregate_list', }), ('prefix', { - 'queryset': Prefix.objects.select_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role'), + 'queryset': Prefix.objects.prefetch_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role'), 'filter': PrefixFilter, 'table': PrefixTable, 'url': 'ipam:prefix_list', }), ('ipaddress', { - 'queryset': IPAddress.objects.select_related('vrf__tenant', 'tenant'), + 'queryset': IPAddress.objects.prefetch_related('vrf__tenant', 'tenant'), 'filter': IPAddressFilter, 'table': IPAddressTable, 'url': 'ipam:ipaddress_list', }), ('vlan', { - 'queryset': VLAN.objects.select_related('site', 'group', 'tenant', 'role'), + 'queryset': VLAN.objects.prefetch_related('site', 'group', 'tenant', 'role'), 'filter': VLANFilter, 'table': VLANTable, 'url': 'ipam:vlan_list', }), # Secrets ('secret', { - 'queryset': Secret.objects.select_related('role', 'device'), + 'queryset': Secret.objects.prefetch_related('role', 'device'), 'filter': SecretFilter, 'table': SecretTable, 'url': 'secrets:secret_list', }), # Tenancy ('tenant', { - 'queryset': Tenant.objects.select_related('group'), + 'queryset': Tenant.objects.prefetch_related('group'), 'filter': TenantFilter, 'table': TenantTable, 'url': 'tenancy:tenant_list', }), # Virtualization ('cluster', { - 'queryset': Cluster.objects.select_related('type', 'group'), + 'queryset': Cluster.objects.prefetch_related('type', 'group'), 'filter': ClusterFilter, 'table': ClusterTable, 'url': 'virtualization:cluster_list', }), ('virtualmachine', { - 'queryset': VirtualMachine.objects.select_related( + 'queryset': VirtualMachine.objects.prefetch_related( 'cluster', 'tenant', 'platform', 'primary_ip4', 'primary_ip6', ), 'filter': VirtualMachineFilter, @@ -224,7 +224,7 @@ class HomeView(View): 'stats': stats, 'topology_maps': TopologyMap.objects.filter(site__isnull=True), 'report_results': ReportResult.objects.order_by('-created')[:10], - 'changelog': ObjectChange.objects.select_related('user', 'changed_object_type')[:50] + 'changelog': ObjectChange.objects.prefetch_related('user', 'changed_object_type')[:50] }) diff --git a/netbox/secrets/api/views.py b/netbox/secrets/api/views.py index 88537b649..ee2b87a1f 100644 --- a/netbox/secrets/api/views.py +++ b/netbox/secrets/api/views.py @@ -46,10 +46,8 @@ class SecretRoleViewSet(ModelViewSet): # class SecretViewSet(ModelViewSet): - queryset = Secret.objects.select_related( - 'device__primary_ip4', 'device__primary_ip6', 'role', - ).prefetch_related( - 'role__users', 'role__groups', 'tags', + queryset = Secret.objects.prefetch_related( + 'device__primary_ip4', 'device__primary_ip6', 'role', 'role__users', 'role__groups', 'tags', ) serializer_class = serializers.SecretSerializer filterset_class = filters.SecretFilter diff --git a/netbox/secrets/views.py b/netbox/secrets/views.py index b30987935..001a09696 100644 --- a/netbox/secrets/views.py +++ b/netbox/secrets/views.py @@ -69,7 +69,7 @@ class SecretRoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): class SecretListView(PermissionRequiredMixin, ObjectListView): permission_required = 'secrets.view_secret' - queryset = Secret.objects.select_related('role', 'device') + queryset = Secret.objects.prefetch_related('role', 'device') filter = filters.SecretFilter filter_form = forms.SecretFilterForm table = tables.SecretTable @@ -247,7 +247,7 @@ class SecretBulkImportView(BulkImportView): class SecretBulkEditView(PermissionRequiredMixin, BulkEditView): permission_required = 'secrets.change_secret' - queryset = Secret.objects.select_related('role', 'device') + queryset = Secret.objects.prefetch_related('role', 'device') filter = filters.SecretFilter table = tables.SecretTable form = forms.SecretBulkEditForm @@ -256,7 +256,7 @@ class SecretBulkEditView(PermissionRequiredMixin, BulkEditView): class SecretBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'secrets.delete_secret' - queryset = Secret.objects.select_related('role', 'device') + queryset = Secret.objects.prefetch_related('role', 'device') filter = filters.SecretFilter table = tables.SecretTable default_return_url = 'secrets:secret_list' diff --git a/netbox/tenancy/api/views.py b/netbox/tenancy/api/views.py index b79a076f1..8783a2af7 100644 --- a/netbox/tenancy/api/views.py +++ b/netbox/tenancy/api/views.py @@ -35,10 +35,8 @@ class TenantGroupViewSet(ModelViewSet): # class TenantViewSet(CustomFieldModelViewSet): - queryset = Tenant.objects.select_related( - 'group' - ).prefetch_related( - 'tags' + queryset = Tenant.objects.prefetch_related( + 'group', 'tags' ).annotate( circuit_count=get_subquery(Circuit, 'tenant'), device_count=get_subquery(Device, 'tenant'), diff --git a/netbox/tenancy/views.py b/netbox/tenancy/views.py index 5d43309de..965ae2853 100644 --- a/netbox/tenancy/views.py +++ b/netbox/tenancy/views.py @@ -56,7 +56,7 @@ class TenantGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): class TenantListView(PermissionRequiredMixin, ObjectListView): permission_required = 'tenancy.view_tenant' - queryset = Tenant.objects.select_related('group') + queryset = Tenant.objects.prefetch_related('group') filter = filters.TenantFilter filter_form = forms.TenantFilterForm table = tables.TenantTable @@ -115,7 +115,7 @@ class TenantBulkImportView(PermissionRequiredMixin, BulkImportView): class TenantBulkEditView(PermissionRequiredMixin, BulkEditView): permission_required = 'tenancy.change_tenant' - queryset = Tenant.objects.select_related('group') + queryset = Tenant.objects.prefetch_related('group') filter = filters.TenantFilter table = tables.TenantTable form = forms.TenantBulkEditForm @@ -124,7 +124,7 @@ class TenantBulkEditView(PermissionRequiredMixin, BulkEditView): class TenantBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'tenancy.delete_tenant' - queryset = Tenant.objects.select_related('group') + queryset = Tenant.objects.prefetch_related('group') filter = filters.TenantFilter table = tables.TenantTable default_return_url = 'tenancy:tenant_list' diff --git a/netbox/virtualization/api/views.py b/netbox/virtualization/api/views.py index 552d5e93f..f6d7f1230 100644 --- a/netbox/virtualization/api/views.py +++ b/netbox/virtualization/api/views.py @@ -40,10 +40,8 @@ class ClusterGroupViewSet(ModelViewSet): class ClusterViewSet(CustomFieldModelViewSet): - queryset = Cluster.objects.select_related( - 'type', 'group', 'site', - ).prefetch_related( - 'tags' + queryset = Cluster.objects.prefetch_related( + 'type', 'group', 'site', 'tags' ).annotate( device_count=get_subquery(Device, 'cluster'), virtualmachine_count=get_subquery(VirtualMachine, 'cluster') @@ -57,9 +55,9 @@ class ClusterViewSet(CustomFieldModelViewSet): # class VirtualMachineViewSet(CustomFieldModelViewSet): - queryset = VirtualMachine.objects.select_related( - 'cluster__site', 'role', 'tenant', 'platform', 'primary_ip4', 'primary_ip6' - ).prefetch_related('tags') + queryset = VirtualMachine.objects.prefetch_related( + 'cluster__site', 'role', 'tenant', 'platform', 'primary_ip4', 'primary_ip6', 'tags' + ) filterset_class = filters.VirtualMachineFilter def get_serializer_class(self): @@ -86,7 +84,9 @@ class VirtualMachineViewSet(CustomFieldModelViewSet): class InterfaceViewSet(ModelViewSet): queryset = Interface.objects.filter( virtual_machine__isnull=False - ).select_related('virtual_machine').prefetch_related('tags') + ).prefetch_related( + 'virtual_machine', 'tags' + ) serializer_class = serializers.InterfaceSerializer filterset_class = filters.InterfaceFilter diff --git a/netbox/virtualization/forms.py b/netbox/virtualization/forms.py index 6ea00ee3e..15b0bca47 100644 --- a/netbox/virtualization/forms.py +++ b/netbox/virtualization/forms.py @@ -376,7 +376,7 @@ class VirtualMachineForm(BootstrapMixin, TenancyForm, CustomFieldForm): for family in [4, 6]: ip_choices = [(None, '---------')] # Collect interface IPs - interface_ips = IPAddress.objects.select_related('interface').filter( + interface_ips = IPAddress.objects.prefetch_related('interface').filter( family=family, interface__virtual_machine=self.instance ) if interface_ips: @@ -386,7 +386,7 @@ class VirtualMachineForm(BootstrapMixin, TenancyForm, CustomFieldForm): ]) ) # Collect NAT IPs - nat_ips = IPAddress.objects.select_related('nat_inside').filter( + nat_ips = IPAddress.objects.prefetch_related('nat_inside').filter( family=family, nat_inside__interface__virtual_machine=self.instance ) if nat_ips: diff --git a/netbox/virtualization/views.py b/netbox/virtualization/views.py index 712c7acb5..06a39e651 100644 --- a/netbox/virtualization/views.py +++ b/netbox/virtualization/views.py @@ -96,7 +96,7 @@ class ClusterGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): class ClusterListView(PermissionRequiredMixin, ObjectListView): permission_required = 'virtualization.view_cluster' - queryset = Cluster.objects.select_related('type', 'group', 'site') + queryset = Cluster.objects.prefetch_related('type', 'group', 'site') table = tables.ClusterTable filter = filters.ClusterFilter filter_form = forms.ClusterFilterForm @@ -109,7 +109,7 @@ class ClusterView(PermissionRequiredMixin, View): def get(self, request, pk): cluster = get_object_or_404(Cluster, pk=pk) - devices = Device.objects.filter(cluster=cluster).select_related( + devices = Device.objects.filter(cluster=cluster).prefetch_related( 'site', 'rack', 'tenant', 'device_type__manufacturer' ) device_table = DeviceTable(list(devices), orderable=False) @@ -148,7 +148,7 @@ class ClusterBulkImportView(PermissionRequiredMixin, BulkImportView): class ClusterBulkEditView(PermissionRequiredMixin, BulkEditView): permission_required = 'virtualization.change_cluster' - queryset = Cluster.objects.select_related('type', 'group', 'site') + queryset = Cluster.objects.prefetch_related('type', 'group', 'site') filter = filters.ClusterFilter table = tables.ClusterTable form = forms.ClusterBulkEditForm @@ -157,7 +157,7 @@ class ClusterBulkEditView(PermissionRequiredMixin, BulkEditView): class ClusterBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'virtualization.delete_cluster' - queryset = Cluster.objects.select_related('type', 'group', 'site') + queryset = Cluster.objects.prefetch_related('type', 'group', 'site') filter = filters.ClusterFilter table = tables.ClusterTable default_return_url = 'virtualization:cluster_list' @@ -253,7 +253,7 @@ class ClusterRemoveDevicesView(PermissionRequiredMixin, View): class VirtualMachineListView(PermissionRequiredMixin, ObjectListView): permission_required = 'virtualization.view_virtualmachine' - queryset = VirtualMachine.objects.select_related('cluster', 'tenant', 'role', 'primary_ip4', 'primary_ip6') + queryset = VirtualMachine.objects.prefetch_related('cluster', 'tenant', 'role', 'primary_ip4', 'primary_ip6') filter = filters.VirtualMachineFilter filter_form = forms.VirtualMachineFilterForm table = tables.VirtualMachineDetailTable @@ -265,7 +265,7 @@ class VirtualMachineView(PermissionRequiredMixin, View): def get(self, request, pk): - virtualmachine = get_object_or_404(VirtualMachine.objects.select_related('tenant__group'), pk=pk) + virtualmachine = get_object_or_404(VirtualMachine.objects.prefetch_related('tenant__group'), pk=pk) interfaces = Interface.objects.filter(virtual_machine=virtualmachine) services = Service.objects.filter(virtual_machine=virtualmachine) @@ -309,7 +309,7 @@ class VirtualMachineBulkImportView(PermissionRequiredMixin, BulkImportView): class VirtualMachineBulkEditView(PermissionRequiredMixin, BulkEditView): permission_required = 'virtualization.change_virtualmachine' - queryset = VirtualMachine.objects.select_related('cluster', 'tenant', 'role') + queryset = VirtualMachine.objects.prefetch_related('cluster', 'tenant', 'role') filter = filters.VirtualMachineFilter table = tables.VirtualMachineTable form = forms.VirtualMachineBulkEditForm @@ -318,7 +318,7 @@ class VirtualMachineBulkEditView(PermissionRequiredMixin, BulkEditView): class VirtualMachineBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'virtualization.delete_virtualmachine' - queryset = VirtualMachine.objects.select_related('cluster', 'tenant', 'role') + queryset = VirtualMachine.objects.prefetch_related('cluster', 'tenant', 'role') filter = filters.VirtualMachineFilter table = tables.VirtualMachineTable default_return_url = 'virtualization:virtualmachine_list'