6

Given these Django models:

class Job(models.Model):
    pass

class Task(models.Model):
    job = models.ForeignKey('Job', related_name='tasks')
    status = models.CharField(choices=TaskStatus.CHOICES, max_length=30)
    dependencies = models.ManyToManyField("self", related_name="dependents", symmetrical=False)

I want to query all Tasks with status PENDING and ALL dependencies having status COMPLETED for a single Job.

I wrote the following query, but it returns tasks that have at least one dependency with status completed, which is obviously not want I'm after.

tasks_that_can_be_executed = Task.objects.filter(
    job__pk=job_id,
    status=TaskStatus.PENDING,
    dependencies__status=TaskStatus.COMPLETED
)

Any suggestions?

3
  • Where is status Column? Commented Feb 21, 2017 at 10:31
  • @PiyushS.Wanare added it. Sorry, I must have missed it when stripping out unrelated code for this example. Commented Feb 21, 2017 at 10:40
  • Why all are using Q I don't know it mostly use when we need to work with conditions? and filter always work with AND Commented Feb 21, 2017 at 10:51

4 Answers 4

7
from django.db.models import IntegerField, Case, When, Count, Q

Task.objects.filter(
    job=job,
    status=TaskStatus.PENDING,
).annotate(
    not_completed_dependencies_count=Count(
        Case(When(~Q(dependencies__status=TaskStatus.COMPLETED), then=1),
             output_field=IntegerField(),
        )
    )
).filter(
    not_completed_dependencies_count__gt=0
)
Sign up to request clarification or add additional context in comments.

Comments

1

You could use exclude() instead or in combination with filter(), as the exclude() would just throw away any result with a status != COMPLETED

tasks_that_can_be_executed = Task.objects.filter(
    job__pk=job_id,
    status=TaskStatus.PENDING,
).exclude(
    dependencies__status=TaskStatus.PENDING
)

Or if there are multiple status values you like to exclude:

tasks_that_can_be_executed = Task.objects.filter(
    job__pk=job_id,
    status=TaskStatus.PENDING,
).exclude(
    dependencies__status__in=[s for s in TaskStatus.CHOICES if s != TaskStatus.COMPLETED]
)

Comments

0

With Q from django.db.models you can make some more complex queries. If I'm understanding you right, you want to

from django.db.models import Q

tasks_that_can_be_executed = Task.objects.filter(job__pk=job_id).filter(
        Q(status=TaskStatus.PENDING) | Q(dependencies__status=TaskStatus.COMPLETED)
)

Not sure I understood properly what you wanted as result, but in the current situation you will get all tasks from the given job_id, where the status is PENDING together with all objects where dependencies__status is COMPLETED

3 Comments

Almost! I only want tasks where ALL dependencies are completed.
Sorry, I missed the ManyToMany. I'm still searching too, but I can't find somehting satisfying yet to keep it short and in one query.
Neither can I. I managed to implement a workaround by filtering out afterwards... but I would rather push it into the query :)
-1

You need to import Q and then do something like:

from django.db.models import Q

tasks_that_can_be_executed = Task.objects.filter(
    Q(job__pk=job_id) & Q(status=TaskStatus.PENDING) & Q(dependencies__status=TaskStatus.COMPLETED)
)

1 Comment

It seems you just rewrote author's filtering with Q objects. It's not like he wants.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.