Skip to content

Commit a20715f

Browse files
Fixes #19321: Reduce redundant database queries during bulk creation of devices (#19993)
* Fixes #19321: Reduce redundant database queries during bulk creation of devices * Add test for test_get_prefetchable_fields
1 parent 1b8767f commit a20715f

File tree

3 files changed

+57
-2
lines changed

3 files changed

+57
-2
lines changed

netbox/dcim/models/devices.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from django.core.files.storage import default_storage
99
from django.core.validators import MaxValueValidator, MinValueValidator
1010
from django.db import models
11-
from django.db.models import F, ProtectedError
11+
from django.db.models import F, ProtectedError, prefetch_related_objects
1212
from django.db.models.functions import Lower
1313
from django.db.models.signals import post_save
1414
from django.urls import reverse
@@ -28,6 +28,7 @@
2828
from netbox.models.mixins import WeightMixin
2929
from netbox.models.features import ContactsMixin, ImageAttachmentsMixin
3030
from utilities.fields import ColorField, CounterCacheField
31+
from utilities.prefetch import get_prefetchable_fields
3132
from utilities.tracking import TrackingModelMixin
3233
from .device_components import *
3334
from .mixins import RenderConfigMixin
@@ -924,7 +925,10 @@ def _instantiate_components(self, queryset, bulk_create=True):
924925
if cf_defaults := CustomField.objects.get_defaults_for_model(model):
925926
for component in components:
926927
component.custom_field_data = cf_defaults
927-
model.objects.bulk_create(components)
928+
components = model.objects.bulk_create(components)
929+
# Prefetch related objects to minimize queries needed during post_save
930+
prefetch_fields = get_prefetchable_fields(model)
931+
prefetch_related_objects(components, *prefetch_fields)
928932
# Manually send the post_save signal for each of the newly created components
929933
for component in components:
930934
post_save.send(

netbox/utilities/prefetch.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from django.contrib.contenttypes.fields import GenericRelation
2+
from django.db.models import ManyToManyField
3+
from django.db.models.fields.related import ForeignObjectRel
4+
from taggit.managers import TaggableManager
5+
6+
__all__ = (
7+
'get_prefetchable_fields',
8+
)
9+
10+
11+
def get_prefetchable_fields(model):
12+
"""
13+
Return a list containing the names of all fields on the given model which support prefetching.
14+
"""
15+
field_names = []
16+
17+
for field in model._meta.get_fields():
18+
# Forward relations (e.g. ManyToManyFields)
19+
if isinstance(field, ManyToManyField):
20+
field_names.append(field.name)
21+
22+
# Reverse relations (e.g. reverse ForeignKeys, reverse M2M)
23+
elif isinstance(field, ForeignObjectRel):
24+
field_names.append(field.get_accessor_name())
25+
26+
# Generic relations
27+
elif isinstance(field, GenericRelation):
28+
field_names.append(field.name)
29+
30+
# Tags
31+
elif isinstance(field, TaggableManager):
32+
field_names.append(field.name)
33+
34+
return field_names
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from circuits.models import Circuit, Provider
2+
from utilities.prefetch import get_prefetchable_fields
3+
from utilities.testing.base import TestCase
4+
5+
6+
class GetPrefetchableFieldsTest(TestCase):
7+
"""
8+
Verify the operation of get_prefetchable_fields()
9+
"""
10+
def test_get_prefetchable_fields(self):
11+
field_names = get_prefetchable_fields(Provider)
12+
self.assertIn('asns', field_names) # ManyToManyField
13+
self.assertIn('circuits', field_names) # Reverse relation
14+
self.assertIn('tags', field_names) # Tags
15+
16+
field_names = get_prefetchable_fields(Circuit)
17+
self.assertIn('group_assignments', field_names) # Generic relation

0 commit comments

Comments
 (0)