How to do many-to-many Django query to find book with 2 given authors?

Not intuitive at first but the answer is right in front of us.

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

If you want an exact match, you could potentially further filter this result by those items that only have exactly 2 authors.

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

If you want exact matches dynamically, how about something like this?:

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.

New questions are pointing to this one as a duplicate, so here is an updated answer (for one specific backend).

If the backend is Postgres, the SQL you want is (assuming the M2M table is called bookauthor):

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

You can get Django to generate nearly this SQL.

First, pip install django-sql-utils. Then create this Array class:

from django.db.models import Func

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

And now you can write your ORM queryset:

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))