1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

Closes #15087: Support for specifying a subset of API serializer fields (#15122)

* Enable dynamic field inclusion for REST API serializers

* Recurse through nested serializer when resolving prefetches

* Remove obsolete calls to prefetch_related() for API views

* Remove support for brief_prefetch_fields viewset attribute

* Rename query parameter

* Fixes #15133: Fix FHRP group representation on assignments endpoint under brief mode (#15134)

* Fixes #15133: Fix FHRP group representation on assignments endpoint under brief mode

* Update API test

* Restore get_queryset() on BriefModeMixin, minus prefetch logic

* get_prefetches_for_serializer() should reference serializer field source if set
This commit is contained in:
Jeremy Stretch
2024-02-14 09:28:37 -05:00
committed by GitHub
parent 72720354df
commit b3f25a400b
16 changed files with 176 additions and 152 deletions

View File

@ -21,7 +21,7 @@ class CircuitsRootView(APIRootView):
# #
class ProviderViewSet(NetBoxModelViewSet): class ProviderViewSet(NetBoxModelViewSet):
queryset = Provider.objects.prefetch_related('asns', 'tags').annotate( queryset = Provider.objects.annotate(
circuit_count=count_related(Circuit, 'provider') circuit_count=count_related(Circuit, 'provider')
) )
serializer_class = serializers.ProviderSerializer serializer_class = serializers.ProviderSerializer
@ -33,7 +33,7 @@ class ProviderViewSet(NetBoxModelViewSet):
# #
class CircuitTypeViewSet(NetBoxModelViewSet): class CircuitTypeViewSet(NetBoxModelViewSet):
queryset = CircuitType.objects.prefetch_related('tags').annotate( queryset = CircuitType.objects.annotate(
circuit_count=count_related(Circuit, 'type') circuit_count=count_related(Circuit, 'type')
) )
serializer_class = serializers.CircuitTypeSerializer serializer_class = serializers.CircuitTypeSerializer
@ -45,9 +45,7 @@ class CircuitTypeViewSet(NetBoxModelViewSet):
# #
class CircuitViewSet(NetBoxModelViewSet): class CircuitViewSet(NetBoxModelViewSet):
queryset = Circuit.objects.prefetch_related( queryset = Circuit.objects.all()
'type', 'tenant', 'provider', 'provider_account', 'termination_a', 'termination_z'
).prefetch_related('tags')
serializer_class = serializers.CircuitSerializer serializer_class = serializers.CircuitSerializer
filterset_class = filtersets.CircuitFilterSet filterset_class = filtersets.CircuitFilterSet
@ -57,12 +55,9 @@ class CircuitViewSet(NetBoxModelViewSet):
# #
class CircuitTerminationViewSet(PassThroughPortMixin, NetBoxModelViewSet): class CircuitTerminationViewSet(PassThroughPortMixin, NetBoxModelViewSet):
queryset = CircuitTermination.objects.prefetch_related( queryset = CircuitTermination.objects.all()
'circuit', 'site', 'provider_network', 'cable__terminations'
)
serializer_class = serializers.CircuitTerminationSerializer serializer_class = serializers.CircuitTerminationSerializer
filterset_class = filtersets.CircuitTerminationFilterSet filterset_class = filtersets.CircuitTerminationFilterSet
brief_prefetch_fields = ['circuit']
# #
@ -70,7 +65,7 @@ class CircuitTerminationViewSet(PassThroughPortMixin, NetBoxModelViewSet):
# #
class ProviderAccountViewSet(NetBoxModelViewSet): class ProviderAccountViewSet(NetBoxModelViewSet):
queryset = ProviderAccount.objects.prefetch_related('provider', 'tags') queryset = ProviderAccount.objects.all()
serializer_class = serializers.ProviderAccountSerializer serializer_class = serializers.ProviderAccountSerializer
filterset_class = filtersets.ProviderAccountFilterSet filterset_class = filtersets.ProviderAccountFilterSet
@ -80,6 +75,6 @@ class ProviderAccountViewSet(NetBoxModelViewSet):
# #
class ProviderNetworkViewSet(NetBoxModelViewSet): class ProviderNetworkViewSet(NetBoxModelViewSet):
queryset = ProviderNetwork.objects.prefetch_related('tags') queryset = ProviderNetwork.objects.all()
serializer_class = serializers.ProviderNetworkSerializer serializer_class = serializers.ProviderNetworkSerializer
filterset_class = filtersets.ProviderNetworkFilterSet filterset_class = filtersets.ProviderNetworkFilterSet

View File

@ -44,7 +44,7 @@ class DataSourceViewSet(NetBoxModelViewSet):
class DataFileViewSet(NetBoxReadOnlyModelViewSet): class DataFileViewSet(NetBoxReadOnlyModelViewSet):
queryset = DataFile.objects.defer('data').prefetch_related('source') queryset = DataFile.objects.defer('data')
serializer_class = serializers.DataFileSerializer serializer_class = serializers.DataFileSerializer
filterset_class = filtersets.DataFileFilterSet filterset_class = filtersets.DataFileFilterSet
@ -53,6 +53,6 @@ class JobViewSet(ReadOnlyModelViewSet):
""" """
Retrieve a list of job results Retrieve a list of job results
""" """
queryset = Job.objects.prefetch_related('user') queryset = Job.objects.all()
serializer_class = serializers.JobSerializer serializer_class = serializers.JobSerializer
filterset_class = filtersets.JobFilterSet filterset_class = filtersets.JobFilterSet

View File

