Django обратный поиск, чтобы получить последнюю информацию - PullRequest
1 голос
/ 08 июля 2020

У меня есть структура модели, как показано ниже:

ACTIVE_STATUS = ['waiting', 'loading', 'starting', 'running', 'stopping']
INACTIVE_STATUS = ['stopped', 'finished', 'failed', 'lost']
ALL_STATUS = ACTIVE_STATUS + INACTIVE_STATUS


class Task(models.Model):
    name = models.CharField(max_length=20)


class Job(models.Model):
    task = models.ForeignKey(Task, related_name='jobs')
    timestamp = models.DateTimeField(auto_now_add=True)
    status = models.CharField(choices=zip(ALL_STATUS, ALL_STATUS), max_length=20)

Как я могу аннотировать «последнюю временную метку и ее статус» в наборе задач?

Мне удалось получить последнюю временную метку by,

Task.objects.annotate(latest_ts=models.Max(models.F('job__timestamp')))

Итак, как я могу получить соответствующий status?

Update-1

Конечная цель этого запроса - отсортировать Task queryset в

  1. с нулевыми заданиями (скажем, Task.objects.filter(job__isnull=True))
  2. latest_job == 'running'

Update-2

Класс TaskManager, использованный для получения отсортированного набора запросов

class TaskManager(models.Manager):

    def get_queryset(self):
        qs = super().get_queryset()
        latest_job = models.Max(models.F('job__timestamp'))

        latest_status = models.Subquery(
            Job.objects.filter(
                task_id=models.OuterRef('pk')
            ).values('status').order_by('-timestamp')[:1]
        )

        qs_order = models.Case(
            models.When(job__isnull=True, then=models.Value(2)),
            models.When(latest_status='running', then=models.Value(1)),
            default=models.Value(0),
            output_field=models.IntegerField()
        )

        return qs.annotate(latest_job=latest_job, latest_status=latest_status, qs_order=qs_order).order_by('-qs_order')

Ответы [ 2 ]

3 голосов
/ 08 июля 2020

Вы можете работать с выражением Subquery [Django -doc] :

from django.db.models import OuterRef, Subquery

Task.objects.annotate(
    latest_status=Subquery(
        Job.objects.filter(
            task_id=OuterRef('pk')
        ).values('status').order_by('-timestamp')[:1]
    )
)

Исходя из этого, вы, вероятно, также можете фильтровать по последнему статусу:

from django.db.models import Q
from django.db.models import OuterRef, Subquery

Task.objects.annotate(
    latest_status=Subquery(
        Job.objects.filter(
            task_id=OuterRef('pk')
        ).values('status').order_by('-timestamp')[:1]
    )
).filter(
    Q(jobs=None) | Q(latest_status='running')
)

или можем заказать по наличию Job, и т.д. c. с:

from django.db.models import BooleanField, Exists, ExpressionWrapper, Max, Q
from django.db.models import OuterRef, Subquery

Task.objects.annotate(
    latest_status=Subquery(
        Job.objects.filter(
            task_id=OuterRef('pk')
        ).values('status').order_by('-timestamp')[:1]
    ),
    latest_job=Max('jobs__timestamp')
).order_by(
    Exists(Job.objects.filter(task_id=OuterRef('pk'))).asc(),
    ExpressionWrapper(Q(latest_status='running'), output_field=BooleanField()).asc(),
    'pk'
)

Было бы неплохо в конечном итоге отфильтровать по первичному ключу, чтобы упорядочить детерминированность c.

1 голос
/ 09 июля 2020

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

Это менеджер моделей в конце,

class TaskManager(models.Manager):

    def get_queryset(self):
        qs = super().get_queryset()
        latest_job = models.Max(models.F('jobs__timestamp'))

        latest_status = models.Subquery(
            Job.objects.filter(
                task_id=models.OuterRef('pk')
            ).values('status').order_by('-timestamp')[:1]
        )
        job_count = models.Count('jobs')

        qs_order = models.Case(
            models.When(job_count=0, then=models.Value(2)),
            models.When(latest_status='running', then=models.Value(1)),
            default=models.Value(0),
            output_field=models.IntegerField()
        )

        return qs.annotate(job_count=job_count,
                           latest_job=latest_job,
                           latest_status=latest_status,
                           qs_order=qs_order
                           ).order_by('-qs_order', '-pk')

Снимок экрана результата

введите описание изображения здесь

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...