Skip to content

Commit 8ae3331

Browse files
committed
Closes #5549: Eliminate extraneous database queries when using brief API calls
1 parent b2e05aa commit 8ae3331

File tree

5 files changed

+41
-21
lines changed

5 files changed

+41
-21
lines changed

netbox/circuits/api/views.py

+1
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,4 @@ class CircuitTerminationViewSet(PathEndpointMixin, ModelViewSet):
6565
)
6666
serializer_class = serializers.CircuitTerminationSerializer
6767
filterset_class = filters.CircuitTerminationFilterSet
68+
brief_prefetch_fields = ['circuit']

netbox/dcim/api/views.py

+11
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ class DeviceTypeViewSet(CustomFieldModelViewSet):
258258
)
259259
serializer_class = serializers.DeviceTypeSerializer
260260
filterset_class = filters.DeviceTypeFilterSet
261+
brief_prefetch_fields = ['manufacturer']
261262

262263

263264
#
@@ -493,6 +494,7 @@ class ConsolePortViewSet(PathEndpointMixin, ModelViewSet):
493494
queryset = ConsolePort.objects.prefetch_related('device', '_path__destination', 'cable', '_cable_peer', 'tags')
494495
serializer_class = serializers.ConsolePortSerializer
495496
filterset_class = filters.ConsolePortFilterSet
497+
brief_prefetch_fields = ['device']
496498

497499

498500
class ConsoleServerPortViewSet(PathEndpointMixin, ModelViewSet):
@@ -501,18 +503,21 @@ class ConsoleServerPortViewSet(PathEndpointMixin, ModelViewSet):
501503
)
502504
serializer_class = serializers.ConsoleServerPortSerializer
503505
filterset_class = filters.ConsoleServerPortFilterSet
506+
brief_prefetch_fields = ['device']
504507

505508

506509
class PowerPortViewSet(PathEndpointMixin, ModelViewSet):
507510
queryset = PowerPort.objects.prefetch_related('device', '_path__destination', 'cable', '_cable_peer', 'tags')
508511
serializer_class = serializers.PowerPortSerializer
509512
filterset_class = filters.PowerPortFilterSet
513+
brief_prefetch_fields = ['device']
510514

511515

512516
class PowerOutletViewSet(PathEndpointMixin, ModelViewSet):
513517
queryset = PowerOutlet.objects.prefetch_related('device', '_path__destination', 'cable', '_cable_peer', 'tags')
514518
serializer_class = serializers.PowerOutletSerializer
515519
filterset_class = filters.PowerOutletFilterSet
520+
brief_prefetch_fields = ['device']
516521

517522

518523
class InterfaceViewSet(PathEndpointMixin, ModelViewSet):
@@ -521,30 +526,35 @@ class InterfaceViewSet(PathEndpointMixin, ModelViewSet):
521526
)
522527
serializer_class = serializers.InterfaceSerializer
523528
filterset_class = filters.InterfaceFilterSet
529+
brief_prefetch_fields = ['device']
524530

525531

526532
class FrontPortViewSet(PassThroughPortMixin, ModelViewSet):
527533
queryset = FrontPort.objects.prefetch_related('device__device_type__manufacturer', 'rear_port', 'cable', 'tags')
528534
serializer_class = serializers.FrontPortSerializer
529535
filterset_class = filters.FrontPortFilterSet
536+
brief_prefetch_fields = ['device']
530537

531538

532539
class RearPortViewSet(PassThroughPortMixin, ModelViewSet):
533540
queryset = RearPort.objects.prefetch_related('device__device_type__manufacturer', 'cable', 'tags')
534541
serializer_class = serializers.RearPortSerializer
535542
filterset_class = filters.RearPortFilterSet
543+
brief_prefetch_fields = ['device']
536544

537545

538546
class DeviceBayViewSet(ModelViewSet):
539547
queryset = DeviceBay.objects.prefetch_related('installed_device').prefetch_related('tags')
540548
serializer_class = serializers.DeviceBaySerializer
541549
filterset_class = filters.DeviceBayFilterSet
550+
brief_prefetch_fields = ['device']
542551

543552

544553
class InventoryItemViewSet(ModelViewSet):
545554
queryset = InventoryItem.objects.prefetch_related('device', 'manufacturer').prefetch_related('tags')
546555
serializer_class = serializers.InventoryItemSerializer
547556
filterset_class = filters.InventoryItemFilterSet
557+
brief_prefetch_fields = ['device']
548558

549559

550560
#
@@ -600,6 +610,7 @@ class VirtualChassisViewSet(ModelViewSet):
600610
)
601611
serializer_class = serializers.VirtualChassisSerializer
602612
filterset_class = filters.VirtualChassisFilterSet
613+
brief_prefetch_fields = ['master']
603614

