diff --git a/netbox/circuits/api/views.py b/netbox/circuits/api/views.py index bd9431887..71db8f8f2 100644 --- a/netbox/circuits/api/views.py +++ b/netbox/circuits/api/views.py @@ -21,7 +21,7 @@ class CircuitsRootView(APIRootView): # class ProviderViewSet(NetBoxModelViewSet): - queryset = Provider.objects.prefetch_related('asns', 'tags').annotate( + queryset = Provider.objects.annotate( circuit_count=count_related(Circuit, 'provider') ) serializer_class = serializers.ProviderSerializer @@ -33,7 +33,7 @@ class ProviderViewSet(NetBoxModelViewSet): # class CircuitTypeViewSet(NetBoxModelViewSet): - queryset = CircuitType.objects.prefetch_related('tags').annotate( + queryset = CircuitType.objects.annotate( circuit_count=count_related(Circuit, 'type') ) serializer_class = serializers.CircuitTypeSerializer @@ -45,9 +45,7 @@ class CircuitTypeViewSet(NetBoxModelViewSet): # class CircuitViewSet(NetBoxModelViewSet): - queryset = Circuit.objects.prefetch_related( - 'type', 'tenant', 'provider', 'provider_account', 'termination_a', 'termination_z' - ).prefetch_related('tags') + queryset = Circuit.objects.all() serializer_class = serializers.CircuitSerializer filterset_class = filtersets.CircuitFilterSet @@ -57,12 +55,9 @@ class CircuitViewSet(NetBoxModelViewSet): # class CircuitTerminationViewSet(PassThroughPortMixin, NetBoxModelViewSet): - queryset = CircuitTermination.objects.prefetch_related( - 'circuit', 'site', 'provider_network', 'cable__terminations' - ) + queryset = CircuitTermination.objects.all() serializer_class = serializers.CircuitTerminationSerializer filterset_class = filtersets.CircuitTerminationFilterSet - brief_prefetch_fields = ['circuit'] # @@ -70,7 +65,7 @@ class CircuitTerminationViewSet(PassThroughPortMixin, NetBoxModelViewSet): # class ProviderAccountViewSet(NetBoxModelViewSet): - queryset = ProviderAccount.objects.prefetch_related('provider', 'tags') + queryset = ProviderAccount.objects.all() serializer_class = serializers.ProviderAccountSerializer filterset_class = filtersets.ProviderAccountFilterSet @@ -80,6 +75,6 @@ class ProviderAccountViewSet(NetBoxModelViewSet): # class ProviderNetworkViewSet(NetBoxModelViewSet): - queryset = ProviderNetwork.objects.prefetch_related('tags') + queryset = ProviderNetwork.objects.all() serializer_class = serializers.ProviderNetworkSerializer filterset_class = filtersets.ProviderNetworkFilterSet diff --git a/netbox/core/api/views.py b/netbox/core/api/views.py index 7bf2f87a6..9c2e23f2e 100644 --- a/netbox/core/api/views.py +++ b/netbox/core/api/views.py @@ -44,7 +44,7 @@ class DataSourceViewSet(NetBoxModelViewSet): class DataFileViewSet(NetBoxReadOnlyModelViewSet): - queryset = DataFile.objects.defer('data').prefetch_related('source') + queryset = DataFile.objects.defer('data') serializer_class = serializers.DataFileSerializer filterset_class = filtersets.DataFileFilterSet @@ -53,6 +53,6 @@ class JobViewSet(ReadOnlyModelViewSet): """ Retrieve a list of job results """ - queryset = Job.objects.prefetch_related('user') + queryset = Job.objects.all() serializer_class = serializers.JobSerializer filterset_class = filtersets.JobFilterSet diff --git a/netbox/dcim/api/views.py b/netbox/dcim/api/views.py index cd5a297c9..c429980c4 100644 --- a/netbox/dcim/api/views.py +++ b/netbox/dcim/api/views.py @@ -103,7 +103,7 @@ class RegionViewSet(MPTTLockedMixin, NetBoxModelViewSet): 'region', 'site_count', cumulative=True - ).prefetch_related('tags') + ) serializer_class = serializers.RegionSerializer filterset_class = filtersets.RegionFilterSet @@ -119,7 +119,7 @@ class SiteGroupViewSet(MPTTLockedMixin, NetBoxModelViewSet): 'group', 'site_count', cumulative=True - ).prefetch_related('tags') + ) serializer_class = serializers.SiteGroupSerializer filterset_class = filtersets.SiteGroupFilterSet @@ -129,9 +129,7 @@ class SiteGroupViewSet(MPTTLockedMixin, NetBoxModelViewSet): # class SiteViewSet(NetBoxModelViewSet): - queryset = Site.objects.prefetch_related( - 'region', 'tenant', 'asns', 'tags' - ).annotate( + queryset = Site.objects.annotate( device_count=count_related(Device, 'site'), rack_count=count_related(Rack, 'site'), prefix_count=count_related(Prefix, 'site'), @@ -160,7 +158,7 @@ class LocationViewSet(MPTTLockedMixin, NetBoxModelViewSet): 'location', 'rack_count', cumulative=True - ).prefetch_related('site', 'tags') + ) serializer_class = serializers.LocationSerializer filterset_class = filtersets.LocationFilterSet @@ -170,7 +168,7 @@ class LocationViewSet(MPTTLockedMixin, NetBoxModelViewSet): # class RackRoleViewSet(NetBoxModelViewSet): - queryset = RackRole.objects.prefetch_related('tags').annotate( + queryset = RackRole.objects.annotate( rack_count=count_related(Rack, 'role') ) serializer_class = serializers.RackRoleSerializer @@ -182,9 +180,7 @@ class RackRoleViewSet(NetBoxModelViewSet): # class RackViewSet(NetBoxModelViewSet): - queryset = Rack.objects.prefetch_related( - 'site', 'location', 'role', 'tenant', 'tags' - ).annotate( + queryset = Rack.objects.annotate( device_count=count_related(Device, 'rack'), powerfeed_count=count_related(PowerFeed, 'rack') ) @@ -249,7 +245,7 @@ class RackViewSet(NetBoxModelViewSet): # class RackReservationViewSet(NetBoxModelViewSet): - queryset = RackReservation.objects.prefetch_related('rack', 'user', 'tenant') + queryset = RackReservation.objects.all() serializer_class = serializers.RackReservationSerializer filterset_class = filtersets.RackReservationFilterSet @@ -259,7 +255,7 @@ class RackReservationViewSet(NetBoxModelViewSet): # class ManufacturerViewSet(NetBoxModelViewSet): - queryset = Manufacturer.objects.prefetch_related('tags').annotate( + queryset = Manufacturer.objects.annotate( devicetype_count=count_related(DeviceType, 'manufacturer'), inventoryitem_count=count_related(InventoryItem, 'manufacturer'), platform_count=count_related(Platform, 'manufacturer') @@ -273,21 +269,17 @@ class ManufacturerViewSet(NetBoxModelViewSet): # class DeviceTypeViewSet(NetBoxModelViewSet): - queryset = DeviceType.objects.prefetch_related('manufacturer', 'default_platform', 'tags').annotate( + queryset = DeviceType.objects.annotate( device_count=count_related(Device, 'device_type') ) serializer_class = serializers.DeviceTypeSerializer filterset_class = filtersets.DeviceTypeFilterSet - brief_prefetch_fields = ['manufacturer'] class ModuleTypeViewSet(NetBoxModelViewSet): - queryset = ModuleType.objects.prefetch_related('manufacturer', 'tags').annotate( - # module_count=count_related(Module, 'module_type') - ) + queryset = ModuleType.objects.all() serializer_class = serializers.ModuleTypeSerializer filterset_class = filtersets.ModuleTypeFilterSet - brief_prefetch_fields = ['manufacturer'] # @@ -295,61 +287,61 @@ class ModuleTypeViewSet(NetBoxModelViewSet): # class ConsolePortTemplateViewSet(NetBoxModelViewSet): - queryset = ConsolePortTemplate.objects.prefetch_related('device_type__manufacturer') + queryset = ConsolePortTemplate.objects.all() serializer_class = serializers.ConsolePortTemplateSerializer filterset_class = filtersets.ConsolePortTemplateFilterSet class ConsoleServerPortTemplateViewSet(NetBoxModelViewSet): - queryset = ConsoleServerPortTemplate.objects.prefetch_related('device_type__manufacturer') + queryset = ConsoleServerPortTemplate.objects.all() serializer_class = serializers.ConsoleServerPortTemplateSerializer filterset_class = filtersets.ConsoleServerPortTemplateFilterSet class PowerPortTemplateViewSet(NetBoxModelViewSet): - queryset = PowerPortTemplate.objects.prefetch_related('device_type__manufacturer') + queryset = PowerPortTemplate.objects.all() serializer_class = serializers.PowerPortTemplateSerializer filterset_class = filtersets.PowerPortTemplateFilterSet class PowerOutletTemplateViewSet(NetBoxModelViewSet): - queryset = PowerOutletTemplate.objects.prefetch_related('device_type__manufacturer') + queryset = PowerOutletTemplate.objects.all() serializer_class = serializers.PowerOutletTemplateSerializer filterset_class = filtersets.PowerOutletTemplateFilterSet class InterfaceTemplateViewSet(NetBoxModelViewSet): - queryset = InterfaceTemplate.objects.prefetch_related('device_type__manufacturer') + queryset = InterfaceTemplate.objects.all() serializer_class = serializers.InterfaceTemplateSerializer filterset_class = filtersets.InterfaceTemplateFilterSet class FrontPortTemplateViewSet(NetBoxModelViewSet): - queryset = FrontPortTemplate.objects.prefetch_related('device_type__manufacturer') + queryset = FrontPortTemplate.objects.all() serializer_class = serializers.FrontPortTemplateSerializer filterset_class = filtersets.FrontPortTemplateFilterSet class RearPortTemplateViewSet(NetBoxModelViewSet): - queryset = RearPortTemplate.objects.prefetch_related('device_type__manufacturer') + queryset = RearPortTemplate.objects.all() serializer_class = serializers.RearPortTemplateSerializer filterset_class = filtersets.RearPortTemplateFilterSet class ModuleBayTemplateViewSet(NetBoxModelViewSet): - queryset = ModuleBayTemplate.objects.prefetch_related('device_type__manufacturer') + queryset = ModuleBayTemplate.objects.all() serializer_class = serializers.ModuleBayTemplateSerializer filterset_class = filtersets.ModuleBayTemplateFilterSet class DeviceBayTemplateViewSet(NetBoxModelViewSet): - queryset = DeviceBayTemplate.objects.prefetch_related('device_type__manufacturer') + queryset = DeviceBayTemplate.objects.all() serializer_class = serializers.DeviceBayTemplateSerializer filterset_class = filtersets.DeviceBayTemplateFilterSet class InventoryItemTemplateViewSet(MPTTLockedMixin, NetBoxModelViewSet): - queryset = InventoryItemTemplate.objects.prefetch_related('device_type__manufacturer', 'role') + queryset = InventoryItemTemplate.objects.all() serializer_class = serializers.InventoryItemTemplateSerializer filterset_class = filtersets.InventoryItemTemplateFilterSet @@ -359,7 +351,7 @@ class InventoryItemTemplateViewSet(MPTTLockedMixin, NetBoxModelViewSet): # class DeviceRoleViewSet(NetBoxModelViewSet): - queryset = DeviceRole.objects.prefetch_related('config_template', 'tags').annotate( + queryset = DeviceRole.objects.annotate( device_count=count_related(Device, 'role'), virtualmachine_count=count_related(VirtualMachine, 'role') ) @@ -372,7 +364,7 @@ class DeviceRoleViewSet(NetBoxModelViewSet): # class PlatformViewSet(NetBoxModelViewSet): - queryset = Platform.objects.prefetch_related('config_template', 'tags').annotate( + queryset = Platform.objects.annotate( device_count=count_related(Device, 'platform'), virtualmachine_count=count_related(VirtualMachine, 'platform') ) @@ -391,8 +383,7 @@ class DeviceViewSet( NetBoxModelViewSet ): queryset = Device.objects.prefetch_related( - 'device_type__manufacturer', 'role', 'tenant', 'platform', 'site', 'location', 'rack', 'parent_bay', - 'virtual_chassis__master', 'primary_ip4__nat_outside', 'primary_ip6__nat_outside', 'config_template', 'tags', + 'parent_bay', # Referenced by DeviceSerializer.get_parent_device() ) filterset_class = filtersets.DeviceFilterSet pagination_class = StripCountAnnotationsPaginator @@ -419,9 +410,7 @@ class DeviceViewSet( class VirtualDeviceContextViewSet(NetBoxModelViewSet): - queryset = VirtualDeviceContext.objects.prefetch_related( - 'device__device_type', 'device', 'tenant', 'tags', - ).annotate( + queryset = VirtualDeviceContext.objects.annotate( interface_count=count_related(Interface, 'vdcs'), ) serializer_class = serializers.VirtualDeviceContextSerializer @@ -429,9 +418,7 @@ class VirtualDeviceContextViewSet(NetBoxModelViewSet): class ModuleViewSet(NetBoxModelViewSet): - queryset = Module.objects.prefetch_related( - 'device', 'module_bay', 'module_type__manufacturer', 'tags', - ) + queryset = Module.objects.all() serializer_class = serializers.ModuleSerializer filterset_class = filtersets.ModuleFilterSet @@ -442,49 +429,45 @@ class ModuleViewSet(NetBoxModelViewSet): class ConsolePortViewSet(PathEndpointMixin, NetBoxModelViewSet): queryset = ConsolePort.objects.prefetch_related( - 'device', 'module__module_bay', '_path', 'cable__terminations', 'tags' + '_path', 'cable__terminations', ) serializer_class = serializers.ConsolePortSerializer filterset_class = filtersets.ConsolePortFilterSet - brief_prefetch_fields = ['device'] class ConsoleServerPortViewSet(PathEndpointMixin, NetBoxModelViewSet): queryset = ConsoleServerPort.objects.prefetch_related( - 'device', 'module__module_bay', '_path', 'cable__terminations', 'tags' + '_path', 'cable__terminations', ) serializer_class = serializers.ConsoleServerPortSerializer filterset_class = filtersets.ConsoleServerPortFilterSet - brief_prefetch_fields = ['device'] class PowerPortViewSet(PathEndpointMixin, NetBoxModelViewSet): queryset = PowerPort.objects.prefetch_related( - 'device', 'module__module_bay', '_path', 'cable__terminations', 'tags' + '_path', 'cable__terminations', ) serializer_class = serializers.PowerPortSerializer filterset_class = filtersets.PowerPortFilterSet - brief_prefetch_fields = ['device'] class PowerOutletViewSet(PathEndpointMixin, NetBoxModelViewSet): queryset = PowerOutlet.objects.prefetch_related( - 'device', 'module__module_bay', '_path', 'cable__terminations', 'tags' + '_path', 'cable__terminations', ) serializer_class = serializers.PowerOutletSerializer filterset_class = filtersets.PowerOutletFilterSet - brief_prefetch_fields = ['device'] class InterfaceViewSet(PathEndpointMixin, NetBoxModelViewSet): queryset = Interface.objects.prefetch_related( - 'device', 'module__module_bay', 'parent', 'bridge', 'lag', '_path', 'cable__terminations', 'wireless_lans', - 'untagged_vlan', 'tagged_vlans', 'vrf', 'ip_addresses', 'fhrp_group_assignments', 'tags', 'l2vpn_terminations', - 'vdcs', + '_path', 'cable__terminations', + 'l2vpn_terminations', # Referenced by InterfaceSerializer.l2vpn_termination + 'ip_addresses', # Referenced by Interface.count_ipaddresses() + 'fhrp_group_assignments', # Referenced by Interface.count_fhrp_groups() ) serializer_class = serializers.InterfaceSerializer filterset_class = filtersets.InterfaceFilterSet - brief_prefetch_fields = ['device'] def get_bulk_destroy_queryset(self): # Ensure child interfaces are deleted prior to their parents @@ -493,41 +476,36 @@ class InterfaceViewSet(PathEndpointMixin, NetBoxModelViewSet): class FrontPortViewSet(PassThroughPortMixin, NetBoxModelViewSet): queryset = FrontPort.objects.prefetch_related( - 'device__device_type__manufacturer', 'module__module_bay', 'rear_port', 'cable__terminations', 'tags' + 'cable__terminations', ) serializer_class = serializers.FrontPortSerializer filterset_class = filtersets.FrontPortFilterSet - brief_prefetch_fields = ['device'] class RearPortViewSet(PassThroughPortMixin, NetBoxModelViewSet): queryset = RearPort.objects.prefetch_related( - 'device__device_type__manufacturer', 'module__module_bay', 'cable__terminations', 'tags' + 'cable__terminations', ) serializer_class = serializers.RearPortSerializer filterset_class = filtersets.RearPortFilterSet - brief_prefetch_fields = ['device'] class ModuleBayViewSet(NetBoxModelViewSet): - queryset = ModuleBay.objects.prefetch_related('tags', 'installed_module') + queryset = ModuleBay.objects.all() serializer_class = serializers.ModuleBaySerializer filterset_class = filtersets.ModuleBayFilterSet - brief_prefetch_fields = ['device'] class DeviceBayViewSet(NetBoxModelViewSet): - queryset = DeviceBay.objects.prefetch_related('installed_device', 'tags') + queryset = DeviceBay.objects.all() serializer_class = serializers.DeviceBaySerializer filterset_class = filtersets.DeviceBayFilterSet - brief_prefetch_fields = ['device'] class InventoryItemViewSet(MPTTLockedMixin, NetBoxModelViewSet): - queryset = InventoryItem.objects.prefetch_related('device', 'manufacturer', 'tags') + queryset = InventoryItem.objects.all() serializer_class = serializers.InventoryItemSerializer filterset_class = filtersets.InventoryItemFilterSet - brief_prefetch_fields = ['device'] # @@ -535,7 +513,7 @@ class InventoryItemViewSet(MPTTLockedMixin, NetBoxModelViewSet): # class InventoryItemRoleViewSet(NetBoxModelViewSet): - queryset = InventoryItemRole.objects.prefetch_related('tags').annotate( + queryset = InventoryItemRole.objects.annotate( inventoryitem_count=count_related(InventoryItem, 'role') ) serializer_class = serializers.InventoryItemRoleSerializer @@ -554,7 +532,7 @@ class CableViewSet(NetBoxModelViewSet): class CableTerminationViewSet(NetBoxModelViewSet): metadata_class = ContentTypeMetadata - queryset = CableTermination.objects.prefetch_related('cable', 'termination') + queryset = CableTermination.objects.all() serializer_class = serializers.CableTerminationSerializer filterset_class = filtersets.CableTerminationFilterSet @@ -564,10 +542,9 @@ class CableTerminationViewSet(NetBoxModelViewSet): # class VirtualChassisViewSet(NetBoxModelViewSet): - queryset = VirtualChassis.objects.prefetch_related('tags') + queryset = VirtualChassis.objects.all() serializer_class = serializers.VirtualChassisSerializer filterset_class = filtersets.VirtualChassisFilterSet - brief_prefetch_fields = ['master'] # @@ -575,9 +552,7 @@ class VirtualChassisViewSet(NetBoxModelViewSet): # class PowerPanelViewSet(NetBoxModelViewSet): - queryset = PowerPanel.objects.prefetch_related( - 'site', 'location' - ).annotate( + queryset = PowerPanel.objects.annotate( powerfeed_count=count_related(PowerFeed, 'power_panel') ) serializer_class = serializers.PowerPanelSerializer @@ -590,7 +565,7 @@ class PowerPanelViewSet(NetBoxModelViewSet): class PowerFeedViewSet(PathEndpointMixin, NetBoxModelViewSet): queryset = PowerFeed.objects.prefetch_related( - 'power_panel', 'rack', '_path', 'cable__terminations', 'tags' + '_path', 'cable__terminations', ) serializer_class = serializers.PowerFeedSerializer filterset_class = filtersets.PowerFeedFilterSet diff --git a/netbox/extras/api/views.py b/netbox/extras/api/views.py index 8f5841da1..32a569d52 100644 --- a/netbox/extras/api/views.py +++ b/netbox/extras/api/views.py @@ -115,7 +115,7 @@ class CustomLinkViewSet(NetBoxModelViewSet): class ExportTemplateViewSet(SyncedDataMixin, NetBoxModelViewSet): metadata_class = ContentTypeMetadata - queryset = ExportTemplate.objects.prefetch_related('data_source', 'data_file') + queryset = ExportTemplate.objects.all() serializer_class = serializers.ExportTemplateSerializer filterset_class = filtersets.ExportTemplateFilterSet @@ -181,10 +181,7 @@ class JournalEntryViewSet(NetBoxModelViewSet): # class ConfigContextViewSet(SyncedDataMixin, NetBoxModelViewSet): - queryset = ConfigContext.objects.prefetch_related( - 'regions', 'site_groups', 'sites', 'locations', 'roles', 'platforms', 'tenant_groups', 'tenants', 'data_source', - 'data_file', - ) + queryset = ConfigContext.objects.all() serializer_class = serializers.ConfigContextSerializer filterset_class = filtersets.ConfigContextFilterSet @@ -194,7 +191,7 @@ class ConfigContextViewSet(SyncedDataMixin, NetBoxModelViewSet): # class ConfigTemplateViewSet(SyncedDataMixin, ConfigTemplateRenderMixin, NetBoxModelViewSet): - queryset = ConfigTemplate.objects.prefetch_related('data_source', 'data_file') + queryset = ConfigTemplate.objects.all() serializer_class = serializers.ConfigTemplateSerializer filterset_class = filtersets.ConfigTemplateFilterSet @@ -312,7 +309,7 @@ class ObjectChangeViewSet(ReadOnlyModelViewSet): Retrieve a list of recent changes. """ metadata_class = ContentTypeMetadata - queryset = ObjectChange.objects.valid_models().prefetch_related('user') + queryset = ObjectChange.objects.valid_models() serializer_class = serializers.ObjectChangeSerializer filterset_class = filtersets.ObjectChangeFilterSet diff --git a/netbox/ipam/api/nested_serializers.py b/netbox/ipam/api/nested_serializers.py index 17d8d74a7..c012eca6d 100644 --- a/netbox/ipam/api/nested_serializers.py +++ b/netbox/ipam/api/nested_serializers.py @@ -116,10 +116,11 @@ class NestedFHRPGroupSerializer(WritableNestedSerializer): class NestedFHRPGroupAssignmentSerializer(WritableNestedSerializer): url = serializers.HyperlinkedIdentityField(view_name='ipam-api:fhrpgroupassignment-detail') + group = NestedFHRPGroupSerializer() class Meta: model = models.FHRPGroupAssignment - fields = ['id', 'url', 'display', 'interface_type', 'interface_id', 'group_id', 'priority'] + fields = ['id', 'url', 'display', 'group', 'interface_type', 'interface_id', 'priority'] # diff --git a/netbox/ipam/api/views.py b/netbox/ipam/api/views.py index 4439e82b4..c3594bdcf 100644 --- a/netbox/ipam/api/views.py +++ b/netbox/ipam/api/views.py @@ -39,13 +39,13 @@ class IPAMRootView(APIRootView): # class ASNRangeViewSet(NetBoxModelViewSet): - queryset = ASNRange.objects.prefetch_related('tenant', 'rir').all() + queryset = ASNRange.objects.all() serializer_class = serializers.ASNRangeSerializer filterset_class = filtersets.ASNRangeFilterSet class ASNViewSet(NetBoxModelViewSet): - queryset = ASN.objects.prefetch_related('tenant', 'rir').annotate( + queryset = ASN.objects.annotate( site_count=count_related(Site, 'asns'), provider_count=count_related(Provider, 'asns') ) @@ -54,9 +54,7 @@ class ASNViewSet(NetBoxModelViewSet): class VRFViewSet(NetBoxModelViewSet): - queryset = VRF.objects.prefetch_related('tenant').prefetch_related( - 'import_targets', 'export_targets', 'tags' - ).annotate( + queryset = VRF.objects.annotate( ipaddress_count=count_related(IPAddress, 'vrf'), prefix_count=count_related(Prefix, 'vrf') ) @@ -65,7 +63,7 @@ class VRFViewSet(NetBoxModelViewSet): class RouteTargetViewSet(NetBoxModelViewSet): - queryset = RouteTarget.objects.prefetch_related('tenant').prefetch_related('tags') + queryset = RouteTarget.objects.all() serializer_class = serializers.RouteTargetSerializer filterset_class = filtersets.RouteTargetFilterSet @@ -73,13 +71,13 @@ class RouteTargetViewSet(NetBoxModelViewSet): class RIRViewSet(NetBoxModelViewSet): queryset = RIR.objects.annotate( aggregate_count=count_related(Aggregate, 'rir') - ).prefetch_related('tags') + ) serializer_class = serializers.RIRSerializer filterset_class = filtersets.RIRFilterSet class AggregateViewSet(NetBoxModelViewSet): - queryset = Aggregate.objects.prefetch_related('rir').prefetch_related('tags') + queryset = Aggregate.objects.all() serializer_class = serializers.AggregateSerializer filterset_class = filtersets.AggregateFilterSet @@ -88,15 +86,13 @@ class RoleViewSet(NetBoxModelViewSet): queryset = Role.objects.annotate( prefix_count=count_related(Prefix, 'role'), vlan_count=count_related(VLAN, 'role') - ).prefetch_related('tags') + ) serializer_class = serializers.RoleSerializer filterset_class = filtersets.RoleFilterSet class PrefixViewSet(NetBoxModelViewSet): - queryset = Prefix.objects.prefetch_related( - 'site', 'vrf__tenant', 'tenant', 'vlan', 'role', 'tags' - ) + queryset = Prefix.objects.all() serializer_class = serializers.PrefixSerializer filterset_class = filtersets.PrefixFilterSet @@ -109,7 +105,7 @@ class PrefixViewSet(NetBoxModelViewSet): class IPRangeViewSet(NetBoxModelViewSet): - queryset = IPRange.objects.prefetch_related('vrf', 'role', 'tenant', 'tags') + queryset = IPRange.objects.all() serializer_class = serializers.IPRangeSerializer filterset_class = filtersets.IPRangeFilterSet @@ -117,9 +113,7 @@ class IPRangeViewSet(NetBoxModelViewSet): class IPAddressViewSet(NetBoxModelViewSet): - queryset = IPAddress.objects.prefetch_related( - 'vrf__tenant', 'tenant', 'nat_inside', 'nat_outside', 'tags', 'assigned_object' - ) + queryset = IPAddress.objects.all() serializer_class = serializers.IPAddressSerializer filterset_class = filtersets.IPAddressFilterSet @@ -137,27 +131,26 @@ class IPAddressViewSet(NetBoxModelViewSet): class FHRPGroupViewSet(NetBoxModelViewSet): - queryset = FHRPGroup.objects.prefetch_related('ip_addresses', 'tags') + queryset = FHRPGroup.objects.all() serializer_class = serializers.FHRPGroupSerializer filterset_class = filtersets.FHRPGroupFilterSet - brief_prefetch_fields = ('ip_addresses',) class FHRPGroupAssignmentViewSet(NetBoxModelViewSet): - queryset = FHRPGroupAssignment.objects.prefetch_related('group', 'interface') + queryset = FHRPGroupAssignment.objects.all() serializer_class = serializers.FHRPGroupAssignmentSerializer filterset_class = filtersets.FHRPGroupAssignmentFilterSet class VLANGroupViewSet(NetBoxModelViewSet): - queryset = VLANGroup.objects.annotate_utilization().prefetch_related('tags') + queryset = VLANGroup.objects.annotate_utilization() serializer_class = serializers.VLANGroupSerializer filterset_class = filtersets.VLANGroupFilterSet class VLANViewSet(NetBoxModelViewSet): queryset = VLAN.objects.prefetch_related( - 'site', 'group', 'tenant', 'role', 'tags' + 'l2vpn_terminations', # Referenced by VLANSerializer.l2vpn_termination ).annotate( prefix_count=count_related(Prefix, 'vlan') ) @@ -166,15 +159,13 @@ class VLANViewSet(NetBoxModelViewSet): class ServiceTemplateViewSet(NetBoxModelViewSet): - queryset = ServiceTemplate.objects.prefetch_related('tags') + queryset = ServiceTemplate.objects.all() serializer_class = serializers.ServiceTemplateSerializer filterset_class = filtersets.ServiceTemplateFilterSet class ServiceViewSet(NetBoxModelViewSet): - queryset = Service.objects.prefetch_related( - 'device', 'virtual_machine', 'tags', 'ipaddresses' - ) + queryset = Service.objects.all() serializer_class = serializers.ServiceSerializer filterset_class = filtersets.ServiceFilterSet diff --git a/netbox/ipam/tests/test_api.py b/netbox/ipam/tests/test_api.py index cb633e162..447415a69 100644 --- a/netbox/ipam/tests/test_api.py +++ b/netbox/ipam/tests/test_api.py @@ -760,7 +760,7 @@ class FHRPGroupTest(APIViewTestCases.APIViewTestCase): class FHRPGroupAssignmentTest(APIViewTestCases.APIViewTestCase): model = FHRPGroupAssignment - brief_fields = ['display', 'group_id', 'id', 'interface_id', 'interface_type', 'priority', 'url'] + brief_fields = ['display', 'group', 'id', 'interface_id', 'interface_type', 'priority', 'url'] bulk_update_data = { 'priority': 100, } diff --git a/netbox/netbox/api/serializers/base.py b/netbox/netbox/api/serializers/base.py index d513c8000..c715b2d26 100644 --- a/netbox/netbox/api/serializers/base.py +++ b/netbox/netbox/api/serializers/base.py @@ -12,6 +12,15 @@ __all__ = ( class BaseModelSerializer(serializers.ModelSerializer): display = serializers.SerializerMethodField(read_only=True) + def __init__(self, *args, requested_fields=None, **kwargs): + super().__init__(*args, **kwargs) + + # If specific fields have been requested, omit the others + if requested_fields: + for field in list(self.fields.keys()): + if field not in requested_fields: + self.fields.pop(field) + @extend_schema_field(OpenApiTypes.STR) def get_display(self, obj): return str(obj) diff --git a/netbox/netbox/api/viewsets/__init__.py b/netbox/netbox/api/viewsets/__init__.py index 522bcf77b..e9edf9311 100644 --- a/netbox/netbox/api/viewsets/__init__.py +++ b/netbox/netbox/api/viewsets/__init__.py @@ -1,4 +1,5 @@ import logging +from functools import cached_property from django.core.exceptions import ObjectDoesNotExist, PermissionDenied from django.db import transaction @@ -9,6 +10,7 @@ from rest_framework import mixins as drf_mixins from rest_framework.response import Response from rest_framework.viewsets import GenericViewSet +from utilities.api import get_prefetches_for_serializer from utilities.exceptions import AbortRequest from . import mixins @@ -40,6 +42,32 @@ class BaseViewSet(GenericViewSet): if action := HTTP_ACTIONS[request.method]: self.queryset = self.queryset.restrict(request.user, action) + def get_queryset(self): + qs = super().get_queryset() + + # Dynamically resolve prefetches for included serializer fields and attach them to the queryset + prefetch = get_prefetches_for_serializer( + self.get_serializer_class(), + fields_to_include=self.requested_fields + ) + if prefetch: + qs = qs.prefetch_related(*prefetch) + + return qs + + def get_serializer(self, *args, **kwargs): + + # If specific fields have been requested, pass them to the serializer + if self.requested_fields: + kwargs['requested_fields'] = self.requested_fields + + return super().get_serializer(*args, **kwargs) + + @cached_property + def requested_fields(self): + requested_fields = self.request.query_params.get('fields') + return requested_fields.split(',') if requested_fields else [] + class NetBoxReadOnlyModelViewSet( mixins.BriefModeMixin, diff --git a/netbox/netbox/api/viewsets/mixins.py b/netbox/netbox/api/viewsets/mixins.py index a45e0bdda..e6790e21c 100644 --- a/netbox/netbox/api/viewsets/mixins.py +++ b/netbox/netbox/api/viewsets/mixins.py @@ -30,7 +30,6 @@ class BriefModeMixin: GET /api/dcim/sites/?brief=True """ brief = False - brief_prefetch_fields = [] def initialize_request(self, request, *args, **kwargs): # Annotate whether brief mode is active @@ -64,9 +63,6 @@ class BriefModeMixin: if annotation not in serializer_class().fields: qs.query.annotations.pop(annotation) - # Clear any prefetches from the queryset and append only brief_prefetch_fields (if any) - return qs.prefetch_related(None).prefetch_related(*self.brief_prefetch_fields) - return qs diff --git a/netbox/tenancy/api/views.py b/netbox/tenancy/api/views.py index 25c0ab403..b922935ae 100644 --- a/netbox/tenancy/api/views.py +++ b/netbox/tenancy/api/views.py @@ -30,15 +30,13 @@ class TenantGroupViewSet(MPTTLockedMixin, NetBoxModelViewSet): 'group', 'tenant_count', cumulative=True - ).prefetch_related('tags') + ) serializer_class = serializers.TenantGroupSerializer filterset_class = filtersets.TenantGroupFilterSet class TenantViewSet(NetBoxModelViewSet): - queryset = Tenant.objects.prefetch_related( - 'group', 'tags' - ).annotate( + queryset = Tenant.objects.annotate( circuit_count=count_related(Circuit, 'tenant'), device_count=count_related(Device, 'tenant'), ipaddress_count=count_related(IPAddress, 'tenant'), @@ -65,24 +63,24 @@ class ContactGroupViewSet(MPTTLockedMixin, NetBoxModelViewSet): 'group', 'contact_count', cumulative=True - ).prefetch_related('tags') + ) serializer_class = serializers.ContactGroupSerializer filterset_class = filtersets.ContactGroupFilterSet class ContactRoleViewSet(NetBoxModelViewSet): - queryset = ContactRole.objects.prefetch_related('tags') + queryset = ContactRole.objects.all() serializer_class = serializers.ContactRoleSerializer filterset_class = filtersets.ContactRoleFilterSet class ContactViewSet(NetBoxModelViewSet): - queryset = Contact.objects.prefetch_related('group', 'tags') + queryset = Contact.objects.all() serializer_class = serializers.ContactSerializer filterset_class = filtersets.ContactFilterSet class ContactAssignmentViewSet(NetBoxModelViewSet): - queryset = ContactAssignment.objects.prefetch_related('content_type', 'object', 'contact', 'role', 'tags') + queryset = ContactAssignment.objects.all() serializer_class = serializers.ContactAssignmentSerializer filterset_class = filtersets.ContactAssignmentFilterSet diff --git a/netbox/users/api/views.py b/netbox/users/api/views.py index 62a32c71b..895600822 100644 --- a/netbox/users/api/views.py +++ b/netbox/users/api/views.py @@ -34,7 +34,7 @@ class UsersRootView(APIRootView): # class UserViewSet(NetBoxModelViewSet): - queryset = RestrictedQuerySet(model=get_user_model()).prefetch_related('groups').order_by('username') + queryset = RestrictedQuerySet(model=get_user_model()).order_by('username') serializer_class = serializers.UserSerializer filterset_class = filtersets.UserFilterSet @@ -50,7 +50,7 @@ class GroupViewSet(NetBoxModelViewSet): # class TokenViewSet(NetBoxModelViewSet): - queryset = Token.objects.prefetch_related('user') + queryset = Token.objects.all() serializer_class = serializers.TokenSerializer filterset_class = filtersets.TokenFilterSet @@ -86,7 +86,7 @@ class TokenProvisionView(APIView): # class ObjectPermissionViewSet(NetBoxModelViewSet): - queryset = ObjectPermission.objects.prefetch_related('object_types', 'groups', 'users') + queryset = ObjectPermission.objects.all() serializer_class = serializers.ObjectPermissionSerializer filterset_class = filtersets.ObjectPermissionFilterSet diff --git a/netbox/utilities/api.py b/netbox/utilities/api.py index b53edf53a..320d175c3 100644 --- a/netbox/utilities/api.py +++ b/netbox/utilities/api.py @@ -2,9 +2,13 @@ import platform import sys from django.conf import settings +from django.contrib.contenttypes.fields import GenericForeignKey +from django.core.exceptions import FieldDoesNotExist +from django.db.models.fields.related import ManyToOneRel, RelatedField from django.http import JsonResponse from django.urls import reverse from rest_framework import status +from rest_framework.serializers import Serializer from rest_framework.utils import formatting from netbox.api.exceptions import GraphQLTypeNotFound, SerializerNotFound @@ -12,6 +16,7 @@ from .utils import dynamic_import __all__ = ( 'get_graphql_type_for_model', + 'get_prefetches_for_serializer', 'get_serializer_for_model', 'get_view_name', 'is_api_request', @@ -89,6 +94,43 @@ def get_view_name(view, suffix=None): return name +def get_prefetches_for_serializer(serializer_class, fields_to_include=None): + """ + Compile and return a list of fields which should be prefetched on the queryset for a serializer. + """ + model = serializer_class.Meta.model + + # If specific fields are not specified, default to all + if not fields_to_include: + fields_to_include = serializer_class.Meta.fields + + prefetch_fields = [] + for field_name in fields_to_include: + serializer_field = serializer_class._declared_fields.get(field_name) + + # Determine the name of the model field referenced by the serializer field + model_field_name = field_name + if serializer_field and serializer_field.source: + model_field_name = serializer_field.source + + # If the serializer field does not map to a discrete model field, skip it. + try: + field = model._meta.get_field(model_field_name) + if isinstance(field, (RelatedField, ManyToOneRel, GenericForeignKey)): + prefetch_fields.append(field.name) + except FieldDoesNotExist: + continue + + # If this field is represented by a nested serializer, recurse to resolve prefetches + # for the related object. + if serializer_field: + if issubclass(type(serializer_field), Serializer): + for subfield in get_prefetches_for_serializer(type(serializer_field)): + prefetch_fields.append(f'{field_name}__{subfield}') + + return prefetch_fields + + def rest_api_server_error(request, *args, **kwargs): """ Handle exceptions and return a useful error message for REST API requests. diff --git a/netbox/virtualization/api/views.py b/netbox/virtualization/api/views.py index 3ba2bb97f..ff894c6dc 100644 --- a/netbox/virtualization/api/views.py +++ b/netbox/virtualization/api/views.py @@ -25,7 +25,7 @@ class VirtualizationRootView(APIRootView): class ClusterTypeViewSet(NetBoxModelViewSet): queryset = ClusterType.objects.annotate( cluster_count=count_related(Cluster, 'type') - ).prefetch_related('tags') + ) serializer_class = serializers.ClusterTypeSerializer filterset_class = filtersets.ClusterTypeFilterSet @@ -33,15 +33,13 @@ class ClusterTypeViewSet(NetBoxModelViewSet): class ClusterGroupViewSet(NetBoxModelViewSet): queryset = ClusterGroup.objects.annotate( cluster_count=count_related(Cluster, 'group') - ).prefetch_related('tags') + ) serializer_class = serializers.ClusterGroupSerializer filterset_class = filtersets.ClusterGroupFilterSet class ClusterViewSet(NetBoxModelViewSet): - queryset = Cluster.objects.prefetch_related( - 'type', 'group', 'tenant', 'site', 'tags' - ).annotate( + queryset = Cluster.objects.annotate( device_count=count_related(Device, 'cluster'), virtualmachine_count=count_related(VirtualMachine, 'cluster') ) @@ -54,10 +52,7 @@ class ClusterViewSet(NetBoxModelViewSet): # class VirtualMachineViewSet(ConfigContextQuerySetMixin, RenderConfigMixin, NetBoxModelViewSet): - queryset = VirtualMachine.objects.prefetch_related( - 'site', 'cluster', 'device', 'role', 'tenant', 'platform', 'primary_ip4', 'primary_ip6', 'config_template', - 'tags', 'virtualdisks', - ) + queryset = VirtualMachine.objects.all() filterset_class = filtersets.VirtualMachineFilterSet def get_serializer_class(self): @@ -83,12 +78,12 @@ class VirtualMachineViewSet(ConfigContextQuerySetMixin, RenderConfigMixin, NetBo class VMInterfaceViewSet(NetBoxModelViewSet): queryset = VMInterface.objects.prefetch_related( - 'virtual_machine', 'parent', 'tags', 'untagged_vlan', 'tagged_vlans', 'vrf', 'ip_addresses', - 'fhrp_group_assignments', + 'l2vpn_terminations', # Referenced by VMInterfaceSerializer.l2vpn_termination + 'ip_addresses', # Referenced by VMInterface.count_ipaddresses() + 'fhrp_group_assignments', # Referenced by VMInterface.count_fhrp_groups() ) serializer_class = serializers.VMInterfaceSerializer filterset_class = filtersets.VMInterfaceFilterSet - brief_prefetch_fields = ['virtual_machine'] def get_bulk_destroy_queryset(self): # Ensure child interfaces are deleted prior to their parents @@ -96,9 +91,6 @@ class VMInterfaceViewSet(NetBoxModelViewSet): class VirtualDiskViewSet(NetBoxModelViewSet): - queryset = VirtualDisk.objects.prefetch_related( - 'virtual_machine', 'tags', - ) + queryset = VirtualDisk.objects.all() serializer_class = serializers.VirtualDiskSerializer filterset_class = filtersets.VirtualDiskFilterSet - brief_prefetch_fields = ['virtual_machine'] diff --git a/netbox/vpn/api/views.py b/netbox/vpn/api/views.py index 58ad2f47d..5015f0618 100644 --- a/netbox/vpn/api/views.py +++ b/netbox/vpn/api/views.py @@ -42,7 +42,7 @@ class TunnelGroupViewSet(NetBoxModelViewSet): class TunnelViewSet(NetBoxModelViewSet): - queryset = Tunnel.objects.prefetch_related('ipsec_profile', 'tenant').annotate( + queryset = Tunnel.objects.annotate( terminations_count=count_related(TunnelTermination, 'tunnel') ) serializer_class = serializers.TunnelSerializer @@ -50,7 +50,7 @@ class TunnelViewSet(NetBoxModelViewSet): class TunnelTerminationViewSet(NetBoxModelViewSet): - queryset = TunnelTermination.objects.prefetch_related('tunnel') + queryset = TunnelTermination.objects.all() serializer_class = serializers.TunnelTerminationSerializer filterset_class = filtersets.TunnelTerminationFilterSet @@ -86,12 +86,12 @@ class IPSecProfileViewSet(NetBoxModelViewSet): class L2VPNViewSet(NetBoxModelViewSet): - queryset = L2VPN.objects.prefetch_related('import_targets', 'export_targets', 'tenant', 'tags') + queryset = L2VPN.objects.all() serializer_class = serializers.L2VPNSerializer filterset_class = filtersets.L2VPNFilterSet class L2VPNTerminationViewSet(NetBoxModelViewSet): - queryset = L2VPNTermination.objects.prefetch_related('assigned_object') + queryset = L2VPNTermination.objects.all() serializer_class = serializers.L2VPNTerminationSerializer filterset_class = filtersets.L2VPNTerminationFilterSet diff --git a/netbox/wireless/api/views.py b/netbox/wireless/api/views.py index a6cc9f535..999743981 100644 --- a/netbox/wireless/api/views.py +++ b/netbox/wireless/api/views.py @@ -27,12 +27,12 @@ class WirelessLANGroupViewSet(MPTTLockedMixin, NetBoxModelViewSet): class WirelessLANViewSet(NetBoxModelViewSet): - queryset = WirelessLAN.objects.prefetch_related('vlan', 'tenant', 'tags') + queryset = WirelessLAN.objects.all() serializer_class = serializers.WirelessLANSerializer filterset_class = filtersets.WirelessLANFilterSet class WirelessLinkViewSet(NetBoxModelViewSet): - queryset = WirelessLink.objects.prefetch_related('interface_a', 'interface_b', 'tenant', 'tags') + queryset = WirelessLink.objects.all() serializer_class = serializers.WirelessLinkSerializer filterset_class = filtersets.WirelessLinkFilterSet