使用来自筛选后的联接的最新值注释QuerySet

big*_*ose 5 django annotations join django-models

如何在单个表达式中从多对一关系的过滤结果中获取最新值以注释 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)
Run Code Online (Sandbox Code Playgroud)

为了在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_status和。bar_statusqueryset = queryset.annotate(foo_status=???).annotate(bar_status=???)

如果我发明了一些功能来做到这一切- ,get_field,,latest_by -我可以写管理员是这样的:filter_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
Run Code Online (Sandbox Code Playgroud)

哪些实际功能应替换这些占位符名称中的每个?

  • loremstatus_set_of_every_corresponding_lorem,相当于Lorem.loremstatusevent_set
  • filter_by,以过滤右侧的联接。
  • latest_by,以汇总结果集(按字段排序)以获取单个最新实例。我没有看到任何这样记录的聚合函数
  • get_field,以引用结果实例中的字段。

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

那么,上述占位符应包含什么实际的Django功能?

Mat*_*kel 7

您应该能够使用新的Subquery功能来做到这一点,该功能也可以作为向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
Run Code Online (Sandbox Code Playgroud)