Home > Net >  Queryset only on the first foreign key linked to the instance
Queryset only on the first foreign key linked to the instance

Time:03-14

Let's say for example I have these models:

class Person(models.Model):
    name = models.CharField(max_length=50, blank=True, null=True, verbose_name=_('name'))


class Keyword(models.Model):
    value = models.CharField(max_length=50, blank=True, null=True, verbose_name=_('name'))
    person = models.ForeignKey('Keyword', on_delete=models.CASCADE)

I'd like to query on the Person model, where we filter on ONLY the first keyword linked to that person.

The keyword list is always ordered.

So for example:

Person John doe has 3 keyword (Programming, Travel, Family)

I'd like to do something like this (if this is possible)

Person.objects.filter(keyword_"first"="Programming") 

CodePudding user response:

You can annotate with .alias(…) [Django-doc] with a Subquery expression [Django-doc], and then filter:

from django.db.models import OuterRef, Subquery

class Person.objects.alias(
    first_keyword=Subquery(
        Keyword.objects.filter(
            person=OuterRef('pk')
        ).values('value')[:1]
    )
).filter(
    first_keyword='Programming'
)

But models are by default unordered: that means that each time a different keyword can be the first. Only if you have a column and an ordering option [Django-doc], or an .order_by(…) clause [Django-doc] in the subquery, it is guaranteed that a certain item will be the first keyword.

You can make the filtering optional with a field only_first that will filter on the first item if True and on all items if False:

from django.db.models import OuterRef, Q, Subquery

class Person.objects.alias(
    first_keyword=Subquery(
        Keyword.objects.filter(
            person=OuterRef('pk')
        ).values('value')[:1]
    )
).filter(
    Q(first_keyword='Programming') |
    Q(only_first=False, keyword__value='Programming')
)
  • Related