Hello, i’m struggling with one problem and i cant find the answer anywhere.
In our production setup we have models structure similar to this one (the models are much bigger, but its not important in the example) - please don’t mind model names they are purely exemplary
from django.db import models
class RelatedOnlyToMainModel(models.Model):
field_1 = models.IntegerField()
class SmallestModel(models.Model):
another_field = models.CharField(max_length=128)
class MainModel(models.Model):
model_related_only_to_main_model = models.ManyToManyField(RelatedOnlyToMainModel, related_name='mains')
smallests = models.ManyToManyField(SmallestModel, related_name='mains')
one_of_many_fields = models.IntegerField()
class ModelA(models.Model):
main = models.ForeignKey(MainModel, on_delete=models.SET_NULL, related_name='model_a', null=True)
some_field = models.BooleanField()
class IntermediateModel(models.Model):
model_a = models.ForeignKey(ModelA, on_delete=models.CASCADE, related_name='small_one_connectors')
smallest_model = models.ForeignKey(SmallestModel, on_delete=models.CASCADE, related_name='model_a_connectors')
irrelevant_field = models.TextField()
Given following serializers that include nested serializers (presumably the root of all trouble):
from rest_framework import serializers
import main.models as models
class RelatedOnlyToMainModelSerializer(serializers.ModelSerializer):
class Meta:
model = models.RelatedOnlyToMainModel
fields = '__all__'
class IntermediateModelSerializer(serializers.ModelSerializer):
class Meta:
model = models.IntermediateModel
fields = '__all__'
class ModelASerializer(serializers.ModelSerializer):
small_one_connectors = IntermediateModelSerializer(many=True)
class Meta:
model = models.ModelA
fields = '__all__'
def update(self, instance, validated_data):
intermediate_data = validated_data.pop('small_one_connectors', [])
intermediate_data_map = {str(intermediate.get('id')): intermediate for intermediate in intermediate_data}
to_update = []
for intermediate in instance.small_one_connectors.all():
new_data = intermediate_data_map.get(intermediate.id)
if new_data:
for key in ['irrelevant_field']:
field_value = new_data.get(key)
if field_value:
setattr(intermediate, key, field_value)
to_update.append(intermediate)
models.IntermediateModel.objects.bulk_update(to_update, ['irrelevant_field'])
return super(ModelASerializer, self).update(instance, validated_data)
class MainModelSerializer(serializers.ModelSerializer):
model_a = ModelASerializer(many=True)
model_related_only_to_main_model = RelatedOnlyToMainModelSerializer(many=True)
class Meta:
model = models.MainModel
fields = '__all__'
def update(self, instance, validated_data):
model_a_data = validated_data.pop('model_a', [])
model_a_data_map = {str(a.get('id')): a for a in model_a_data}
for a in instance.model_a.all():
try:
ModelASerializer(context=self.context).update(a, model_a_data_map[str(a.id)])
except:
pass
validated_data.pop('model_related_only_to_main_model', None)
return super(MainModelSerializer, self).update(instance, validated_data)
And the view:
from rest_framework.viewsets import ModelViewSet
from .models import MainModel
from .serializers import MainModelSerializer
class MainModelViewSet(ModelViewSet):
queryset = MainModel.objects
serializer_class = MainModelSerializer
def get_queryset(self):
return self.queryset.prefetch_related('model_related_only_to_main_model', 'model_a',
'model_a__small_one_connectors',
'model_a__small_one_connectors__smallest_model')
Command to populate the db
import random
from django.core.management.base import BaseCommand
from main.models import MainModel, RelatedOnlyToMainModel, SmallestModel, ModelA, IntermediateModel
class Command(BaseCommand):
help = 'Populate the database with MainModel, RelatedOnlyToMainModel, ModelA, SmallestModel, and IntermediateModel instances'
def handle(self, *args, **kwargs):
main_model = MainModel.objects.create(one_of_many_fields=1)
related_instances = [RelatedOnlyToMainModel.objects.create(field_1=i) for i in range(1, 6)]
main_model.model_related_only_to_main_model.set(related_instances)
model_a_instances = [ModelA.objects.create(main=main_model, some_field=(i % 2 == 0)) for i in range(10)]
smallest_model_instances = [SmallestModel.objects.create(another_field=f'SmallestModel {i}') for i in range(300)]
for smallest_model in smallest_model_instances:
model_a_instance = random.choice(model_a_instances)
IntermediateModel.objects.create(model_a=model_a_instance, smallest_model=smallest_model, irrelevant_field='Irrelevant')
main_model.smallests.set(smallest_model_instances)
self.stdout.write(self.style.SUCCESS('Database populated successfully'))
I’m looking for the way to decrease number of database queries (given models size it became much of a performance issues) required when sending the POST request (931 with the test data vs 6 at GET). I realize the ORM needs to do some queries to map and initialize the objects properly, but i’m unable to prefetch the data by hand for this operation. I would suspect similar way of doing this as for the GET method, where programmer is responsible for prefetching the data, and one can do it properly as one has knowledge about the models. I’ve tried many things such as overriding get_object() method of the serializer etc. but nothing seems to work. Given the complexity of my production models and serializers’ update() methods and poor test coverage, unfortunately i’m unable to create separate “SaveSerializers” that will not contain nested serializers and instead the update() would do anything “manually”. Does anyone knows the way to do it properly without losing existing functionalities e.g. model level defined validations?