Replace nested serializers with primary serializers where possible

This commit is contained in:
Jeremy Stretch
2024-02-26 16:49:30 -05:00
parent d042e6f69d
commit c146f5e1b5
16 changed files with 1525 additions and 1437 deletions

View File

@@ -3,23 +3,26 @@ import sys
from django.conf import settings
from django.contrib.contenttypes.fields import GenericForeignKey
from django.core.exceptions import FieldDoesNotExist
from django.core.exceptions import (
FieldDoesNotExist, FieldError, MultipleObjectsReturned, ObjectDoesNotExist, ValidationError,
)
from django.db.models.fields.related import ManyToOneRel, RelatedField
from django.http import JsonResponse
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from rest_framework import status
from rest_framework.serializers import Serializer
from rest_framework.utils import formatting
from netbox.api.fields import RelatedObjectCountField
from netbox.api.exceptions import GraphQLTypeNotFound, SerializerNotFound
from utilities.utils import count_related
from .utils import dynamic_import
from .utils import count_related, dict_to_filter_params, dynamic_import
__all__ = (
'get_annotations_for_serializer',
'get_graphql_type_for_model',
'get_prefetches_for_serializer',
'get_related_object_by_attrs',
'get_serializer_for_model',
'get_view_name',
'is_api_request',
@@ -103,7 +106,7 @@ def get_prefetches_for_serializer(serializer_class, fields_to_include=None):
"""
model = serializer_class.Meta.model
# If specific fields are not specified, default to all
# If fields are not specified, default to all
if not fields_to_include:
fields_to_include = serializer_class.Meta.fields
@@ -128,7 +131,9 @@ def get_prefetches_for_serializer(serializer_class, fields_to_include=None):
# for the related object.
if serializer_field:
if issubclass(type(serializer_field), Serializer):
for subfield in get_prefetches_for_serializer(type(serializer_field)):
# Determine which fields to prefetch for the nested object
subfields = serializer_field.Meta.brief_fields if serializer_field.nested else None
for subfield in get_prefetches_for_serializer(type(serializer_field), subfields):
prefetch_fields.append(f'{field_name}__{subfield}')
return prefetch_fields
@@ -154,6 +159,48 @@ def get_annotations_for_serializer(serializer_class, fields_to_include=None):
return annotations
def get_related_object_by_attrs(queryset, attrs):
"""
Return an object identified by either a dictionary of attributes or its numeric primary key (ID). This is used
for referencing related objects when creating/updating objects via the REST API.
"""
if attrs is None:
return None
# Dictionary of related object attributes
if isinstance(attrs, dict):
params = dict_to_filter_params(attrs)
try:
return queryset.get(**params)
except ObjectDoesNotExist:
raise ValidationError(
_("Related object not found using the provided attributes: {params}").format(params=params))
except MultipleObjectsReturned:
raise ValidationError(
_("Multiple objects match the provided attributes: {params}").format(params=params)
)
except FieldError as e:
raise ValidationError(e)
# Integer PK of related object
try:
# Cast as integer in case a PK was mistakenly sent as a string
pk = int(attrs)
except (TypeError, ValueError):
raise ValidationError(
_(
"Related objects must be referenced by numeric ID or by dictionary of attributes. Received an "
"unrecognized value: {value}"
).format(value=attrs)
)
# Look up object by PK
try:
return queryset.get(pk=pk)
except ObjectDoesNotExist:
raise ValidationError(_("Related object not found using the provided numeric ID: {id}").format(id=pk))
def rest_api_server_error(request, *args, **kwargs):
"""
Handle exceptions and return a useful error message for REST API requests.