604615

605616
#

netbox/extras/api/views.py

+4-5
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ class ConfigContextQuerySetMixin:
3939
Provides a get_queryset() method which deals with adding the config context
4040
data annotation or not.
4141
"""
42-
4342
def get_queryset(self):
4443
"""
4544
Build the proper queryset based on the request context
@@ -49,11 +48,11 @@ def get_queryset(self):
4948
5049
Else, return the queryset annotated with config context data
5150
"""
52-
51+
queryset = super().get_queryset()
5352
request = self.get_serializer_context()['request']
54-
if request.query_params.get('brief') or 'config_context' in request.query_params.get('exclude', []):
55-
return self.queryset
56-
return self.queryset.annotate_config_context_data()
53+
if self.brief or 'config_context' in request.query_params.get('exclude', []):
54+
return queryset
55+
return queryset.annotate_config_context_data()
5756

5857

5958
#

netbox/netbox/api/views.py

+24-16
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@
99
from django.db import transaction
1010
from django.db.models import ProtectedError
1111
from django_rq.queues import get_connection
12-
from rest_framework import mixins, status
12+
from rest_framework import status
1313
from rest_framework.response import Response
1414
from rest_framework.reverse import reverse
1515
from rest_framework.views import APIView
16-
from rest_framework.viewsets import GenericViewSet
16+
from rest_framework.viewsets import ModelViewSet as ModelViewSet_
1717
from rq.worker import Worker
1818

1919
from netbox.api import BulkOperationSerializer
@@ -120,17 +120,13 @@ def perform_bulk_destroy(self, objects):
120120
# Viewsets
121121
#
122122

123-
class ModelViewSet(mixins.CreateModelMixin,
124-
mixins.RetrieveModelMixin,
125-
mixins.UpdateModelMixin,
126-
mixins.DestroyModelMixin,
127-
mixins.ListModelMixin,
128-
BulkUpdateModelMixin,
129-
BulkDestroyModelMixin,
130-
GenericViewSet):
123+
class ModelViewSet(BulkUpdateModelMixin, BulkDestroyModelMixin, ModelViewSet_):
131124
"""
132-
Accept either a single object or a list of objects to create.
125+
Extend DRF's ModelViewSet to support bulk update and delete functions.
133126
"""
127+
brief = False
128+
brief_prefetch_fields = []
129+
134130
def get_serializer(self, *args, **kwargs):
135131

136132
# If a list of objects has been provided, initialize the serializer with many=True
@@ -142,22 +138,34 @@ def get_serializer(self, *args, **kwargs):
142138
def get_serializer_class(self):
143139
logger = logging.getLogger('netbox.api.views.ModelViewSet')
144140

145-
# If 'brief' has been passed as a query param, find and return the nested serializer for this model, if one
146-
# exists
147-
request = self.get_serializer_context()['request']
148-
if request.query_params.get('brief'):
141+
# If using 'brief' mode, find and return the nested serializer for this model, if one exists
142+
if self.brief:
149143
logger.debug("Request is for 'brief' format; initializing nested serializer")
150144
try:
151145
serializer = get_serializer_for_model(self.queryset.model, prefix='Nested')
152146
logger.debug(f"Using serializer {serializer}")
153147
return serializer
154148
except SerializerNotFound:
155-
pass
149+
logger.debug(f"Nested serializer for {self.queryset.model} not found!")
156150

157151
# Fall back to the hard-coded serializer class
158152
logger.debug(f"Using serializer {self.serializer_class}")
159153
return self.serializer_class
160154

155+
def get_queryset(self):
156+
# If using brief mode, clear all prefetches from the queryset and append only brief_prefetch_fields (if any)
157+
if self.brief:
158+
return super().get_queryset().prefetch_related(None).prefetch_related(*self.brief_prefetch_fields)
159+
160+
return super().get_queryset()
161+
162+
def initialize_request(self, request, *args, **kwargs):
163+
# Check if brief=True has been passed
164+
if request.method == 'GET' and request.GET.get('brief'):
165+
self.brief = True
166+
167+
return super().initialize_request(request, *args, **kwargs)
168+
161169
def initial(self, request, *args, **kwargs):
162170
super().initial(request, *args, **kwargs)
163171

netbox/virtualization/api/views.py

+1
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,4 @@ class VMInterfaceViewSet(ModelViewSet):
8484
)
8585
serializer_class = serializers.VMInterfaceSerializer
8686
filterset_class = filters.VMInterfaceFilterSet
87+
brief_prefetch_fields = ['virtual_machine']

0 commit comments

Comments
 (0)