diff --git a/netbox/utilities/api.py b/netbox/utilities/api.py index 6793c05263..baf7e2f979 100644 --- a/netbox/utilities/api.py +++ b/netbox/utilities/api.py @@ -1,8 +1,7 @@ -from django.contrib.contenttypes.fields import GenericForeignKey from django.core.exceptions import ( FieldDoesNotExist, FieldError, MultipleObjectsReturned, ObjectDoesNotExist, ValidationError, ) -from django.db.models.fields.related import ManyToOneRel, RelatedField +from django.db.models import Prefetch from django.urls import reverse from django.utils.module_loading import import_string from django.utils.translation import gettext_lazy as _ @@ -76,7 +75,7 @@ def get_view_name(view): return drf_get_view_name(view) -def get_prefetches_for_serializer(serializer_class, fields_to_include=None): +def get_prefetches_for_serializer(serializer_class, fields_to_include=None, source_field=None): """ Compile and return a list of fields which should be prefetched on the queryset for a serializer. """ @@ -87,6 +86,14 @@ def get_prefetches_for_serializer(serializer_class, fields_to_include=None): fields_to_include = serializer_class.Meta.fields prefetch_fields = [] + + # If this serializer is nested, get annotations and prefetches for the nested serializer + if source_field is not None: + nested_annotations = get_annotations_for_serializer(serializer_class, fields_to_include=fields_to_include) + if nested_annotations: + related_prefetch = Prefetch(source_field, queryset=model.objects.all().annotate(**nested_annotations)) + prefetch_fields.append(related_prefetch) + for field_name in fields_to_include: serializer_field = serializer_class._declared_fields.get(field_name) @@ -98,20 +105,24 @@ def get_prefetches_for_serializer(serializer_class, fields_to_include=None): # 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 + # Prefetch for tags + if field.name == 'tags': + prefetch_fields.append('tags') + # If this field is represented by a nested serializer, recurse to resolve prefetches # for the related object. - if serializer_field: + if serializer_field and source_field is None: if issubclass(type(serializer_field), Serializer): # 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}') - + for subfield in get_prefetches_for_serializer(type(serializer_field), subfields, field_name): + if isinstance(subfield, Prefetch): + prefetch_fields.append(subfield) + else: + prefetch_fields.append(f'{field_name}__{subfield}') return prefetch_fields