From 00073af7a6a6894fb1873bfd5b5531a2e4fd2f89 Mon Sep 17 00:00:00 2001 From: Renato Almeida de Oliveira Zaroubin Date: Thu, 6 Feb 2025 01:32:49 +0000 Subject: [PATCH 1/4] Change get_prefetches_for_serializer to annotate nested serializers --- netbox/utilities/api.py | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/netbox/utilities/api.py b/netbox/utilities/api.py index 6793c052633..8f356ac5332 100644 --- a/netbox/utilities/api.py +++ b/netbox/utilities/api.py @@ -3,6 +3,7 @@ FieldDoesNotExist, FieldError, MultipleObjectsReturned, ObjectDoesNotExist, ValidationError, ) from django.db.models.fields.related import ManyToOneRel, RelatedField +from django.db.models import Count, Prefetch from django.urls import reverse from django.utils.module_loading import import_string from django.utils.translation import gettext_lazy as _ @@ -76,7 +77,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 +88,7 @@ def get_prefetches_for_serializer(serializer_class, fields_to_include=None): fields_to_include = serializer_class.Meta.fields prefetch_fields = [] + annotaded_prefetch = {} for field_name in fields_to_include: serializer_field = serializer_class._declared_fields.get(field_name) @@ -95,23 +97,37 @@ def get_prefetches_for_serializer(serializer_class, fields_to_include=None): if serializer_field and serializer_field.source: model_field_name = serializer_field.source + # If the serializer field is a RelatedObjectCountField and its a nested field + # Add an annotation to the annotaded_prefetch + if isinstance(serializer_field, RelatedObjectCountField) and source_field is not None: + if model_field_name not in annotaded_prefetch: + annotaded_prefetch[model_field_name] = Count(serializer_field.relation) + # 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)): + if (isinstance(field, (RelatedField, ManyToOneRel, GenericForeignKey)) and + not issubclass(type(serializer_field), Serializer)): prefetch_fields.append(field.name) except FieldDoesNotExist: continue # 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}') + + # If there are annotaded_prefetch, add the annotaded prefetch to the prefetch_fields + if annotaded_prefetch: + related_prefetch = Prefetch(source_field, queryset=model.objects.all().annotate(**annotaded_prefetch)) + prefetch_fields.append(related_prefetch) return prefetch_fields From 5ea85b410e7122e7f5089f65e134daa50a994169 Mon Sep 17 00:00:00 2001 From: Renato Almeida de Oliveira Zaroubin Date: Tue, 25 Feb 2025 01:13:33 +0000 Subject: [PATCH 2/4] fix typo --- netbox/utilities/api.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/netbox/utilities/api.py b/netbox/utilities/api.py index 8f356ac5332..cd1c947bfdd 100644 --- a/netbox/utilities/api.py +++ b/netbox/utilities/api.py @@ -88,7 +88,7 @@ def get_prefetches_for_serializer(serializer_class, fields_to_include=None, sour fields_to_include = serializer_class.Meta.fields prefetch_fields = [] - annotaded_prefetch = {} + annotated_prefetch = {} for field_name in fields_to_include: serializer_field = serializer_class._declared_fields.get(field_name) @@ -98,10 +98,10 @@ def get_prefetches_for_serializer(serializer_class, fields_to_include=None, sour model_field_name = serializer_field.source # If the serializer field is a RelatedObjectCountField and its a nested field - # Add an annotation to the annotaded_prefetch + # Add an annotation to the annotated_prefetch if isinstance(serializer_field, RelatedObjectCountField) and source_field is not None: - if model_field_name not in annotaded_prefetch: - annotaded_prefetch[model_field_name] = Count(serializer_field.relation) + if model_field_name not in annotated_prefetch: + annotated_prefetch[model_field_name] = Count(serializer_field.relation) # If the serializer field does not map to a discrete model field, skip it. try: @@ -124,9 +124,9 @@ def get_prefetches_for_serializer(serializer_class, fields_to_include=None, sour else: prefetch_fields.append(f'{field_name}__{subfield}') - # If there are annotaded_prefetch, add the annotaded prefetch to the prefetch_fields - if annotaded_prefetch: - related_prefetch = Prefetch(source_field, queryset=model.objects.all().annotate(**annotaded_prefetch)) + # If there are annotated_prefetch, add the annotaded prefetch to the prefetch_fields + if annotated_prefetch: + related_prefetch = Prefetch(source_field, queryset=model.objects.all().annotate(**annotated_prefetch)) prefetch_fields.append(related_prefetch) return prefetch_fields From 53e8da674fbc9bc41db2d89018860a667d17586d Mon Sep 17 00:00:00 2001 From: Renato Almeida de Oliveira Zaroubin Date: Wed, 26 Feb 2025 12:01:06 +0000 Subject: [PATCH 3/4] refactor get_prefetches_for_serializer --- netbox/utilities/api.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/netbox/utilities/api.py b/netbox/utilities/api.py index cd1c947bfdd..39a2395d7e1 100644 --- a/netbox/utilities/api.py +++ b/netbox/utilities/api.py @@ -3,7 +3,7 @@ FieldDoesNotExist, FieldError, MultipleObjectsReturned, ObjectDoesNotExist, ValidationError, ) from django.db.models.fields.related import ManyToOneRel, RelatedField -from django.db.models import Count, Prefetch +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 _ @@ -88,7 +88,14 @@ def get_prefetches_for_serializer(serializer_class, fields_to_include=None, sour fields_to_include = serializer_class.Meta.fields prefetch_fields = [] - annotated_prefetch = {} + + # 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) @@ -97,12 +104,6 @@ def get_prefetches_for_serializer(serializer_class, fields_to_include=None, sour if serializer_field and serializer_field.source: model_field_name = serializer_field.source - # If the serializer field is a RelatedObjectCountField and its a nested field - # Add an annotation to the annotated_prefetch - if isinstance(serializer_field, RelatedObjectCountField) and source_field is not None: - if model_field_name not in annotated_prefetch: - annotated_prefetch[model_field_name] = Count(serializer_field.relation) - # If the serializer field does not map to a discrete model field, skip it. try: field = model._meta.get_field(model_field_name) @@ -124,10 +125,6 @@ def get_prefetches_for_serializer(serializer_class, fields_to_include=None, sour else: prefetch_fields.append(f'{field_name}__{subfield}') - # If there are annotated_prefetch, add the annotaded prefetch to the prefetch_fields - if annotated_prefetch: - related_prefetch = Prefetch(source_field, queryset=model.objects.all().annotate(**annotated_prefetch)) - prefetch_fields.append(related_prefetch) return prefetch_fields From 603253ddd7b8a6b27ae7a636d4fd3781a24500a7 Mon Sep 17 00:00:00 2001 From: Renato Almeida de Oliveira Zaroubin Date: Thu, 27 Feb 2025 02:40:15 +0000 Subject: [PATCH 4/4] Removed prefetch for model fields, since its a Serializer and add for tags --- netbox/utilities/api.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/netbox/utilities/api.py b/netbox/utilities/api.py index 39a2395d7e1..baf7e2f9798 100644 --- a/netbox/utilities/api.py +++ b/netbox/utilities/api.py @@ -1,8 +1,6 @@ -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 @@ -107,12 +105,13 @@ def get_prefetches_for_serializer(serializer_class, fields_to_include=None, sour # 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)) and - not issubclass(type(serializer_field), Serializer)): - 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 and source_field is None: @@ -124,7 +123,6 @@ def get_prefetches_for_serializer(serializer_class, fields_to_include=None, sour prefetch_fields.append(subfield) else: prefetch_fields.append(f'{field_name}__{subfield}') - return prefetch_fields