David Downes David Downes - 7 months ago 30
Python Question

Order a django queryset by ManyToManyField = Value

If have some models like:

class Tag(models.Model):
name = models.CharField()

class Thing(models.Model):
title = models.CharField()
tags = models.ManyToManyField(Tag)


I can do a filter:

Thing.objects.filter(tags__name='foo')
Thing.objects.filter(tags__name__in=['foo', 'bar'])


But is it possible to order a queryset on the tags value?

Thing.objects.order_by(tags__name='foo')
Thing.objects.order_by(tags__name__in=['foo','bar'])


What I would expect (or like) back in this example, would be ALL Thing models, but ordered where they have a Tag/Tags that I know. I don't want to filter them out, but bring them to the top.

I gather this is possible using the FIELD operator, but seemingly I can only make it work on columns in that models table, e.g. title, but not on linked tables.

Thanks!

Answer

I'd try annotate the query with the conditional value that turns true when the tag is in the list you provide

from django.db.models import Case, When, IntegerField

Thing.objects.annotate(tag_is_known=Case(
    When(tags__name__in=['foo', 'bar'], then=1),
    default=0,
    output_field=IntegerField()
))

Next we filter using that annotation

Thing.objects.annotate(tag_is_known=...).order_by('tag_is_known')

Boolean version

Thing.objects.annotate(tag_is_known=Case(
    When(tags__name__in=['foo', 'bar'], then=True),
    default=False,
    output_field=BooleanField()
))