linux newbie
linux newbie

Reputation: 43

Django annotate a queryset with objects

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

Answers (1)

willeM_ Van Onsem
willeM_ Van Onsem

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

Related Questions