Как я могу отфильтровать набор запросов Django по последней из связанных моделей?

Представьте, что у меня есть следующие 2 модели в надуманном примере:

class User(models.Model):
    name = models.CharField()

class Login(models.Model):
    user = models.ForeignKey(User, related_name='logins')
    success = models.BooleanField()
    datetime = models.DateTimeField()

    class Meta:
        get_latest_by = 'datetime'

Как я могу получить набор запросов пользователей, который содержит только пользователей, чей последний вход не был успешным.

Я знаю, что следующее не работает, но это иллюстрирует то, что я хочу получить:

User.objects.filter(login__latest__success=False)

Я предполагаю, что могу сделать это с объектами Q, и / или Case When, и / или какой-либо другой формой аннотации и фильтрации, но я не могу разобраться в этом.

Всего 3 ответа


Мы можем использовать Subquery здесь:

from django.db.models import OuterRef, Subquery

latest_login = Subquery(Login.objects.filter(
    user=OuterRef('pk')
).order_by('-datetime').values('success')[:1])

User.objects.annotate(
    latest_login=latest_login
).filter(latest_login=False)

Это сгенерирует запрос, который выглядит следующим образом:

SELECT auth_user.*, (
    SELECT U0.success
    FROM login U0
    WHERE U0.user_id = auth_user.id
    ORDER BY U0.datetime DESC
    LIMIT 1
  ) AS latest_login
FROM auth_user
WHERE (
    SELECT U0.success
    FROM login U0
    WHERE U0.user_id = auth_user.id
    ORDER BY U0.datetime
    DESC LIMIT 1
  ) = False

Таким образом, результатом Subquery является success последнего объекта Login , и если это False , мы добавляем соответствующего User в QuerySet .


для проверки bool use success=False и для получения последнего использования latest() ваш фильтр выглядит так:

User.objects.filter(success=False).latest()

Вы можете сначала аннотировать максимальные даты, а затем фильтровать на основе успеха и максимальной даты, используя выражения F :

User.objects.annotate(max_date=Max('logins__datetime'))
    .filter(logins__datetime=F('max_date'), logins__success=False)

Есть идеи?

10000