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..769bdcb26 100644 --- a/netbox/netbox/api/viewsets/__init__.py +++ b/netbox/netbox/api/viewsets/__init__.py @@ -1,8 +1,11 @@ import logging +from functools import cached_property -from django.core.exceptions import ObjectDoesNotExist, PermissionDenied +from django.contrib.contenttypes.fields import GenericForeignKey +from django.core.exceptions import FieldDoesNotExist, ObjectDoesNotExist, PermissionDenied from django.db import transaction from django.db.models import ProtectedError, RestrictedError +from django.db.models.fields.related import ManyToOneRel, RelatedField from django_pglocks import advisory_lock from netbox.constants import ADVISORY_LOCK_KEYS from rest_framework import mixins as drf_mixins @@ -40,6 +43,40 @@ 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 + serializer_class = self.get_serializer_class() + model = serializer_class.Meta.model + fields_to_include = self.requested_fields or serializer_class.Meta.fields + prefetch = [] + for field_name in fields_to_include: + try: + field = model._meta.get_field(field_name) + except FieldDoesNotExist: + continue + if isinstance(field, (RelatedField, ManyToOneRel, GenericForeignKey)): + # TODO: Use serializer field source if set, else use its name + prefetch.append(field_name) + 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('include') + return requested_fields.split(',') if requested_fields else [] + class NetBoxReadOnlyModelViewSet( mixins.BriefModeMixin,