Django sort by annotate in model Meta

Viewed 320

Is there a way to set ordering in a models Meta class by an annotated field?

i.e.

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

class ModelB(models.Model):
    name    = models.CharField()
    model_a = models.ForeignKey(ModelA)

This is what i can do for queryset but need same way to do on class Meta if possible

ModalA.objects.all()
      .annotate(
          count_b=Count("modal_a")
      )
      .order_by("count_b")

1 Answers

You can override the .objects manager to the soring, so:

class ModelAManager(models.Manager):

    def get_queryset(self, *args, **kwargs):
        return super().get_queryset(*args, **kwargs).annotate(
            count_b = Count('modelb')
        ).order_by('count_b')

and then register this as the manager of ModelA:

class ModelA(models.Model):
    # …
    objects = ModelAManager()

That being said, I would order by the count in the views where you need this, and not in general, here you make a LEFT OUTER JOIN, but it means that if you make extra joins through filtering, annotating, etc. the two (or more) JOINs will interact with each other, and thus act as a multiplier to the other LEFT OUTER JOINs.

It thus might be better to simply order by the number of related ModelBs when necessary.

We can also add a method to the Queryset that makes it possible to do the ordering by count_b through a method:

class ModelAQueryset(models.QuerySet):
    def order_by_b(self):
        return self.annotate(
            count_b=Count('modelb')
        ).order_by('count_b')

then we define a manager based on the ModelAQueryset:

class ModelA(models.Model):
    # …
    objects = ModelAQueryset.as_manager()

then you can order on b with:

ModelA.objects.order_by_b()

You thus can call .order_by_b() in the queryset at any part in the chain where you construct the queryset.

Related