使 Django ORM 自动获取属性
Make Django ORM automatically fetch properties
假设我有一个这样的 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 的所有数据。我知道我可以通过 [=11= 在一个查询中检索用户以及 post ],允许我保存查询。但是,有没有办法自动获取 comment_count
呢?
我知道我可以使用 Post.objects.annotate(comment_count=Count('comments'))
(这将要求我删除 属性 或更改注释的名称,因为会有 AttributeError
),但是可以让 ORM 自动执行该部分,因为 属性 是在模型中声明的?
虽然我可以只添加 .annotate
,但当有多个属性和外键需要在多个地方获取时,这会变得非常乏味。
也许这对您来说不是一个完美的解决方案,但您可能会发现类似的解决方案。您可以添加自定义对象管理器,see docs
然后你可以添加一个像with_comment_count()
这样的方法。或者如果你总是想注释,那么你可以 modify the initial queryset.
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 我才让它开始工作!如问题中所述,这是在有多个 properties/foreign 键需要预取的情况下,因此需要 'chain' 预取的能力。这通过链接 PostManager
方法是不可能的,因为它们不是 return 一个 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)
请注意,为了使其工作,我还必须将 @property
更改为 @cached_property
,从而允许 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 的所有数据。我知道我可以通过 [=11= 在一个查询中检索用户以及 post ],允许我保存查询。但是,有没有办法自动获取 comment_count
呢?
我知道我可以使用 Post.objects.annotate(comment_count=Count('comments'))
(这将要求我删除 属性 或更改注释的名称,因为会有 AttributeError
),但是可以让 ORM 自动执行该部分,因为 属性 是在模型中声明的?
虽然我可以只添加 .annotate
,但当有多个属性和外键需要在多个地方获取时,这会变得非常乏味。
也许这对您来说不是一个完美的解决方案,但您可能会发现类似的解决方案。您可以添加自定义对象管理器,see docs
然后你可以添加一个像with_comment_count()
这样的方法。或者如果你总是想注释,那么你可以 modify the initial queryset.
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 我才让它开始工作!如问题中所述,这是在有多个 properties/foreign 键需要预取的情况下,因此需要 'chain' 预取的能力。这通过链接 PostManager
方法是不可能的,因为它们不是 return 一个 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)
请注意,为了使其工作,我还必须将 @property
更改为 @cached_property
,从而允许 ORM 替换值,并允许使用相同的名称进行适当的缓存。