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 中去除 bookcycle__id=3