3

I have an event object, there are other objects besides Notes that have a generic relation to Event, and which don't have an active field. Now I want to be able to write a query which excludes all Events where the active field of the Notes is False. So I tried doing the following.

queryset = Event.objects.all()
filters = (
        Q(content_type__model='notes') &
        Q(note__active=False)
    )
queryset = queryset.exclude(filters)

This didn't work because it's running the query separately, and when it tries to execute for items for which there are no content_object or which are not of type Notes, it fails and gives the following error:

AttributeError 'GenericRelation' object has no attribute 'field'.

class Event(models.Model):
    content_type = models.ForeignKey(ContentType, null=True, blank=True)
    object_id = models.PositiveIntegerField(null=True, blank=True)
    content_object = GenericForeignKey('content_type', 'object_id')

class Notes(models.Model):
    active = models.BooleanField(default=True)
    event_log = GenericRelation(
        'Event',
        related_query_name='note'
    )
Matthias Fischer
  • 553
  • 4
  • 18
rak1n
  • 671
  • 1
  • 8
  • 17

2 Answers2

3

The usual way to do that using the Django ORM is with a subquery:

Event.objects.exclude(
    content_type=note_content_type,
    object_id__in=Notes.objects.filter(active=False),
)

The sad thing is that this can be expressed in SQL with a join instead of a subquery, but the ORM doesn't make that easy.

In some cases PostgreSQL's query planner can optimize the subquery into a hash semi join, so don't dismiss the subquery in preference to raw SQL without benchmarking on the data.

wim
  • 338,267
  • 99
  • 616
  • 750
  • won't this only give me all events with content_type of note, but I also want to include other content types. But if the content type is note then I want to check if active is false. – rak1n Jan 19 '17 at 16:30
  • Thanks this worked, i justed used exclude instead of filter. – rak1n Jan 19 '17 at 16:35
  • Oh, yeah it should have been exclude instead of filter. Updated. – wim Jan 19 '17 at 17:41
1

Another approach is to use Conditional Expression, e.g.

from django.db import models
queryset = Event.objects.all()
queryset = queryset.annotate(
    is_inactive_note=models.Case(
        models.When(
            content_type__model='notes',
            note__active=False,
            then=models.Value(True),
        ),
        default=models.Value(False),
        output_field=models.BooleanField(),
    )
).filter(is_inactive_note=False)
Todor
  • 15,307
  • 5
  • 55
  • 62