Reputation: 43
I have models named Product, Material, and Tag. Each product has many materials, each material has many tags. Each product has tags (not an actual relation) , the tags are defined as all distinct tags of the product's materials. I would like to pass Product queryset with tags attribute to django rest serializer without N+1 problem.
I used subquery, but it only return a row and a column. I tried to operate it at python level, but it produces N+1 problem, and even if I somehow manage to avoid N+1 problem it will be slower than using ORM because it's python. I tried to add tags relation to Product, and update it everytime the material and/or material's tags changed, and query it with prefetch_related so it has no N+1, it works great, but adding more complexities to my code for something that could be so simple. So I prefer to not add tags relation in product.
class Product(models.Model):
name = models.CharField(max_length=255)
class Material(models.Model):
name = models.CharField(max_length=255)
product = models.ForeignKey(Product, related_name='materials', on_delete=models.CASCADE)
class Tag(models.Model):
name = models.CharField(max_length=255)
material = models.ForeignKey(Material, related_name='tags', on_delete=models.CASCADE)
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = '__all__'
class ProductSerializer(serializers.ModelSerializer):
tags = TagSerializer(many=True)
class Meta:
model = Product
fields = '__all__'
I expect to have queryset that can be passed to ProductSerializer without N+1 problem and without using signals.
Upvotes: 1
Views: 284
Reputation: 476709
You don't need to load related objects yourself. Django has tooling to do that for you. You can make use of .prefetch_related(..)
[Django-doc]. This will make queries to retrieve the related objects with an additional query, and then link the objects to the related object. It thus basically performs a JOIN at the Python/Django side and only uses a constant number of queries for these JOINs:
products = Product.objects.prefetch_related('materials__tags')
serializer = ProductSerializer(products, many=True)
Here we thus avoid fetching the tags
for each Product
with another query.
Upvotes: 3