It seems natural to expect that the queries
Book.objects.filter(criteria) and Bool.objects.exlude(same criteria) give complementary results: each book should appear in one and only one of these two querysets. However, I am faced with a situation in which this is not the case. My question is whether this should be considered normal or whether it is a bug to report. Here’s a minimal example:
class Genre(models.Model):
name = models.CharField(max_length=100)
class Book(models.Model):
title = models.CharField(max_length=100)
genre = models.ForeignKey(Genre, on_delete=models.CASCADE)
genres = models.ManyToManyField(Genre, related_name='books')
Imagine that genres is the family of genres that properly represents the book and genre is the main genre. We want to make sure that book.genre is in book.genres.all(). We get the list of books that satisfy this condition by
Book.objects.filter(genre__in = F('genres'))
There, no problem. We hope that the following query will provide the list of books that do not satisfy it:
Book.objects.exclude(genre__in = F('genres'))
But bang: this is not at all what is expected. Let’s introduce some items in the database :
And let’s see the result:
>>> Book.objects.filter(genre__in = F('genres'))
<QuerySet [<Book: Book1>, <Book: Book2>, <Book: Book4>]>
>>> Book.objects.exclude(genre__in = F('genres'))
<QuerySet [<Book: Book1>, <Book: Book3>, <Book: Book3>, <Book: Book4>, <Book: Book4>]>
We understand what is happening by examining the SQL query produced:
SELECT "myapp_book"."id", "myapp_book"."genre_id"
FROM "myapp_book"
LEFT OUTER JOIN
"myapp_book_genres"
ON ("myapp_book"."id" = "myapp_book_genres"."book_id")
WHERE NOT ("myapp_book"."genre_id" IN ("myapp_book_genres"."genre_id"))
which is not, it seems to me, what we expect (instead of “myapp_book_genres”.“genre_id”, at the end, we should have some subquery catching “book.objects.genres.all()” ).
So : is it a bug or not ?