1

I have a tagging system, where the tags exist in a hierarchy. I've used django-mptt to build the Tag model. I want to display a group of tags differently if every sibling in the group is a leaf node.

To do this, I want a model method called all_siblings_leaf(). Here's my first take on that method:

    def all_siblings_leaf(self):
        """Return True if all siblings, including self, are leaf nodes."""
        siblings = self.get_siblings()
        return all([sibling.is_leaf_node() for sibling in siblings])

This works, but it's one trip to the db for the get_siblings() call. When I want to call all_siblings_leaf(), I've already queried for all the tags I want to use. Using this method repeatedly with a set of several hundred tags means several hundred new db calls.

Since I already have all the tags, I feel like I should be able to get the information about leaf nodes without any additional trips to the db. Here's what I've come up with:

    def all_siblings_leaf(self, all_tags):
        """Return True if all siblings including self are leaf nodes.

        all_tags should be a queryset that used select_related('parent')
        """
        if self.level == 0:
            siblings = [t for t in all_tags if t.level == 0]
        else:
            siblings = [t for t in all_tags if t.parent == self.parent]

        return all([sibling.is_leaf_node() for sibling in siblings])

This works; on a page where this method is used many times, there's only one trip to the db as measured by debug toolbar.

This feels really kludgy, going through each other node and checking if it has the same parent, since we've already fetched every tag's parent information. Is this a reasonable approach, or is there a simpler solution? I'm calling the method from a template while looping through tags, if that makes a difference.

japhyr
  • 1,710
  • 2
  • 18
  • 24
  • Hi! Have you figured it out already? – Andrey Nelubin May 21 '20 at 10:01
  • @AndreyNelubin, I haven't come up with anything better than this. I have dug more into MPTT, and I see that my approach of checking parents is pretty similar to what the default mptt methods do. But on further reflection I wonder if there's an efficient way to follow right tags until a tag has a different parent, then do the same working left in the tree. I'd still need to use my own method, passing in all_tags. – japhyr May 21 '20 at 16:13
  • It's interesting question, I'll think about it at this weekends – Andrey Nelubin May 22 '20 at 07:18

1 Answers1

0

Ok. First we have this answer

So your method will be:

def all_siblings_is_leaf(self, include_self=False):
    siblings = self.get_siblings(include_self=include_self)
    return siblings.annotate(
        descendants_count=Floor(
            (F(self._mpttfield('right')) - F(self._mpttfield('left')) - 1) / 2
        )
    ).values(
        'descendants_count'
    ).aggregate(
        total_count=Sum('descendants_count')
    )['total_count'] == 0

Which causes 1 DB request. For more information check related answer link

Andrey Nelubin
  • 3,084
  • 1
  • 20
  • 25