How can I, in a single expression, get the latest value from the filtered results of a many-to-one relationship to annotate a Django QuerySet?
Given this toy schema:
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)
For the purpose of making a custom QuerySet in a Django LoremAdmin
(for the Lorem
model), I need to:
foo_status
and bar_status
each from separate JOIN clauses to a LoremStatusEvent
model: lorem.loremstatusevent_set__status
.Ipsum
value: foo_status_events=LoremStatusEvent.filter(ipsum='foo')
.foo_status=LoremStatusEvent.objects.filter(ipsum='foo').latest('created').status
.foo_status
and bar_status
: queryset = queryset.annotate(foo_status=???).annotate(bar_status=???)
.If I invent some functions to do all this – get_field
, latest_by
, filter_by
, loremstatus_set_of_every_corresponding_lorem
– I could write the admin something like this:
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
What actual functions should replace each of these placeholder names?
loremstatus_set_of_every_corresponding_lorem
, the equivalent of Lorem.loremstatusevent_set
.filter_by
, to filter a join at the right side.latest_by
, to aggregate the result set, ordered by a field, to get the single latest instance. I don't see any such documented aggregation function.get_field
, to reference a field from the resulting instance.Remember that all this needs to be done on a queryset of Lorem
instances, accessing the related instances via Lorem.loremstatusevent_set
; I don't have a LoremStatusEvent
instance at that point, so I can't directly use attributes of LoremStatusEvent
.
So, what actual Django functionality should be in the above placeholders?
You should be able to do this with the new Subquery functionality, which is also available as a 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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With