Причина, по которой ваш запрос не работает, заключается в том, что вы явно запрашиваете «Книги» с заметками вашего пользователя или вообще без заметок: это исключает книги, в которых заметки есть только у других пользователей.
Я думаю, то, что вы ищете, лучше всего выполнять в качестве аннотации. В django 2.0+ вы можете использовать новое FilteredRelation для выполнения LEFT OUTER JOIN ON (... AND ...)
, но у меня были проблемы с выполнением этого и поддержанием ForeignKey в ORM; вам придется повторно экспортировать нужные поля с дополнительными аннотациями.
q = Book.objects.all().annotate(
usernote=FilteredRelation('booknote', condition=Q(booknote__user=USER_ID)),
usernote_text=F('usernote__text'),
usernote_id=F('usernote'),
)
Результирующий запрос:
SELECT "books_book"."id", "books_book"."name", usernote."text" AS "usernote_text", usernote."id" AS "usernote_id" FROM "books_book" LEFT OUTER JOIN "books_booknote" usernote ON ("books_book"."id" = usernote."book_id" AND (usernote."user_id" = <USER_ID>))
Если вы все еще используете 1.11, вы можете получить тот же результат (но с меньшей производительностью и другими запросами) с объектами предварительной выборки или с аннотацией case-when .
В models.py:
class Book(models.Model):
# SNIP
@property
def usernote(self):
# raises an error if not prefetched
try:
return self._usernote[0] if self._usernote else None
except AttributeError:
raise Exception("Book.usernote must be prefetched with prefetch_related(Book.usernote_prefetch(user)) before use")
@staticmethod
def usernote_prefetch(user):
return Prefetch(
'booknote_set',
queryset=BookNote.objects.filter(user=user)
to_attr='_usernote'
)
По вашему запросу:
q = Book.objects.all().prefetch_related(Book.usernote_prefetch(USER))
Полный tests.py
:
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.test import TestCase
from django.db.models import *
from books.models import Book, BookNote
from django.contrib.auth import get_user_model
class BookTest(TestCase):
def setUp(self):
User = get_user_model()
self.u1 = User.objects.create(username="U1")
self.u2 = User.objects.create(username="U2")
self.b1 = Book.objects.create(name="B1") # Has no notes
self.b2 = Book.objects.create(name="B2") # Has a note for U1 and U2
self.b3 = Book.objects.create(name="B3") # Has a note for just U2
self.n1 = BookNote.objects.create(text="N1", book=self.b2, user=self.u1)
BookNote.objects.create(text="N2", book=self.b2, user=self.u2)
BookNote.objects.create(text="N3", book=self.b1, user=self.u2)
def test_on_multiple(self):
q = Book.objects.all().annotate(
usernote=FilteredRelation('booknote', condition=Q(booknote__user=self.u1)),
usernote_text=F('usernote__text'),
usernote_id=F('usernote'),
).order_by('id')
print(q.query)
self.assertEqual(q.count(), Book.objects.count())
self.assertIsNone(q[0].usernote_text)
self.assertEqual( q[1].usernote_text, self.n1.text)
self.assertIsNone(q[2].usernote_text)
def test_on_multiple_prefetch(self):
@property
def usernote(self):
return self._usernote[0] if self._usernote else None
Book.usernote = usernote
q = Book.objects.all().prefetch_related(Prefetch(
'booknote_set',
queryset=BookNote.objects.filter(user=self.u1),
to_attr='_usernote'
)).order_by('id')
self.assertEqual(q.count(), Book.objects.count())
self.assertIsNone(q[0].usernote)
self.assertEqual( q[1].usernote.text, self.n1.text)
self.assertIsNone(q[2].usernote)