Consulta de Django para la contención de subconjuntos de muchos a muchos

¿Hay alguna forma de consultar la contención de un subconjunto o superconjunto con campos de muchos a muchos?

Supongamos que cada persona tiene una lista de aves que quieren ver, y cada pajarera alberga una lista de aves. ¿Cómo puedo hacer una consulta para encontrar, para una instancia dada de la Persona, qué Aviarios tienen cada ave en la lista de la persona? Y de manera similar, para una instancia dada de la Persona, ¿cómo puedo encontrar qué Aviarios tienen solo pájaros en la lista de la persona (pero no necesariamente todos)?

Aquí están mis modelos Django 1.5:

class Bird(models.Model): name = models.CharField(max_length=255, unique=True) class Aviary(models.Model): name = models.CharField(max_length=255, unique=True) birds = models.ManyToManyField(Bird) class Person(models.Model): name = models.CharField(max_length=255, unique=True) birds_to_see = models.ManyToManyField(Bird) 

Sé cómo encontraría los aviarios que tienen al menos uno de los pájaros de una persona, pero no veo cómo lo adaptaría aquí. (Ver, por ejemplo: django queryset para campo de muchos a muchos )

Si hay una consulta que hace lo que quiero, también me interesa saber si / por qué es preferible hacerlo más “manualmente”. Por ejemplo, podría hacer un bucle en los aviarios, extraer la lista de aves de cada aviario y ver si los pájaros de la persona son un subconjunto o superconjunto de la lista de aves del aviario:

     def find_aviaries(self): person_birds = set(self.birds_to_see.all()) found_aviaries = [] for aviary in Aviary.objects.all(): aviary_birds = set(aviary.birds.all()) if person_birds.issubset(aviary_birds): found_aviaries.append(aviary) return found_aviaries 

    Cualquier ayuda es apreciada!

    Existe una buena solución para Django> = 2.0 . Es posible anotar Aviarios por el número de Aves coincidentes y filtrar los Aviarios que coincidan con al menos un Pájaro o un número requerido.

     from django.db.models import Count ... person_birds = set(self.birds_to_see.all()) aviaries = ( Aviary.objects .annotate(bird_match_count=Count('birds', filter=Q(birds__in=person_birds))) .filter(bird_match_count__gt=0) ) 

    Entonces es trivial filtrar un nuevo conjunto de consultas por bird_match_count=len(person_birds) o filtrar el queryset original en Python o clasificarlo por bird_match_count.

    Django <2.0 requeriría hacer referencia al modelo intermediario AviaryBirds y será más detallado.


    Verificado leyendo el SQL:

     >>> print(aviaries.query) SELECT aviary.id, aviary.name, COUNT(CASE WHEN aviary_birds.bird_id IN (1,..) THEN aviary_birds.bird_id ELSE NULL END) AS bird_match_count FROM aviary LEFT OUTER JOIN aviary_birds ON (aviary.id = aviary_birds.aviary_id) GROUP BY aviary.id, aviary.name HAVING COUNT(CASE WHEN (aviary_birds.bird_id IN (1,..)) THEN aviary_birds.bird_id ELSE NULL END) > 0