Как сделать много-много запросов Django, чтобы найти книгу с двумя авторами?

У меня есть запрос, который требует отфильтровать ровно 2 авторов с идентификатором

Теоретически

Book.objects.filter(author__id=1, author__id=2). 

что невозможно.

Как я могу решить эту проблему?

Cheers, Микки

Ответы

Ответ 1

Сначала не интуитивно, но ответ прямо перед нами.

Book.objects.filter(author__id=1).filter(author__id=2)

Если вы хотите получить точное соответствие, вы можете еще больше фильтровать этот результат теми элементами, которые имеют ровно 2 автора.

Book.objects.annotate(count=Count('author')).filter(author__id=1)\
                .filter(author__id=13).filter(count=2)

Если вы хотите, чтобы точные соответствия были динамически, как насчет чего-то вроде этого?

def get_exact_match(model_class, m2m_field, ids):
    query = model_class.objects.annotate(count=Count(m2m_field))\
                .filter(count=len(ids))
    for _id in ids:
        query = query.filter(**{m2m_field: _id})
    return query

matches = get_exact_match(MyModel, 'my_m2m_field', [1, 2, 3, 4])

# matches is still an unevaluated queryset, so you could run more filters
# without hitting the database.

Ответ 2

Новые вопросы указывают на этот вопрос как на дубликат, так что вот обновленный ответ (для одного конкретного бэкэнда).

Если бэкэнд является Postgres, то вам нужен SQL (при условии, что таблица M2M называется bookauthor):

SELECT *
FROM book
WHERE
    (SELECT ARRAY_AGG(bookauthor.author_id)
     FROM bookauthor
     WHERE bookauthor.book_id = book.id) = Array[1, 2];

Вы можете заставить Django генерировать почти этот SQL.

Во-первых, pip install django-sql-utils. Затем создайте этот класс Array:

from django.db.models import Func

class Array(Func):
    function = 'ARRAY'
    template = '%(function)s[%(expressions)s]'

И теперь вы можете написать свой набор запросов ORM:

from sql_util.utils import SubqueryAggregate
from django.contrib.postgres.aggregates import ArrayAgg

books = Book.objects.annotate(
            author_ids=SubqueryAggregate('author__id', Aggregate=ArrayAgg)
        ).filter(author_ids=Array(1, 2))

Ответ 3

Объекты

Q помогут вам. Docs

Book.objects.filter(Q(author__id=1) & Q(author__id=2))

Ответ 4

Вы можете использовать запрос "IN". Django Docs

Book.objects.filter(author__id__in=[1,2])