Django:包括values()dict中的预取对象

时间:2016-06-22 19:21:58

标签: django orm prefetch

我想基于涉及预取对象的django orm查询创建一个dict或类似dict的对象。由于涉及的对象数量,我希望在数据库中尽可能多地执行此操作。

要使用熟悉的示例结构,如果我有一个作者和书籍,通常使用Book to Author的ForeignKey,并且我还有一个带有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连接到查询集的“绑定”字段,如上所述,但此字段不包含在调用.values()的结果中。这意味着我必须遍历对象,这对我的目的来说需要太长时间(有很多对象,并且请求超时)

1 个答案:

答案 0 :(得分:1)

创建自定义Concat注释,该注释将模仿MySQL GROUP_CONCAT功能。然后,您可以在带注释的.values上使用bindings

对于Django 1.8,您的Concat类可以是这样的:

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')

遗憾的是,这不会按books过滤cycle__id=3,但您可以在注释发生之前应用过滤器。

Author.objects.filter(book__cycle__id=3).annotate(bindings=Concat('book__edition__binding')).values('name', 'bindings')

这将从结果authors中删除book cycle__id=3。{/ p>