Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Querying ManyToMany in Django giving me duplicate results

I have two models

class Tag(models.Model):
   key = models.CharField(max_length=200)

class Post(models.Model):
   name = models.CharField(max_length=200)
   tags = models.ManyToManyField(Tag)

I m trying to filter out posts with a list of tags. Lets say tags heat and warm. I will get a list of tags in my api function(['heat', 'warm']). I want to filter all Post data which have tags whose keys are in the list. I tried many types and didn't get correct output. Is there a way to do this on single query?

like image 526
qwertyui90 Avatar asked Dec 18 '22 08:12

qwertyui90


2 Answers

All Post with this tag key equal to heat or warm

Post.objects.filter(tags__key_in=['heat', 'warm']) 

Add distinct to avoid duplicate :

Post.objects.filter(tags__key_in=['heat', 'warm']).distinct()
like image 194
Wilfried Avatar answered Feb 22 '23 23:02

Wilfried


While Wilfried's answer is correct and perfectly fine for a lot of use cases, it's worth noting that on the SQL level, DISTINCT may have a performance impact, especially if you expect you query to match a significant portion of your data (depending on DB and table sizes; see e.g. here and here for more details).

Another slightly more verbose option, which avoids this pitfall, is using a through model and a Subquery (introduced in django 1.11). Based on OPs code:

class Tag(models.Model):
    key = models.CharField(max_length=200)

class Post(models.Model):
    name = models.CharField(max_length=200)
    tags = models.ManyToManyField(Tag, through='Tagging')

class Tagging(models.Model):
    tag = models.ForeignKey(Tag, on_delete=models.CASCADE)
    post = models.ForeignKey(Post, on_delete=models.CASCADE)

and the respective query:

tags = Tagging.objects.filter(tag__key__in=['heat', 'warm'])
Post.objects.filter(pk__in=models.Subquery(tags.values('post__pk')))

Using .explain(analyze=True) (introduced in django 2.1) will help you make an informed decision.

like image 20
Leo Antunes Avatar answered Feb 22 '23 23:02

Leo Antunes