[Django]-Annotate Total Count of Descents of Mptt Model

4👍

queryset.annotate(
    descendants_count=Floor((F('rght') - F('lft') - 1) / 2)
).values(
    'descendants_count'
).aggregate(
    total_count=Count('descendants_count')
)

Let me explain

First, current method of get_descendant_count just operates existing data, so we can use it in Queryset.

def get_descendant_count(self):
    """
    Returns the number of descendants this model instance has.
    """
    if self._mpttfield('right') is None:
        # node not saved yet
        return 0
    else:
        return (self._mpttfield('right') - self._mpttfield('left') - 1) // 2

This is the current mptt models’ method. In queryset we are sure that all of instances is already saved so we’ll skip that.

Next step is to transform math operations into db expressions.
In Django 3.0 appeared Floor expression. But we can use it even in 1.7 (as I do)

from django.db.models.lookups import Transform

class Floor(Transform):
     function = 'FLOOR'
     lookup_name = 'floor'

If you want you can refactor this to use self._mpttfield('right') analog instead of hardcoded rght, lftand make this as Manager method

Let’s test. I have top element with descendants

In [1]: m = MenuItem.objects.get(id=settings.TOP_MENU_ID)

In [2]: m.get_descendant_count()
Out[2]: 226

In [3]: n = m.get_descendants()

In [4]: n.annotate(descendants_count=Floor((F('rght') - F('lft') - 1) / 2)).values('descendants_count').aggregate(total_count=Count('descendants_count'))
Out[4]: {'total_count': 226}

Leave a comment