diff --git a/docs/integrations/rest-api.md b/docs/integrations/rest-api.md index 9b34ad238..bac984a08 100644 --- a/docs/integrations/rest-api.md +++ b/docs/integrations/rest-api.md @@ -250,13 +250,16 @@ Similarly, you can opt to omit only specific fields by passing the `omit` parame GET /api/dcim/sites/?omit=circuit_count,device_count,virtualmachine_count ``` -!!! note "The `omit` paramater was introduced in NetBox v4.5.2." +!!! note "The `omit` parameter was introduced in NetBox v4.5.2." -Strategic use of the `fields` and `omit` parameters can drastically improve REST API performance, as the exclusion of fields which reference related objects reduces the number and compelxity of underlying database queries needed to generate the response. +Strategic use of the `fields` and `omit` parameters can drastically improve REST API performance, as the exclusion of fields which reference related objects reduces the number and complexity of underlying database queries needed to generate the response. + +!!! note + The `fields` and `omit` parameters should be considered mutually exclusive. If both are passed, `fields` takes precedence. #### Brief Format -Most API endpoints support an optional "brief" format, which returns only a minimal representation of each object in the response. This is useful when you need only a list of available objects without any related data, such as when populating a drop-down list in a form. It's also more convenient than listing out individual fileds via the `fields` or `omit` parameters. As an example, the default (complete) format of a prefix looks like this: +Most API endpoints support an optional "brief" format, which returns only a minimal representation of each object in the response. This is useful when you need only a list of available objects without any related data, such as when populating a drop-down list in a form. It's also more convenient than listing out individual fields via the `fields` or `omit` parameters. As an example, the default (complete) format of a prefix looks like this: ```no-highlight GET /api/ipam/prefixes/13980/ diff --git a/netbox/netbox/api/serializers/base.py b/netbox/netbox/api/serializers/base.py index 7f66e12c7..16d57fc50 100644 --- a/netbox/netbox/api/serializers/base.py +++ b/netbox/netbox/api/serializers/base.py @@ -1,9 +1,8 @@ from functools import cached_property -from rest_framework import serializers -from rest_framework.utils.serializer_helpers import BindingDict -from drf_spectacular.utils import extend_schema_field from drf_spectacular.types import OpenApiTypes +from drf_spectacular.utils import extend_schema_field +from rest_framework import serializers from utilities.api import get_related_object_by_attrs from .fields import NetBoxAPIHyperlinkedIdentityField, NetBoxURLHyperlinkedIdentityField @@ -26,9 +25,11 @@ class BaseModelSerializer(serializers.ModelSerializer): :param nested: Set to True if this serializer is being employed within a parent serializer :param fields: An iterable of fields to include when rendering the serialized object, If nested is True but no fields are specified, Meta.brief_fields will be used. + :param omit: An iterable of fields to omit from the serialized object """ self.nested = nested - self._requested_fields = fields + self._include_fields = fields or [] + self._omit_fields = omit or [] # Disable validators for nested objects (which already exist) if self.nested: @@ -36,8 +37,8 @@ class BaseModelSerializer(serializers.ModelSerializer): # If this serializer is nested but no fields have been specified, # default to using Meta.brief_fields (if set) - if self.nested and not fields: - self._requested_fields = getattr(self.Meta, 'brief_fields', None) + if self.nested and not fields and not omit: + self._include_fields = getattr(self.Meta, 'brief_fields', None) super().__init__(*args, **kwargs) @@ -63,16 +64,20 @@ class BaseModelSerializer(serializers.ModelSerializer): @cached_property def fields(self): """ - Override the fields property to check for requested fields. If defined, - return only the applicable fields. + Override the fields property to return only specifically requested fields if needed. """ - if not self._requested_fields: - return super().fields + fields = super().fields + + # Include only requested fields + if self._include_fields: + fields = { + k: v for k, v in fields.items() if k in self._include_fields + } + + # Remove omitted fields + for field_name in set(self._omit_fields): + fields.pop(field_name, None) - fields = BindingDict(self) - for key, value in self.get_fields().items(): - if key in self._requested_fields: - fields[key] = value return fields @extend_schema_field(OpenApiTypes.STR) diff --git a/netbox/netbox/api/viewsets/__init__.py b/netbox/netbox/api/viewsets/__init__.py index 517eeaf4b..ea2195990 100644 --- a/netbox/netbox/api/viewsets/__init__.py +++ b/netbox/netbox/api/viewsets/__init__.py @@ -5,13 +5,13 @@ from django.core.exceptions import ObjectDoesNotExist, PermissionDenied from django.db import router, transaction from django.db.models import ProtectedError, RestrictedError from django_pglocks import advisory_lock -from netbox.constants import ADVISORY_LOCK_KEYS from rest_framework import mixins as drf_mixins from rest_framework import status from rest_framework.response import Response from rest_framework.viewsets import GenericViewSet from netbox.api.serializers.features import ChangeLogMessageSerializer +from netbox.constants import ADVISORY_LOCK_KEYS from utilities.api import get_annotations_for_serializer, get_prefetches_for_serializer from utilities.exceptions import AbortRequest from utilities.query import reapply_model_ordering @@ -75,6 +75,7 @@ class BaseViewSet(GenericViewSet): @cached_property def field_kwargs(self): + """Return a dictionary of keyword arguments to be passed when instantiating the serializer.""" # An explicit list of fields was requested if requested_fields := self.request.query_params.get('fields'): return {'fields': requested_fields.split(',')}