Заставьте 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)