Django:在 values() dict 中包含预取对象
Django: including prefetched objects in values() dict
我想基于涉及预取对象的 django orm 查询创建字典或类似字典的对象。由于涉及的对象数量众多,我想尽可能多地在数据库中执行此操作。
要使用熟悉的示例结构,如果我有一个 Author 和 Books,具有从 Book 到 Author 的常用 ForeignKey,并且我还有一个 Edition,其 ForeignKey 返回 Book,我想做类似的事情
Author.objects.prefetch_related(Prefetch('book_set', queryset=Book.objects.filter(cycle__id=3).select_related('edition__binding'), to_attr="bindings")) # clearly this is wrong
并最终调用 .values()
或类似的东西来获得由作者行组成的类似 dict 的结果,其中应包含一个条目 "bindings",其中包含每个绑定的列表作者已发表于。延伸目标是让绑定成为一个以分号分隔的列表,即 [{"Author": "Daniel Dennett", "bindings": "paper; hardback; cloth"}, {"Author": "Jemiah Jefferson", "bindings": "paper; zine"}]
到目前为止,我已经能够使用 prefetch_related 和 select_related 获得像 "bindings" 这样的字段附加到查询集,如上所述,但是这个字段不包含在调用 .values()
的结果。这意味着我必须遍历对象,这对我的目的来说花费的时间太长了(有很多对象,请求超时)
创建自定义 Concat
注释,它将模仿 MySQL
GROUP_CONCAT
函数。然后你可以在带注释的 bindings
.
上使用 .values
对于 Django 1.8,您的 Concat
class 可以是这样的:
from django.db import models
class Concat(models.Aggregate):
# supports GROUP_CONCAT(DISTINCT field SEPARATOR str_val)
# do not support order_by
function = 'GROUP_CONCAT'
template = '%(function)s(%(distinct)s%(expressions)s SEPARATOR "%(separator)s")'
def __init__(self, expression, distinct=False, separator=None, **extra):
super(Concat, self).__init__(
expression,
distinct='DISTINCT ' if distinct else '',
separator=separator or '',
output_field=models.CharField(),
**extra)
在 Django 1.7
class Concat(models.Aggregate):
def add_to_query(self, query, alias, col, source, is_summary):
#we send source=CharField to prevent Django from casting string to int
aggregate = SQLConcat(col, source=models.CharField(), is_summary=is_summary, **self.extra)
query.aggregates[alias] = aggregate
#for mysql
class SQLConcat(models.sql.aggregates.Aggregate):
sql_function = 'group_concat'
@property
def sql_template(self):
if self.extra.get('separator'):
return '%(function)s(%(field)s SEPARATOR "%(separator)s")'
else:
return '%(function)s(%(field)s)'
现在你可以做:
Author.objects.annotate(bindings=Concat('book__edition__binding')).values('name', 'bindings')
不幸的是,这不会通过 cycle__id=3
过滤您的 books
,但您可以在注释发生之前应用过滤器。
Author.objects.filter(book__cycle__id=3).annotate(bindings=Concat('book__edition__binding')).values('name', 'bindings')
这将从结果 authors
中去除 book
和 cycle__id=3
。
我想基于涉及预取对象的 django orm 查询创建字典或类似字典的对象。由于涉及的对象数量众多,我想尽可能多地在数据库中执行此操作。
要使用熟悉的示例结构,如果我有一个 Author 和 Books,具有从 Book 到 Author 的常用 ForeignKey,并且我还有一个 Edition,其 ForeignKey 返回 Book,我想做类似的事情
Author.objects.prefetch_related(Prefetch('book_set', queryset=Book.objects.filter(cycle__id=3).select_related('edition__binding'), to_attr="bindings")) # clearly this is wrong
并最终调用 .values()
或类似的东西来获得由作者行组成的类似 dict 的结果,其中应包含一个条目 "bindings",其中包含每个绑定的列表作者已发表于。延伸目标是让绑定成为一个以分号分隔的列表,即 [{"Author": "Daniel Dennett", "bindings": "paper; hardback; cloth"}, {"Author": "Jemiah Jefferson", "bindings": "paper; zine"}]
到目前为止,我已经能够使用 prefetch_related 和 select_related 获得像 "bindings" 这样的字段附加到查询集,如上所述,但是这个字段不包含在调用 .values()
的结果。这意味着我必须遍历对象,这对我的目的来说花费的时间太长了(有很多对象,请求超时)
创建自定义 Concat
注释,它将模仿 MySQL
GROUP_CONCAT
函数。然后你可以在带注释的 bindings
.
.values
对于 Django 1.8,您的 Concat
class 可以是这样的:
from django.db import models
class Concat(models.Aggregate):
# supports GROUP_CONCAT(DISTINCT field SEPARATOR str_val)
# do not support order_by
function = 'GROUP_CONCAT'
template = '%(function)s(%(distinct)s%(expressions)s SEPARATOR "%(separator)s")'
def __init__(self, expression, distinct=False, separator=None, **extra):
super(Concat, self).__init__(
expression,
distinct='DISTINCT ' if distinct else '',
separator=separator or '',
output_field=models.CharField(),
**extra)
在 Django 1.7
class Concat(models.Aggregate):
def add_to_query(self, query, alias, col, source, is_summary):
#we send source=CharField to prevent Django from casting string to int
aggregate = SQLConcat(col, source=models.CharField(), is_summary=is_summary, **self.extra)
query.aggregates[alias] = aggregate
#for mysql
class SQLConcat(models.sql.aggregates.Aggregate):
sql_function = 'group_concat'
@property
def sql_template(self):
if self.extra.get('separator'):
return '%(function)s(%(field)s SEPARATOR "%(separator)s")'
else:
return '%(function)s(%(field)s)'
现在你可以做:
Author.objects.annotate(bindings=Concat('book__edition__binding')).values('name', 'bindings')
不幸的是,这不会通过 cycle__id=3
过滤您的 books
,但您可以在注释发生之前应用过滤器。
Author.objects.filter(book__cycle__id=3).annotate(bindings=Concat('book__edition__binding')).values('name', 'bindings')
这将从结果 authors
中去除 book
和 cycle__id=3
。