使用过滤连接中的最新值注释QuerySet

时间:2017-05-04 06:08:52

标签: django join django-models annotations

如何在单个表达式中从多对一关系的过滤结果中获取最新注释一个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 LoremAdminLorem模型)中创建自定义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_status注释结果:queryset = 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功能呢?

1 个答案:

答案 0 :(得分:4)

您应该可以使用新的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