@ -103,7 +103,7 @@ class RegionViewSet(MPTTLockedMixin, NetBoxModelViewSet):
'region', 'region',
'site_count', 'site_count',
cumulative=True cumulative=True
).prefetch_related('tags') )
serializer_class = serializers.RegionSerializer serializer_class = serializers.RegionSerializer
filterset_class = filtersets.RegionFilterSet filterset_class = filtersets.RegionFilterSet
@ -119,7 +119,7 @@ class SiteGroupViewSet(MPTTLockedMixin, NetBoxModelViewSet):
'group', 'group',
'site_count', 'site_count',
cumulative=True cumulative=True
).prefetch_related('tags') )
serializer_class = serializers.SiteGroupSerializer serializer_class = serializers.SiteGroupSerializer
filterset_class = filtersets.SiteGroupFilterSet filterset_class = filtersets.SiteGroupFilterSet
@ -129,9 +129,7 @@ class SiteGroupViewSet(MPTTLockedMixin, NetBoxModelViewSet):
# #
class SiteViewSet(NetBoxModelViewSet): class SiteViewSet(NetBoxModelViewSet):
queryset = Site.objects.prefetch_related( queryset = Site.objects.annotate(
'region', 'tenant', 'asns', 'tags'
).annotate(
device_count=count_related(Device, 'site'), device_count=count_related(Device, 'site'),
rack_count=count_related(Rack, 'site'), rack_count=count_related(Rack, 'site'),
prefix_count=count_related(Prefix, 'site'), prefix_count=count_related(Prefix, 'site'),
@ -160,7 +158,7 @@ class LocationViewSet(MPTTLockedMixin, NetBoxModelViewSet):
'location', 'location',
'rack_count', 'rack_count',
cumulative=True cumulative=True
).prefetch_related('site', 'tags') )
serializer_class = serializers.LocationSerializer serializer_class = serializers.LocationSerializer
filterset_class = filtersets.LocationFilterSet filterset_class = filtersets.LocationFilterSet
@ -170,7 +168,7 @@ class LocationViewSet(MPTTLockedMixin, NetBoxModelViewSet):
# #
class RackRoleViewSet(NetBoxModelViewSet): class RackRoleViewSet(NetBoxModelViewSet):
queryset = RackRole.objects.prefetch_related('tags').annotate( queryset = RackRole.objects.annotate(
rack_count=count_related(Rack, 'role') rack_count=count_related(Rack, 'role')
) )
serializer_class = serializers.RackRoleSerializer serializer_class = serializers.RackRoleSerializer
@ -182,9 +180,7 @@ class RackRoleViewSet(NetBoxModelViewSet):
# #
class RackViewSet(NetBoxModelViewSet): class RackViewSet(NetBoxModelViewSet):
queryset = Rack.objects.prefetch_related( queryset = Rack.objects.annotate(
'site', 'location', 'role', 'tenant', 'tags'
).annotate(
device_count=count_related(Device, 'rack'), device_count=count_related(Device, 'rack'),
powerfeed_count=count_related(PowerFeed, 'rack') powerfeed_count=count_related(PowerFeed, 'rack')
) )
@ -249,7 +245,7 @@ class RackViewSet(NetBoxModelViewSet):
# #
class RackReservationViewSet(NetBoxModelViewSet): class RackReservationViewSet(NetBoxModelViewSet):
queryset = RackReservation.objects.prefetch_related('rack', 'user', 'tenant') queryset = RackReservation.objects.all()
serializer_class = serializers.RackReservationSerializer serializer_class = serializers.RackReservationSerializer
filterset_class = filtersets.RackReservationFilterSet filterset_class = filtersets.RackReservationFilterSet
@ -259,7 +255,7 @@ class RackReservationViewSet(NetBoxModelViewSet):
# #
class ManufacturerViewSet(NetBoxModelViewSet): class ManufacturerViewSet(NetBoxModelViewSet):
queryset = Manufacturer.objects.prefetch_related('tags').annotate( queryset = Manufacturer.objects.annotate(
devicetype_count=count_related(DeviceType, 'manufacturer'), devicetype_count=count_related(DeviceType, 'manufacturer'),
inventoryitem_count=count_related(InventoryItem, 'manufacturer'), inventoryitem_count=count_related(InventoryItem, 'manufacturer'),
platform_count=count_related(Platform, 'manufacturer') platform_count=count_related(Platform, 'manufacturer')
@ -273,21 +269,17 @@ class ManufacturerViewSet(NetBoxModelViewSet):
# #
class DeviceTypeViewSet(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') device_count=count_related(Device, 'device_type')
) )
serializer_class = serializers.DeviceTypeSerializer serializer_class = serializers.DeviceTypeSerializer
filterset_class = filtersets.DeviceTypeFilterSet filterset_class = filtersets.DeviceTypeFilterSet
brief_prefetch_fields = ['manufacturer']
class ModuleTypeViewSet(NetBoxModelViewSet): class ModuleTypeViewSet(NetBoxModelViewSet):
queryset = ModuleType.objects.prefetch_related('manufacturer', 'tags').annotate( queryset = ModuleType.objects.all()
# module_count=count_related(Module, 'module_type')
)
serializer_class = serializers.ModuleTypeSerializer serializer_class = serializers.ModuleTypeSerializer
filterset_class = filtersets.ModuleTypeFilterSet filterset_class = filtersets.ModuleTypeFilterSet
brief_prefetch_fields = ['manufacturer']
# #
@ -295,61 +287,61 @@ class ModuleTypeViewSet(NetBoxModelViewSet):
# #
class ConsolePortTemplateViewSet(NetBoxModelViewSet): class ConsolePortTemplateViewSet(NetBoxModelViewSet):
queryset = ConsolePortTemplate.objects.prefetch_related('device_type__manufacturer') queryset = ConsolePortTemplate.objects.all()
serializer_class = serializers.ConsolePortTemplateSerializer serializer_class = serializers.ConsolePortTemplateSerializer
filterset_class = filtersets.ConsolePortTemplateFilterSet filterset_class = filtersets.ConsolePortTemplateFilterSet
class ConsoleServerPortTemplateViewSet(NetBoxModelViewSet): class ConsoleServerPortTemplateViewSet(NetBoxModelViewSet):
queryset = ConsoleServerPortTemplate.objects.prefetch_related('device_type__manufacturer') queryset = ConsoleServerPortTemplate.objects.all()
serializer_class = serializers.ConsoleServerPortTemplateSerializer serializer_class = serializers.ConsoleServerPortTemplateSerializer
filterset_class = filtersets.ConsoleServerPortTemplateFilterSet filterset_class = filtersets.ConsoleServerPortTemplateFilterSet
class PowerPortTemplateViewSet(NetBoxModelViewSet): class PowerPortTemplateViewSet(NetBoxModelViewSet):
queryset = PowerPortTemplate.objects.prefetch_related('device_type__manufacturer') queryset = PowerPortTemplate.objects.all()
serializer_class = serializers.PowerPortTemplateSerializer serializer_class = serializers.PowerPortTemplateSerializer
filterset_class = filtersets.PowerPortTemplateFilterSet filterset_class = filtersets.PowerPortTemplateFilterSet
class PowerOutletTemplateViewSet(NetBoxModelViewSet): class PowerOutletTemplateViewSet(NetBoxModelViewSet):
queryset = PowerOutletTemplate.objects.prefetch_related('device_type__manufacturer') queryset = PowerOutletTemplate.objects.all()
serializer_class = serializers.PowerOutletTemplateSerializer serializer_class = serializers.PowerOutletTemplateSerializer
filterset_class = filtersets.PowerOutletTemplateFilterSet filterset_class = filtersets.PowerOutletTemplateFilterSet
class InterfaceTemplateViewSet(NetBoxModelViewSet): class InterfaceTemplateViewSet(NetBoxModelViewSet):
queryset = InterfaceTemplate.objects.prefetch_related('device_type__manufacturer') queryset = InterfaceTemplate.objects.all()
serializer_class = serializers.InterfaceTemplateSerializer serializer_class = serializers.InterfaceTemplateSerializer
filterset_class = filtersets.InterfaceTemplateFilterSet filterset_class = filtersets.InterfaceTemplateFilterSet
class FrontPortTemplateViewSet(NetBoxModelViewSet): class FrontPortTemplateViewSet(NetBoxModelViewSet):
queryset = FrontPortTemplate.objects.prefetch_related('device_type__manufacturer') queryset = FrontPortTemplate.objects.all()
serializer_class = serializers.FrontPortTemplateSerializer serializer_class = serializers.FrontPortTemplateSerializer
filterset_class = filtersets.FrontPortTemplateFilterSet filterset_class = filtersets.FrontPortTemplateFilterSet
class RearPortTemplateViewSet(NetBoxModelViewSet): class RearPortTemplateViewSet(NetBoxModelViewSet):
queryset = RearPortTemplate.objects.prefetch_related('device_type__manufacturer') queryset = RearPortTemplate.objects.all()
serializer_class = serializers.RearPortTemplateSerializer serializer_class = serializers.RearPortTemplateSerializer
filterset_class = filtersets.RearPortTemplateFilterSet filterset_class = filtersets.RearPortTemplateFilterSet
class ModuleBayTemplateViewSet(NetBoxModelViewSet): class ModuleBayTemplateViewSet(NetBoxModelViewSet):
queryset = ModuleBayTemplate.objects.prefetch_related('device_type__manufacturer') queryset = ModuleBayTemplate.objects.all()
serializer_class = serializers.ModuleBayTemplateSerializer serializer_class = serializers.ModuleBayTemplateSerializer
filterset_class = filtersets.ModuleBayTemplateFilterSet filterset_class = filtersets.ModuleBayTemplateFilterSet
class DeviceBayTemplateViewSet(NetBoxModelViewSet): class DeviceBayTemplateViewSet(NetBoxModelViewSet):
queryset = DeviceBayTemplate.objects.prefetch_related('device_type__manufacturer') queryset = DeviceBayTemplate.objects.all()
serializer_class = serializers.DeviceBayTemplateSerializer serializer_class = serializers.DeviceBayTemplateSerializer
filterset_class = filtersets.DeviceBayTemplateFilterSet filterset_class = filtersets.DeviceBayTemplateFilterSet
class InventoryItemTemplateViewSet(MPTTLockedMixin, NetBoxModelViewSet): class InventoryItemTemplateViewSet(MPTTLockedMixin, NetBoxModelViewSet):
queryset = InventoryItemTemplate.objects.prefetch_related('device_type__manufacturer', 'role') queryset = InventoryItemTemplate.objects.all()
serializer_class = serializers.InventoryItemTemplateSerializer serializer_class = serializers.InventoryItemTemplateSerializer
filterset_class = filtersets.InventoryItemTemplateFilterSet filterset_class = filtersets.InventoryItemTemplateFilterSet
@ -359,7 +351,7 @@ class InventoryItemTemplateViewSet(MPTTLockedMixin, NetBoxModelViewSet):
# #
class DeviceRoleViewSet(NetBoxModelViewSet): class DeviceRoleViewSet(NetBoxModelViewSet):
queryset = DeviceRole.objects.prefetch_related('config_template', 'tags').annotate( queryset = DeviceRole.objects.annotate(
device_count=count_related(Device, 'role'), device_count=count_related(Device, 'role'),
virtualmachine_count=count_related(VirtualMachine, 'role') virtualmachine_count=count_related(VirtualMachine, 'role')
) )
@ -372,7 +364,7 @@ class DeviceRoleViewSet(NetBoxModelViewSet):
# #
class PlatformViewSet(NetBoxModelViewSet): class PlatformViewSet(NetBoxModelViewSet):
queryset = Platform.objects.prefetch_related('config_template', 'tags').annotate( queryset = Platform.objects.annotate(
device_count=count_related(Device, 'platform'), device_count=count_related(Device, 'platform'),
virtualmachine_count=count_related(VirtualMachine, 'platform') virtualmachine_count=count_related(VirtualMachine, 'platform')
) )
@ -391,8 +383,7 @@ class DeviceViewSet(
NetBoxModelViewSet NetBoxModelViewSet
): ):
queryset = Device.objects.prefetch_related( queryset = Device.objects.prefetch_related(
'device_type__manufacturer', 'role', 'tenant', 'platform', 'site', 'location', 'rack', 'parent_bay', 'parent_bay', # Referenced by DeviceSerializer.get_parent_device()
'virtual_chassis__master', 'primary_ip4__nat_outside', 'primary_ip6__nat_outside', 'config_template', 'tags',
) )
filterset_class = filtersets.DeviceFilterSet filterset_class = filtersets.DeviceFilterSet
pagination_class = StripCountAnnotationsPaginator pagination_class = StripCountAnnotationsPaginator
@ -419,9 +410,7 @@ class DeviceViewSet(
class VirtualDeviceContextViewSet(NetBoxModelViewSet): class VirtualDeviceContextViewSet(NetBoxModelViewSet):
queryset = VirtualDeviceContext.objects.prefetch_related( queryset = VirtualDeviceContext.objects.annotate(
'device__device_type', 'device', 'tenant', 'tags',
).annotate(
interface_count=count_related(Interface, 'vdcs'), interface_count=count_related(Interface, 'vdcs'),
) )
serializer_class = serializers.VirtualDeviceContextSerializer serializer_class = serializers.VirtualDeviceContextSerializer
@ -429,9 +418,7 @@ class VirtualDeviceContextViewSet(NetBoxModelViewSet):
class ModuleViewSet(NetBoxModelViewSet): class ModuleViewSet(NetBoxModelViewSet):
queryset = Module.objects.prefetch_related( queryset = Module.objects.all()
'device', 'module_bay', 'module_type__manufacturer', 'tags',
)
serializer_class = serializers.ModuleSerializer serializer_class = serializers.ModuleSerializer
filterset_class = filtersets.ModuleFilterSet filterset_class = filtersets.ModuleFilterSet
@ -442,49 +429,45 @@ class ModuleViewSet(NetBoxModelViewSet):
class ConsolePortViewSet(PathEndpointMixin, NetBoxModelViewSet): class ConsolePortViewSet(PathEndpointMixin, NetBoxModelViewSet):
queryset = ConsolePort.objects.prefetch_related( queryset = ConsolePort.objects.prefetch_related(
'device', 'module__module_bay', '_path', 'cable__terminations', 'tags' '_path', 'cable__terminations',
) )
serializer_class = serializers.ConsolePortSerializer serializer_class = serializers.ConsolePortSerializer
filterset_class = filtersets.ConsolePortFilterSet filterset_class = filtersets.ConsolePortFilterSet
brief_prefetch_fields = ['device']
class ConsoleServerPortViewSet(PathEndpointMixin, NetBoxModelViewSet): class ConsoleServerPortViewSet(PathEndpointMixin, NetBoxModelViewSet):
queryset = ConsoleServerPort.objects.prefetch_related( queryset = ConsoleServerPort.objects.prefetch_related(
'device', 'module__module_bay', '_path', 'cable__terminations', 'tags' '_path', 'cable__terminations',
) )
serializer_class = serializers.ConsoleServerPortSerializer serializer_class = serializers.ConsoleServerPortSerializer
filterset_class = filtersets.ConsoleServerPortFilterSet filterset_class = filtersets.ConsoleServerPortFilterSet
brief_prefetch_fields = ['device']
class PowerPortViewSet(PathEndpointMixin, NetBoxModelViewSet): class PowerPortViewSet(PathEndpointMixin, NetBoxModelViewSet):
queryset = PowerPort.objects.prefetch_related( queryset = PowerPort.objects.prefetch_related(
'device', 'module__module_bay', '_path', 'cable__terminations', 'tags' '_path', 'cable__terminations',
) )
serializer_class = serializers.PowerPortSerializer serializer_class = serializers.PowerPortSerializer
filterset_class = filtersets.PowerPortFilterSet filterset_class = filtersets.PowerPortFilterSet
brief_prefetch_fields = ['device']
class PowerOutletViewSet(PathEndpointMixin, NetBoxModelViewSet): class PowerOutletViewSet(PathEndpointMixin, NetBoxModelViewSet):
queryset = PowerOutlet.objects.prefetch_related( queryset = PowerOutlet.objects.prefetch_related(
'device', 'module__module_bay', '_path', 'cable__terminations', 'tags' '_path', 'cable__terminations',
) )
serializer_class = serializers.PowerOutletSerializer serializer_class = serializers.PowerOutletSerializer
filterset_class = filtersets.PowerOutletFilterSet filterset_class = filtersets.PowerOutletFilterSet
brief_prefetch_fields = ['device']
class InterfaceViewSet(PathEndpointMixin, NetBoxModelViewSet): class InterfaceViewSet(PathEndpointMixin, NetBoxModelViewSet):
queryset = Interface.objects.prefetch_related( queryset = Interface.objects.prefetch_related(
'device', 'module__module_bay', 'parent', 'bridge', 'lag', '_path', 'cable__terminations', 'wireless_lans', '_path', 'cable__terminations',
'untagged_vlan', 'tagged_vlans', 'vrf', 'ip_addresses', 'fhrp_group_assignments', 'tags', 'l2vpn_terminations', 'l2vpn_terminations', # Referenced by InterfaceSerializer.l2vpn_termination
'vdcs', 'ip_addresses', # Referenced by Interface.count_ipaddresses()
'fhrp_group_assignments', # Referenced by Interface.count_fhrp_groups()
) )
serializer_class = serializers.InterfaceSerializer serializer_class = serializers.InterfaceSerializer
filterset_class = filtersets.InterfaceFilterSet filterset_class = filtersets.InterfaceFilterSet
brief_prefetch_fields = ['device']
def get_bulk_destroy_queryset(self): def get_bulk_destroy_queryset(self):
# Ensure child interfaces are deleted prior to their parents # Ensure child interfaces are deleted prior to their parents
@ -493,41 +476,36 @@ class InterfaceViewSet(PathEndpointMixin, NetBoxModelViewSet):
class FrontPortViewSet(PassThroughPortMixin, NetBoxModelViewSet): class FrontPortViewSet(PassThroughPortMixin, NetBoxModelViewSet):
queryset = FrontPort.objects.prefetch_related( queryset = FrontPort.objects.prefetch_related(
'device__device_type__manufacturer', 'module__module_bay', 'rear_port', 'cable__terminations', 'tags' 'cable__terminations',
) )
serializer_class = serializers.FrontPortSerializer serializer_class = serializers.FrontPortSerializer
filterset_class = filtersets.FrontPortFilterSet filterset_class = filtersets.FrontPortFilterSet
brief_prefetch_fields = ['device']
class RearPortViewSet(PassThroughPortMixin, NetBoxModelViewSet): class RearPortViewSet(PassThroughPortMixin, NetBoxModelViewSet):
queryset = RearPort.objects.prefetch_related( queryset = RearPort.objects.prefetch_related(
'device__device_type__manufacturer', 'module__module_bay', 'cable__terminations', 'tags' 'cable__terminations',
) )
serializer_class = serializers.RearPortSerializer serializer_class = serializers.RearPortSerializer
filterset_class = filtersets.RearPortFilterSet filterset_class = filtersets.RearPortFilterSet
brief_prefetch_fields = ['device']
class ModuleBayViewSet(NetBoxModelViewSet): class ModuleBayViewSet(NetBoxModelViewSet):
queryset = ModuleBay.objects.prefetch_related('tags', 'installed_module') queryset = ModuleBay.objects.all()
serializer_class = serializers.ModuleBaySerializer serializer_class = serializers.ModuleBaySerializer
filterset_class = filtersets.ModuleBayFilterSet filterset_class = filtersets.ModuleBayFilterSet
brief_prefetch_fields = ['device']
class DeviceBayViewSet(NetBoxModelViewSet): class DeviceBayViewSet(NetBoxModelViewSet):
queryset = DeviceBay.objects.prefetch_related('installed_device', 'tags') queryset = DeviceBay.objects.all()
serializer_class = serializers.DeviceBaySerializer serializer_class = serializers.DeviceBaySerializer
filterset_class = filtersets.DeviceBayFilterSet filterset_class = filtersets.DeviceBayFilterSet
brief_prefetch_fields = ['device']
class InventoryItemViewSet(MPTTLockedMixin, NetBoxModelViewSet): class InventoryItemViewSet(MPTTLockedMixin, NetBoxModelViewSet):
queryset = InventoryItem.objects.prefetch_related('device', 'manufacturer', 'tags') queryset = InventoryItem.objects.all()
serializer_class = serializers.InventoryItemSerializer serializer_class = serializers.InventoryItemSerializer
filterset_class = filtersets.InventoryItemFilterSet filterset_class = filtersets.InventoryItemFilterSet
brief_prefetch_fields = ['device']
# #
@ -535,7 +513,7 @@ class InventoryItemViewSet(MPTTLockedMixin, NetBoxModelViewSet):
# #
class InventoryItemRoleViewSet(NetBoxModelViewSet): class InventoryItemRoleViewSet(NetBoxModelViewSet):
queryset = InventoryItemRole.objects.prefetch_related('tags').annotate( queryset = InventoryItemRole.objects.annotate(
inventoryitem_count=count_related(InventoryItem, 'role') inventoryitem_count=count_related(InventoryItem, 'role')
) )
serializer_class = serializers.InventoryItemRoleSerializer serializer_class = serializers.InventoryItemRoleSerializer
@ -554,7 +532,7 @@ class CableViewSet(NetBoxModelViewSet):
class CableTerminationViewSet(NetBoxModelViewSet): class CableTerminationViewSet(NetBoxModelViewSet):
metadata_class = ContentTypeMetadata metadata_class = ContentTypeMetadata
queryset = CableTermination.objects.prefetch_related('cable', 'termination') queryset = CableTermination.objects.all()
serializer_class = serializers.CableTerminationSerializer serializer_class = serializers.CableTerminationSerializer
filterset_class = filtersets.CableTerminationFilterSet filterset_class = filtersets.CableTerminationFilterSet
@ -564,10 +542,9 @@ class CableTerminationViewSet(NetBoxModelViewSet):
# #
class VirtualChassisViewSet(NetBoxModelViewSet): class VirtualChassisViewSet(NetBoxModelViewSet):
queryset = VirtualChassis.objects.prefetch_related('tags') queryset = VirtualChassis.objects.all()
serializer_class = serializers.VirtualChassisSerializer serializer_class = serializers.VirtualChassisSerializer
filterset_class = filtersets.VirtualChassisFilterSet filterset_class = filtersets.VirtualChassisFilterSet
brief_prefetch_fields = ['master']
# #
@ -575,9 +552,7 @@ class VirtualChassisViewSet(NetBoxModelViewSet):
# #
class PowerPanelViewSet(NetBoxModelViewSet): class PowerPanelViewSet(NetBoxModelViewSet):
queryset = PowerPanel.objects.prefetch_related( queryset = PowerPanel.objects.annotate(
'site', 'location'
).annotate(
powerfeed_count=count_related(PowerFeed, 'power_panel') powerfeed_count=count_related(PowerFeed, 'power_panel')
) )
serializer_class = serializers.PowerPanelSerializer serializer_class = serializers.PowerPanelSerializer
@ -590,7 +565,7 @@ class PowerPanelViewSet(NetBoxModelViewSet):
class PowerFeedViewSet(PathEndpointMixin, NetBoxModelViewSet): class PowerFeedViewSet(PathEndpointMixin, NetBoxModelViewSet):
queryset = PowerFeed.objects.prefetch_related( queryset = PowerFeed.objects.prefetch_related(
'power_panel', 'rack', '_path', 'cable__terminations', 'tags' '_path', 'cable__terminations',
) )
serializer_class = serializers.PowerFeedSerializer serializer_class = serializers.PowerFeedSerializer
filterset_class = filtersets.PowerFeedFilterSet filterset_class = filtersets.PowerFeedFilterSet

View File

@ -115,7 +115,7 @@ class CustomLinkViewSet(NetBoxModelViewSet):
class ExportTemplateViewSet(SyncedDataMixin, NetBoxModelViewSet): class ExportTemplateViewSet(SyncedDataMixin, NetBoxModelViewSet):
metadata_class = ContentTypeMetadata metadata_class = ContentTypeMetadata
queryset = ExportTemplate.objects.prefetch_related('data_source', 'data_file') queryset = ExportTemplate.objects.all()
serializer_class = serializers.ExportTemplateSerializer serializer_class = serializers.ExportTemplateSerializer
filterset_class = filtersets.ExportTemplateFilterSet filterset_class = filtersets.ExportTemplateFilterSet
@ -181,10 +181,7 @@ class JournalEntryViewSet(NetBoxModelViewSet):
# #
class ConfigContextViewSet(SyncedDataMixin, NetBoxModelViewSet): class ConfigContextViewSet(SyncedDataMixin, NetBoxModelViewSet):
queryset = ConfigContext.objects.prefetch_related( queryset = ConfigContext.objects.all()
'regions', 'site_groups', 'sites', 'locations', 'roles', 'platforms', 'tenant_groups', 'tenants', 'data_source',
'data_file',
)
serializer_class = serializers.ConfigContextSerializer serializer_class = serializers.ConfigContextSerializer
filterset_class = filtersets.ConfigContextFilterSet filterset_class = filtersets.ConfigContextFilterSet
@ -194,7 +191,7 @@ class ConfigContextViewSet(SyncedDataMixin, NetBoxModelViewSet):
# #
class ConfigTemplateViewSet(SyncedDataMixin, ConfigTemplateRenderMixin, NetBoxModelViewSet): class ConfigTemplateViewSet(SyncedDataMixin, ConfigTemplateRenderMixin, NetBoxModelViewSet):
queryset = ConfigTemplate.objects.prefetch_related('data_source', 'data_file') queryset = ConfigTemplate.objects.all()
serializer_class = serializers.ConfigTemplateSerializer serializer_class = serializers.ConfigTemplateSerializer
filterset_class = filtersets.ConfigTemplateFilterSet filterset_class = filtersets.ConfigTemplateFilterSet
@ -312,7 +309,7 @@ class ObjectChangeViewSet(ReadOnlyModelViewSet):
Retrieve a list of recent changes. Retrieve a list of recent changes.
""" """
metadata_class = ContentTypeMetadata metadata_class = ContentTypeMetadata
queryset = ObjectChange.objects.valid_models().prefetch_related('user') queryset = ObjectChange.objects.valid_models()
serializer_class = serializers.ObjectChangeSerializer serializer_class = serializers.ObjectChangeSerializer
filterset_class = filtersets.ObjectChangeFilterSet filterset_class = filtersets.ObjectChangeFilterSet

View File

@ -116,10 +116,11 @@ class NestedFHRPGroupSerializer(WritableNestedSerializer):
class NestedFHRPGroupAssignmentSerializer(WritableNestedSerializer): class NestedFHRPGroupAssignmentSerializer(WritableNestedSerializer):
url = serializers.HyperlinkedIdentityField(view_name='ipam-api:fhrpgroupassignment-detail') url = serializers.HyperlinkedIdentityField(view_name='ipam-api:fhrpgroupassignment-detail')
group = NestedFHRPGroupSerializer()
class Meta: class Meta:
model = models.FHRPGroupAssignment model = models.FHRPGroupAssignment
fields = ['id', 'url', 'display', 'interface_type', 'interface_id', 'group_id', 'priority'] fields = ['id', 'url', 'display', 'group', 'interface_type', 'interface_id', 'priority']
# #

View File

@ -39,13 +39,13 @@ class IPAMRootView(APIRootView):
# #
class ASNRangeViewSet(NetBoxModelViewSet): class ASNRangeViewSet(NetBoxModelViewSet):
queryset = ASNRange.objects.prefetch_related('tenant', 'rir').all() queryset = ASNRange.objects.all()
serializer_class = serializers.ASNRangeSerializer serializer_class = serializers.ASNRangeSerializer
filterset_class = filtersets.ASNRangeFilterSet filterset_class = filtersets.ASNRangeFilterSet
class ASNViewSet(NetBoxModelViewSet): class ASNViewSet(NetBoxModelViewSet):
queryset = ASN.objects.prefetch_related('tenant', 'rir').annotate( queryset = ASN.objects.annotate(
site_count=count_related(Site, 'asns'), site_count=count_related(Site, 'asns'),
provider_count=count_related(Provider, 'asns') provider_count=count_related(Provider, 'asns')
) )
@ -54,9 +54,7 @@ class ASNViewSet(NetBoxModelViewSet):
class VRFViewSet(NetBoxModelViewSet): class VRFViewSet(NetBoxModelViewSet):
queryset = VRF.objects.prefetch_related('tenant').prefetch_related( queryset = VRF.objects.annotate(
'import_targets', 'export_targets', 'tags'
).annotate(
ipaddress_count=count_related(IPAddress, 'vrf'), ipaddress_count=count_related(IPAddress, 'vrf'),
prefix_count=count_related(Prefix, 'vrf') prefix_count=count_related(Prefix, 'vrf')
) )
@ -65,7 +63,7 @@ class VRFViewSet(NetBoxModelViewSet):
class RouteTargetViewSet(NetBoxModelViewSet): class RouteTargetViewSet(NetBoxModelViewSet):
queryset = RouteTarget.objects.prefetch_related('tenant').prefetch_related('tags') queryset = RouteTarget.objects.all()
serializer_class = serializers.RouteTargetSerializer serializer_class = serializers.RouteTargetSerializer
filterset_class = filtersets.RouteTargetFilterSet filterset_class = filtersets.RouteTargetFilterSet
@ -73,13 +71,13 @@ class RouteTargetViewSet(NetBoxModelViewSet):
class RIRViewSet(NetBoxModelViewSet): class RIRViewSet(NetBoxModelViewSet):
queryset = RIR.objects.annotate( queryset = RIR.objects.annotate(
aggregate_count=count_related(Aggregate, 'rir') aggregate_count=count_related(Aggregate, 'rir')
).prefetch_related('tags') )
serializer_class = serializers.RIRSerializer serializer_class = serializers.RIRSerializer
filterset_class = filtersets.RIRFilterSet filterset_class = filtersets.RIRFilterSet
class AggregateViewSet(NetBoxModelViewSet): class AggregateViewSet(NetBoxModelViewSet):
queryset = Aggregate.objects.prefetch_related('rir').prefetch_related('tags') queryset = Aggregate.objects.all()
serializer_class = serializers.AggregateSerializer serializer_class = serializers.AggregateSerializer
filterset_class = filtersets.AggregateFilterSet filterset_class = filtersets.AggregateFilterSet
@ -88,15 +86,13 @@ class RoleViewSet(NetBoxModelViewSet):
queryset = Role.objects.annotate( queryset = Role.objects.annotate(
prefix_count=count_related(Prefix, 'role'), prefix_count=count_related(Prefix, 'role'),
vlan_count=count_related(VLAN, 'role') vlan_count=count_related(VLAN, 'role')
).prefetch_related('tags') )
serializer_class = serializers.RoleSerializer serializer_class = serializers.RoleSerializer
filterset_class = filtersets.RoleFilterSet filterset_class = filtersets.RoleFilterSet
class PrefixViewSet(NetBoxModelViewSet): class PrefixViewSet(NetBoxModelViewSet):
queryset = Prefix.objects.prefetch_related( queryset = Prefix.objects.all()
'site', 'vrf__tenant', 'tenant', 'vlan', 'role', 'tags'
)
serializer_class = serializers.PrefixSerializer serializer_class = serializers.PrefixSerializer
filterset_class = filtersets.PrefixFilterSet filterset_class = filtersets.PrefixFilterSet
@ -109,7 +105,7 @@ class PrefixViewSet(NetBoxModelViewSet):
class IPRangeViewSet(NetBoxModelViewSet): class IPRangeViewSet(NetBoxModelViewSet):
queryset = IPRange.objects.prefetch_related('vrf', 'role', 'tenant', 'tags') queryset = IPRange.objects.all()
serializer_class = serializers.IPRangeSerializer serializer_class = serializers.IPRangeSerializer
filterset_class = filtersets.IPRangeFilterSet filterset_class = filtersets.IPRangeFilterSet
@ -117,9 +113,7 @@ class IPRangeViewSet(NetBoxModelViewSet):
class IPAddressViewSet(NetBoxModelViewSet): class IPAddressViewSet(NetBoxModelViewSet):
queryset = IPAddress.objects.prefetch_related( queryset = IPAddress.objects.all()
'vrf__tenant', 'tenant', 'nat_inside', 'nat_outside', 'tags', 'assigned_object'
)
serializer_class = serializers.IPAddressSerializer serializer_class = serializers.IPAddressSerializer
filterset_class = filtersets.IPAddressFilterSet filterset_class = filtersets.IPAddressFilterSet
@ -137,27 +131,26 @@ class IPAddressViewSet(NetBoxModelViewSet):
class FHRPGroupViewSet(NetBoxModelViewSet): class FHRPGroupViewSet(NetBoxModelViewSet):
queryset = FHRPGroup.objects.prefetch_related('ip_addresses', 'tags') queryset = FHRPGroup.objects.all()
serializer_class = serializers.FHRPGroupSerializer serializer_class = serializers.FHRPGroupSerializer
filterset_class = filtersets.FHRPGroupFilterSet filterset_class = filtersets.FHRPGroupFilterSet
brief_prefetch_fields = ('ip_addresses',)
class FHRPGroupAssignmentViewSet(NetBoxModelViewSet): class FHRPGroupAssignmentViewSet(NetBoxModelViewSet):
queryset = FHRPGroupAssignment.objects.prefetch_related('group', 'interface') queryset = FHRPGroupAssignment.objects.all()
serializer_class = serializers.FHRPGroupAssignmentSerializer serializer_class = serializers.FHRPGroupAssignmentSerializer
filterset_class = filtersets.FHRPGroupAssignmentFilterSet filterset_class = filtersets.FHRPGroupAssignmentFilterSet
class VLANGroupViewSet(NetBoxModelViewSet): class VLANGroupViewSet(NetBoxModelViewSet):
queryset = VLANGroup.objects.annotate_utilization().prefetch_related('tags') queryset = VLANGroup.objects.annotate_utilization()
serializer_class = serializers.VLANGroupSerializer serializer_class = serializers.VLANGroupSerializer
filterset_class = filtersets.VLANGroupFilterSet filterset_class = filtersets.VLANGroupFilterSet
class VLANViewSet(NetBoxModelViewSet): class VLANViewSet(NetBoxModelViewSet):
queryset = VLAN.objects.prefetch_related( queryset = VLAN.objects.prefetch_related(
'site', 'group', 'tenant', 'role', 'tags' 'l2vpn_terminations', # Referenced by VLANSerializer.l2vpn_termination
).annotate( ).annotate(
prefix_count=count_related(Prefix, 'vlan') prefix_count=count_related(Prefix, 'vlan')
) )
@ -166,15 +159,13 @@ class VLANViewSet(NetBoxModelViewSet):
class ServiceTemplateViewSet(NetBoxModelViewSet): class ServiceTemplateViewSet(NetBoxModelViewSet):
queryset = ServiceTemplate.objects.prefetch_related('tags') queryset = ServiceTemplate.objects.all()
serializer_class = serializers.ServiceTemplateSerializer serializer_class = serializers.ServiceTemplateSerializer
filterset_class = filtersets.ServiceTemplateFilterSet filterset_class = filtersets.ServiceTemplateFilterSet
class ServiceViewSet(NetBoxModelViewSet): class ServiceViewSet(NetBoxModelViewSet):
queryset = Service.objects.prefetch_related( queryset = Service.objects.all()
'device', 'virtual_machine', 'tags', 'ipaddresses'
)
serializer_class = serializers.ServiceSerializer serializer_class = serializers.ServiceSerializer
filterset_class = filtersets.ServiceFilterSet filterset_class = filtersets.ServiceFilterSet

View File

@ -760,7 +760,7 @@ class FHRPGroupTest(APIViewTestCases.APIViewTestCase):
class FHRPGroupAssignmentTest(APIViewTestCases.APIViewTestCase): class FHRPGroupAssignmentTest(APIViewTestCases.APIViewTestCase):
model = FHRPGroupAssignment 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 = { bulk_update_data = {
'priority': 100, 'priority': 100,
} }

View File

@ -12,6 +12,15 @@ __all__ = (
class BaseModelSerializer(serializers.ModelSerializer): class BaseModelSerializer(serializers.ModelSerializer):
display = serializers.SerializerMethodField(read_only=True) 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) @extend_schema_field(OpenApiTypes.STR)
def get_display(self, obj): def get_display(self, obj):
return str(obj) return str(obj)

View File

@ -1,4 +1,5 @@
import logging import logging
from functools import cached_property
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
from django.db import transaction 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.response import Response
from rest_framework.viewsets import GenericViewSet from rest_framework.viewsets import GenericViewSet
from utilities.api import get_prefetches_for_serializer
from utilities.exceptions import AbortRequest from utilities.exceptions import AbortRequest
from . import mixins from . import mixins
@ -40,6 +42,32 @@ class BaseViewSet(GenericViewSet):
if action := HTTP_ACTIONS[request.method]: if action := HTTP_ACTIONS[request.method]:
self.queryset = self.queryset.restrict(request.user, action) 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( class NetBoxReadOnlyModelViewSet(
mixins.BriefModeMixin, mixins.BriefModeMixin,

View File

@ -30,7 +30,6 @@ class BriefModeMixin:
GET /api/dcim/sites/?brief=True GET /api/dcim/sites/?brief=True
""" """
brief = False brief = False
brief_prefetch_fields = []
def initialize_request(self, request, *args, **kwargs): def initialize_request(self, request, *args, **kwargs):
# Annotate whether brief mode is active # Annotate whether brief mode is active
@ -64,9 +63,6 @@ class BriefModeMixin:
if annotation not in serializer_class().fields: if annotation not in serializer_class().fields:
qs.query.annotations.pop(annotation) 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 return qs

View File

@ -30,15 +30,13 @@ class TenantGroupViewSet(MPTTLockedMixin, NetBoxModelViewSet):
'group', 'group',
'tenant_count', 'tenant_count',
cumulative=True cumulative=True
).prefetch_related('tags') )
serializer_class = serializers.TenantGroupSerializer serializer_class = serializers.TenantGroupSerializer
filterset_class = filtersets.TenantGroupFilterSet filterset_class = filtersets.TenantGroupFilterSet
class TenantViewSet(NetBoxModelViewSet): class TenantViewSet(NetBoxModelViewSet):
queryset = Tenant.objects.prefetch_related( queryset = Tenant.objects.annotate(
'group', 'tags'
).annotate(
circuit_count=count_related(Circuit, 'tenant'), circuit_count=count_related(Circuit, 'tenant'),
device_count=count_related(Device, 'tenant'), device_count=count_related(Device, 'tenant'),
ipaddress_count=count_related(IPAddress, 'tenant'), ipaddress_count=count_related(IPAddress, 'tenant'),
@ -65,24 +63,24 @@ class ContactGroupViewSet(MPTTLockedMixin, NetBoxModelViewSet):
'group', 'group',
'contact_count', 'contact_count',
cumulative=True cumulative=True
).prefetch_related('tags') )
serializer_class = serializers.ContactGroupSerializer serializer_class = serializers.ContactGroupSerializer
filterset_class = filtersets.ContactGroupFilterSet filterset_class = filtersets.ContactGroupFilterSet
class ContactRoleViewSet(NetBoxModelViewSet): class ContactRoleViewSet(NetBoxModelViewSet):
queryset = ContactRole.objects.prefetch_related('tags') queryset = ContactRole.objects.all()
serializer_class = serializers.ContactRoleSerializer serializer_class = serializers.ContactRoleSerializer
filterset_class = filtersets.ContactRoleFilterSet filterset_class = filtersets.ContactRoleFilterSet
class ContactViewSet(NetBoxModelViewSet): class ContactViewSet(NetBoxModelViewSet):
queryset = Contact.objects.prefetch_related('group', 'tags') queryset = Contact.objects.all()
serializer_class = serializers.ContactSerializer serializer_class = serializers.ContactSerializer
filterset_class = filtersets.ContactFilterSet filterset_class = filtersets.ContactFilterSet
class ContactAssignmentViewSet(NetBoxModelViewSet): class ContactAssignmentViewSet(NetBoxModelViewSet):
queryset = ContactAssignment.objects.prefetch_related('content_type', 'object', 'contact', 'role', 'tags') queryset = ContactAssignment.objects.all()
serializer_class = serializers.ContactAssignmentSerializer serializer_class = serializers.ContactAssignmentSerializer
filterset_class = filtersets.ContactAssignmentFilterSet filterset_class = filtersets.ContactAssignmentFilterSet

View File

@ -34,7 +34,7 @@ class UsersRootView(APIRootView):
# #
class UserViewSet(NetBoxModelViewSet): 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 serializer_class = serializers.UserSerializer
filterset_class = filtersets.UserFilterSet filterset_class = filtersets.UserFilterSet
@ -50,7 +50,7 @@ class GroupViewSet(NetBoxModelViewSet):
# #
class TokenViewSet(NetBoxModelViewSet): class TokenViewSet(NetBoxModelViewSet):
queryset = Token.objects.prefetch_related('user') queryset = Token.objects.all()
serializer_class = serializers.TokenSerializer serializer_class = serializers.TokenSerializer
filterset_class = filtersets.TokenFilterSet filterset_class = filtersets.TokenFilterSet
@ -86,7 +86,7 @@ class TokenProvisionView(APIView):
# #
class ObjectPermissionViewSet(NetBoxModelViewSet): class ObjectPermissionViewSet(NetBoxModelViewSet):
queryset = ObjectPermission.objects.prefetch_related('object_types', 'groups', 'users') queryset = ObjectPermission.objects.all()
serializer_class = serializers.ObjectPermissionSerializer serializer_class = serializers.ObjectPermissionSerializer
filterset_class = filtersets.ObjectPermissionFilterSet filterset_class = filtersets.ObjectPermissionFilterSet

View File

@ -2,9 +2,13 @@ import platform
import sys import sys
from django.conf import settings 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.http import JsonResponse
from django.urls import reverse from django.urls import reverse
from rest_framework import status from rest_framework import status
from rest_framework.serializers import Serializer
from rest_framework.utils import formatting from rest_framework.utils import formatting
from netbox.api.exceptions import GraphQLTypeNotFound, SerializerNotFound from netbox.api.exceptions import GraphQLTypeNotFound, SerializerNotFound
@ -12,6 +16,7 @@ from .utils import dynamic_import
__all__ = ( __all__ = (
'get_graphql_type_for_model', 'get_graphql_type_for_model',
'get_prefetches_for_serializer',
'get_serializer_for_model', 'get_serializer_for_model',
'get_view_name', 'get_view_name',
'is_api_request', 'is_api_request',
@ -89,6 +94,43 @@ def get_view_name(view, suffix=None):
return name 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): def rest_api_server_error(request, *args, **kwargs):
""" """
Handle exceptions and return a useful error message for REST API requests. Handle exceptions and return a useful error message for REST API requests.

View File

@ -25,7 +25,7 @@ class VirtualizationRootView(APIRootView):
class ClusterTypeViewSet(NetBoxModelViewSet): class ClusterTypeViewSet(NetBoxModelViewSet):
queryset = ClusterType.objects.annotate( queryset = ClusterType.objects.annotate(
cluster_count=count_related(Cluster, 'type') cluster_count=count_related(Cluster, 'type')
).prefetch_related('tags') )
serializer_class = serializers.ClusterTypeSerializer serializer_class = serializers.ClusterTypeSerializer
filterset_class = filtersets.ClusterTypeFilterSet filterset_class = filtersets.ClusterTypeFilterSet
@ -33,15 +33,13 @@ class ClusterTypeViewSet(NetBoxModelViewSet):
class ClusterGroupViewSet(NetBoxModelViewSet): class ClusterGroupViewSet(NetBoxModelViewSet):
queryset = ClusterGroup.objects.annotate( queryset = ClusterGroup.objects.annotate(
cluster_count=count_related(Cluster, 'group') cluster_count=count_related(Cluster, 'group')
).prefetch_related('tags') )
serializer_class = serializers.ClusterGroupSerializer serializer_class = serializers.ClusterGroupSerializer
filterset_class = filtersets.ClusterGroupFilterSet filterset_class = filtersets.ClusterGroupFilterSet
class ClusterViewSet(NetBoxModelViewSet): class ClusterViewSet(NetBoxModelViewSet):
queryset = Cluster.objects.prefetch_related( queryset = Cluster.objects.annotate(
'type', 'group', 'tenant', 'site', 'tags'
).annotate(
device_count=count_related(Device, 'cluster'), device_count=count_related(Device, 'cluster'),
virtualmachine_count=count_related(VirtualMachine, 'cluster') virtualmachine_count=count_related(VirtualMachine, 'cluster')
) )
@ -54,10 +52,7 @@ class ClusterViewSet(NetBoxModelViewSet):
# #
class VirtualMachineViewSet(ConfigContextQuerySetMixin, RenderConfigMixin, NetBoxModelViewSet): class VirtualMachineViewSet(ConfigContextQuerySetMixin, RenderConfigMixin, NetBoxModelViewSet):
queryset = VirtualMachine.objects.prefetch_related( queryset = VirtualMachine.objects.all()
'site', 'cluster', 'device', 'role', 'tenant', 'platform', 'primary_ip4', 'primary_ip6', 'config_template',
'tags', 'virtualdisks',
)
filterset_class = filtersets.VirtualMachineFilterSet filterset_class = filtersets.VirtualMachineFilterSet
def get_serializer_class(self): def get_serializer_class(self):
@ -83,12 +78,12 @@ class VirtualMachineViewSet(ConfigContextQuerySetMixin, RenderConfigMixin, NetBo
class VMInterfaceViewSet(NetBoxModelViewSet): class VMInterfaceViewSet(NetBoxModelViewSet):
queryset = VMInterface.objects.prefetch_related( queryset = VMInterface.objects.prefetch_related(
'virtual_machine', 'parent', 'tags', 'untagged_vlan', 'tagged_vlans', 'vrf', 'ip_addresses', 'l2vpn_terminations', # Referenced by VMInterfaceSerializer.l2vpn_termination
'fhrp_group_assignments', 'ip_addresses', # Referenced by VMInterface.count_ipaddresses()
'fhrp_group_assignments', # Referenced by VMInterface.count_fhrp_groups()
) )
serializer_class = serializers.VMInterfaceSerializer serializer_class = serializers.VMInterfaceSerializer
filterset_class = filtersets.VMInterfaceFilterSet filterset_class = filtersets.VMInterfaceFilterSet
brief_prefetch_fields = ['virtual_machine']
def get_bulk_destroy_queryset(self): def get_bulk_destroy_queryset(self):
# Ensure child interfaces are deleted prior to their parents # Ensure child interfaces are deleted prior to their parents
@ -96,9 +91,6 @@ class VMInterfaceViewSet(NetBoxModelViewSet):
class VirtualDiskViewSet(NetBoxModelViewSet): class VirtualDiskViewSet(NetBoxModelViewSet):
queryset = VirtualDisk.objects.prefetch_related( queryset = VirtualDisk.objects.all()
'virtual_machine', 'tags',
)
serializer_class = serializers.VirtualDiskSerializer serializer_class = serializers.VirtualDiskSerializer
filterset_class = filtersets.VirtualDiskFilterSet filterset_class = filtersets.VirtualDiskFilterSet
brief_prefetch_fields = ['virtual_machine']

View File

@ -42,7 +42,7 @@ class TunnelGroupViewSet(NetBoxModelViewSet):
class TunnelViewSet(NetBoxModelViewSet): class TunnelViewSet(NetBoxModelViewSet):
queryset = Tunnel.objects.prefetch_related('ipsec_profile', 'tenant').annotate( queryset = Tunnel.objects.annotate(
terminations_count=count_related(TunnelTermination, 'tunnel') terminations_count=count_related(TunnelTermination, 'tunnel')
) )
serializer_class = serializers.TunnelSerializer serializer_class = serializers.TunnelSerializer
@ -50,7 +50,7 @@ class TunnelViewSet(NetBoxModelViewSet):
class TunnelTerminationViewSet(NetBoxModelViewSet): class TunnelTerminationViewSet(NetBoxModelViewSet):
queryset = TunnelTermination.objects.prefetch_related('tunnel') queryset = TunnelTermination.objects.all()
serializer_class = serializers.TunnelTerminationSerializer serializer_class = serializers.TunnelTerminationSerializer
filterset_class = filtersets.TunnelTerminationFilterSet filterset_class = filtersets.TunnelTerminationFilterSet
@ -86,12 +86,12 @@ class IPSecProfileViewSet(NetBoxModelViewSet):
class L2VPNViewSet(NetBoxModelViewSet): class L2VPNViewSet(NetBoxModelViewSet):
queryset = L2VPN.objects.prefetch_related('import_targets', 'export_targets', 'tenant', 'tags') queryset = L2VPN.objects.all()
serializer_class = serializers.L2VPNSerializer serializer_class = serializers.L2VPNSerializer
filterset_class = filtersets.L2VPNFilterSet filterset_class = filtersets.L2VPNFilterSet
class L2VPNTerminationViewSet(NetBoxModelViewSet): class L2VPNTerminationViewSet(NetBoxModelViewSet):
queryset = L2VPNTermination.objects.prefetch_related('assigned_object') queryset = L2VPNTermination.objects.all()
serializer_class = serializers.L2VPNTerminationSerializer serializer_class = serializers.L2VPNTerminationSerializer
filterset_class = filtersets.L2VPNTerminationFilterSet filterset_class = filtersets.L2VPNTerminationFilterSet

View File

@ -27,12 +27,12 @@ class WirelessLANGroupViewSet(MPTTLockedMixin, NetBoxModelViewSet):
class WirelessLANViewSet(NetBoxModelViewSet): class WirelessLANViewSet(NetBoxModelViewSet):
queryset = WirelessLAN.objects.prefetch_related('vlan', 'tenant', 'tags') queryset = WirelessLAN.objects.all()
serializer_class = serializers.WirelessLANSerializer serializer_class = serializers.WirelessLANSerializer
filterset_class = filtersets.WirelessLANFilterSet filterset_class = filtersets.WirelessLANFilterSet
class WirelessLinkViewSet(NetBoxModelViewSet): class WirelessLinkViewSet(NetBoxModelViewSet):
queryset = WirelessLink.objects.prefetch_related('interface_a', 'interface_b', 'tenant', 'tags') queryset = WirelessLink.objects.all()
serializer_class = serializers.WirelessLinkSerializer serializer_class = serializers.WirelessLinkSerializer
filterset_class = filtersets.WirelessLinkFilterSet filterset_class = filtersets.WirelessLinkFilterSet