Julio Cesar
Julio Cesar

Reputation: 265

filter records based on a relationship | Django

I have the following models:

class PodcastPlatform(models.Model):
    name = models.CharField(max_length = 60)
    badge_path = models.FilePathField(path = settings.BADGES_DIR, unique = True)

    class Meta:
        ordering = ['name']

    def __str__(self):
        return self.name

class Podcast(models.Model):
    name = models.CharField(max_length = 60)
    description = models.CharField(max_length = 400)
    created = models.DateTimeField(auto_now_add = True)
    updated = models.DateTimeField(auto_now = True)

    class Meta:
        ordering = ['-created', '-updated']

    def __str__(self):
        return self.name

class PodcastLink(models.Model):
    podcast_platform = models.ForeignKey(PodcastPlatform, on_delete = models.CASCADE)
    podcast = models.ForeignKey(Podcast, on_delete = models.CASCADE)
    url = models.URLField(max_length = 400, verbose_name = 'URL')

    class Meta:
        ordering = ['podcast_platform__name']
        unique_together = ['podcast_platform', 'podcast']

    def __str__(self):
        return self.podcast_platform.name

    def get_badge_url(self):
        return static(re.search('img/.{0,}', self.podcast_platform.badge_path).group())

I want to filter the instances of the Podcast model where they have at least one link or a related instance of the PodcastLink model. To do it, I did the following:

Podcast.objects.filter(podcastlink__gte = 1)

Returning the following QuerySet:

<QuerySet [<Podcast: Libero tenetur>, <Podcast: Libero tenetur>, <Podcast: Libero tenetur>, <Podcast: Assumenda iusto>, <Podcast: Assumenda iusto>, <Podcast: Assumenda iusto>, <Podcast: Assumenda iusto>, <Podcast: Explicabo>, <Podcast: Explicabo>, <Podcast: Explicabo>, <Podcast: Explicabo>, <Podcast: Explicabo>]>

I get the expected result, but instead I notice that the instances are repeating... Try the following and in the same way I got the same result (repeated instances):

Podcast.objects.filter(Q(podcastlink__gte = 1))

Why do I get repeated instances? as i understand this should not happen... plus it's weird...

Upvotes: 0

Views: 683

Answers (2)

Antoine Pinsard
Antoine Pinsard

Reputation: 34942

I assume that you have related_name='podcastlink' on PodcastLink.podcast or some logic that sets it automatically, otherwise your code should not work.

Podcast.objects.filter(podcastlink__gte=1). Does not make any sens. podcastlink is a relation not a number. I guess it works by chance with podcastlink__gte=1 but wouldn't work with podcastlink__gte=2. To test existence of a relation you should rather use podcastlink__isnull=False:

Podcast.objects.filter(podcastlink__isnull=False)

However, this query will join Podcast and PodcastLink and return one entry for each combination. As you can have many PodcastLink per Podcast, you may get duplicates of Podcast. To avoid these duplicates, use `distinct().

Podcast.objects.filter(podcastlink__isnull=False).distinct()

Another approach is to actually count the number of links per podcast:

from django.db.models import Count
Podcast.objects.annotate(nb_links=Count('podcastlink')).filter(nb_links__gte=1)

In this case, you don't need to use distinct() because you aggregated the PodcastLink as a counter in Podcast. Using this method, you can also now decide to filter Podcast having at least 2 links as well if you want:

Podcast.objects.annotate(nb_links=Count('podcastlink')).filter(nb_links__gte=2)

Upvotes: 1

Daniel
Daniel

Reputation: 3527

You can use distinct() to get unique instances. You probably also want to filter on the count:

Podcast.objects.filter(podcastlink__count__gte = 1).distinct()

Upvotes: 1

Related Questions