This is the relevant code:
class Book(models.Model):
name = models.CharField(max_length=50)
class BookNote(models.Model):
text = models.CharField(max_length=50)
book = models.ForeignKey(Book)
user = models.ForeignKey(settings.AUTH_USER_MODEL)
class Meta:
unique_together = [('book', 'user'), ]
Now, for a specific user in the website:
I want to query all the books (all the table). And,
For each book object, If the user has a BookNote for the book - get it, otherwise booknote should be null.
This is how I would do that with SQL (works):
SELECT book.name, booknote.text
FROM book
LEFT OUTER JOIN booknote ON
(book.id = booknote.book_id AND booknote.user_id = {user_id_here})
This is what I've tried, does not work:
qs = Book.objects.filter(Q(booknote__user_id=user_id_here) | Q(booknote__isnull=True))
I examine qs.query
and I see why - Django uses WHERE clause to filter by user_id, so I don't get all the books.
How can I do the same query with django ORM? Without raw sql?
The reason your query doesn't work is you're expicitly asking for either Books with your user's notes or no notes at all: this excludes books where only other users have notes.
I think what you're looking for is best performed as an annotation. Under django 2.0+, you can use the new FilteredRelation to perform a
LEFT OUTER JOIN ON (... AND ...)
, but I had trouble doing it and maintaining the ForeignKey in the ORM; you'll have to re-export the fields you need with additional annotations.Resulting query:
If you're using 1.11 still, you can get the same result (but less performance and different queries) with Prefetch objects
or a case-when annotation.In models.py:
By your query:
Full
tests.py
: