Заставьте Django ORM автоматически получать свойства

Допустим, у меня есть модель Post следующего вида:

class Post(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    text_content = models.CharField()

    @property
    def comment_count(self):
        return self.comments.count()

Допустим, мне нужно получить все данные для поста с ID 3. Я знаю, что могу получить пользователя вместе с постом в одном запросе, выполнив Post.objects.select_related('user').get(pk=3), что позволит мне сохранить запрос. Однако есть ли способ автоматически получить также и comment_count?

Я знаю, что могу использовать Post.objects.annotate(comment_count=Count('comments')) (что потребует от меня удаления свойства или изменения имени аннотации, поскольку будет AttributeError), однако возможно ли заставить ORM сделать эту часть автоматически, поскольку свойство объявлено в модели?

Хотя я мог бы просто добавить .annotate, это может стать очень утомительным, когда есть несколько свойств и внешних ключей, которые должны быть получены в нескольких местах.

Возможно, это не идеальное решение для вас, но вы можете найти что-то похожее, что работает. Вы можете добавить пользовательский менеджер объектов, смотрите документацию

Тогда вы можете добавить метод типа with_comment_count(). ИЛИ если вы всегда хотите аннотировать, то вы можете изменить исходный кверисет.

class PostManager(models.Manager):
    def with_comment_count(self):
        return self.annotate(
            comment_count=Count('comments')
        )
    # Or this to always annotate
    def get_queryset(self):
        return super().get_queryset().annotate(
            comment_count=Count('comments')
        )

class Post(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    text_content = models.CharField()
    objects = PostManager()

Тогда вы можете запросить следующим образом.

post = Post.objects.get(pk=1).with_comment_count()
post.comment_count

Благодаря Felix Eklöf мне удалось заставить его работать! Как указано в вопросе, это было в случае, когда есть несколько свойств/внешних ключей, которые нуждаются в предварительной выборке, и как таковая возможность "цепной" предварительной выборки была необходима. Это невозможно с помощью цепочки методов PostManager, так как они возвращают не PostManager, а QuerySet, однако я придумал функцию, которая кэширует все, что ее просят кэшировать, сразу, избегая необходимости цепочки методов:

class PostManager(models.Manager):
    def caching(self, *args):
        query = self.all()
        if 'views' in args:
            query = query.annotate(view_count=Count('views'))
        if 'comments' in args:
            query = query.annotate(comment_count=Count('comments'))
        if 'user' in args:
            query = query.select_related('user')
        if 'archives' in args:
            query = query.prefetch_related('archives')
        return query

class Post(models.Model):
    objects = PostManager()
    ...

# caching can then be added by doing, this, for instance:
Post.objects.caching('views', 'user', 'archives').get(id=3)
Вернуться на верх