【问题标题】:Annotate a QuerySet with the latest value from a filtered join使用过滤连接中的最新值注释 QuerySet
【发布时间】:2017-10-02 04:10:28
【问题描述】:

如何在单个表达式中从 多对一 关系的 过滤 结果中获取 最新注释 Django QuerySet?

鉴于这个玩具架构:

from django.db import models

class Lorem(models.Model):
    """ Lorem ipsum, dolor sit amet. """

class LoremStatusEvent(models.Model):
    """ A status event change on a given `ipsum` for a `lorem`. """

    created = models.DateTimeField(auto_now_add=True)
    lorem = models.ForeignKey(Lorem)
    ipsum = models.CharField(max_length=200)
    status = models.CharField(max_length=10)

为了在 Django LoremAdmin(对于 Lorem 模型)中制作自定义 QuerySet,我需要:

  • foo_statusbar_status 分别从单独的 JOIN 子句派生到 LoremStatusEvent 模型:lorem.loremstatusevent_set__status
  • 过滤每个连接以仅包含那些对应Ipsum 值的事件:foo_status_events=LoremStatusEvent.filter(ipsum='foo')
  • 将状态事件集聚合为每个对应连接的最新状态事件:foo_status=LoremStatusEvent.objects.filter(ipsum='foo').latest('created').status
  • 用几个额外的值注释结果,foo_statusbar_statusqueryset = queryset.annotate(foo_status=???).annotate(bar_status=???)

如果我发明一些函数来完成这一切——get_fieldlatest_byfilter_byloremstatus_set_of_every_corresponding_lorem——我可以这样写管理员:

from django.contrib import admin

class LoremAdmin(admin.ModelAdmin):
    """ Django admin for `Lorem` model. """

    class Meta:
        model = Lorem

    def get_queryset(request):
        """ Get the `QuerySet` of all instances available to this admin. """
        queryset = super().get_queryset(request)
        queryset.annotate(
            foo_status=(
                get_field('status')(
                    latest_by('created')(
                        filter_by(ipsum='foo')(
                            loremstatusevent_set_of_every_corresponding_lorem)))),
            bar_status=(
                get_field('status')(
                    latest_by('created')(
                        filter_by(ipsum='bar')(
                            loremstatusevent_set_of_every_corresponding_lorem)))),
        )
        return queryset

哪些实际函数应该替换这些占位符名称?

  • loremstatus_set_of_every_corresponding_lorem,相当于Lorem.loremstatusevent_set
  • filter_by,用于过滤右侧的连接。
  • latest_by,聚合结果集,按字段排序,获取单个最新实例。我没有看到任何这样的documented aggregation function
  • get_field,从结果实例中引用一个字段。

请记住,所有这些都需要在 Lorem 实例的查询集上完成,通过 Lorem.loremstatusevent_set 访问相关实例;我当时没有LoremStatusEvent 实例,所以我不能直接使用LoremStatusEvent 的属性。

那么,上述占位符中应该包含哪些实际的 Django 功能?

【问题讨论】:

标签: django join django-models annotations


【解决方案1】:

您应该可以使用新的Subquery 功能来做到这一点,该功能也可以作为backport to 1.8+ 使用。

try:
    from django.db.models.expressions import Subquery, OuterRef
except ImportError:
    from django_subquery.expressions import Subquery, OuterRef

class LoremAdmin(admin.ModelAdmin):
    # …

    def get_queryset(request):
        queryset = super().get_queryset(request)
        status_event_per_lorem = LoremStatusEvent.objects.filter(
            lorem=OuterRef('pk'))
        latest_status_event_per_lorem = (
            status_event_per_lorem.order_by(
                'pk', '-created').distinct('pk'))
        latest_status_event_for_ipsum_foo = (
            latest_status_event_per_lorem.filter(ipsum='foo'))
        latest_status_event_for_ipsum_bar = (
            latest_status_event_per_lorem.filter(ipsum='bar'))
        queryset = queryset.annotate(
            foo_status=Subquery(
                latest_status_event_for_ipsum_foo.values('status')[:1]), 
            bar_status=Subquery(
                latest_status_event_for_ipsum_bar.values('status')[:1]), 
        )

        return queryset

【讨论】:

  • 这会引发错误ValueError: This queryset contains a reference to an outer query and may only be used in a subquery. 我认为这是因为filter(…) 结果随后用于latest(…)
  • 我找到了一种不会导致该错误的方法;我正在编辑此答案,使其按预期工作。
猜你喜欢
  • 2016-09-07
  • 2016-01-07
  • 1970-01-01
  • 2023-03-30
  • 1970-01-01
  • 2011-05-31
  • 2011-04-18
  • 2018-06-24
相关资源
最近更新 更多