Подсчет всех элементов в узлах django-treebeard Materialised Path

Я использую django-treebeard для создания дерева Категорий, каждая из которых содержит несколько Книг:

from django.db import models
from treebeard.mp_tree import MP_Node

class Book(models.Model):
    title = models.CharField(max_length=255, blank=False, null=False)
    categories = models.ManyToManyField("Category", related_name="books", blank=True)

class Category(MP_Node):
    title = models.CharField(max_length=255, blank=False, null=False)
    book_count = models.IntegerField(default=0, blank=False,
        help_text="Number of Books in this Category")
    total_book_count = models.IntegerField(default=0, blank=False,
        help_text="Number of Books in this Category and its descendants")

Я хочу отобразить количество книг в каждой категории, хранящихся в Category.book_count, поэтому у меня есть сигнал post_save следующего вида:

from django.dispatch import receiver

@receiver(post_save, sender=Book)
def book_post_save(sender, **kwargs):
    for category in kwargs["instance"].categories.all():
        category.book_count = category.websites.count()
        category.save()

(В реальности все немного сложнее, но суть такова)

Это работает отлично, и я также могу сделать подобное для сигнала m2m_changed, если категории книги изменились.

НО, я также хочу подсчитать total_book_count - общее количество книг в категории и во всех ее потомках. Я могу сделать это с помощью метода, подобного следующему на Category:

def set_total_book_count(self):
    # All the Books in this Category's descendants:
    descendant_books = Book.objects.filter(categories__in=self.get_descendants())
    # Combine with the Books in this Category but avoid counting duplicates:
    books = (descendant_books | self.books).distinct()
    self.total_book_count = books.count()

Это тоже работает, и я могу вызывать его из сигналов. НО все это становится сложным, особенно если я перемещаю категорию - мне придется пересчитать total_book_count для перемещенной категории, всех ее предыдущих предков и всех ее новых предков. В этот момент, возможно, проще пересчитать подсчеты для ВСЕХ категорий... процесс, который имеет свои трудности (например, с чего начать пересчет).

Другие люди наверняка уже делали что-то подобное, и поэтому я задаюсь вопросом, не изобретаю ли я заново колесо? Есть ли существующие реализации такого рода вещей?

Это частичный ответ, поскольку это простой способ пересчета всех подсчетов для всех категорий. Но я ожидаю, что он может оказаться медленным, если есть много категорий и много книг, учитывая, что set_total_book_count() нужно сделать для каждой категории.

categories = Category.get_tree()

for category in categories:
    category.book_count = category.books.count()
    category.set_total_book_count()
    category.save()
Вернуться на верх