2011-09-19 17 views
8

Tengo este modelo:¿Cómo pedirle al gerente manytomany de Django que coincida con varias relaciones a la vez?

class Movie(models.Model): 
    # I use taggit for tag management 
    tags = taggit.managers.TaggableManager() 

class Person(models.Model): 
    # manytomany with a intermediary model 
    movies = models.ManyToManyField(Movie, through='Activity') 

class Activity(models.Model): 
    movie = models.ForeignKey(Movie) 
    person = models.ForeignKey(Person) 
    name = models.CharField(max_length=30, default='actor') 

Y me gustaría para que coincida con una película que tiene los mismos actores de otro. Not one actor in common, but all the actors in common.

Así que no quiero esto:

# actors is a shortcut property 
one_actor_in_common = Movie.object.filter(activities__name='actor', 
              team_members__in=self.movie.actors) 

Quiero algo que haría "Matrix I" partido "Matrix II", ya que comparten 'de Keanu Reeves y Laurence Fishburne', pero no coinciden "Velocidad" porque comparten 'Keanu Reeves' pero no 'Laurence Fishburne'.

Respuesta

7

El administrador many to manytomany no puede hacer coincidir varias relaciones a la vez. Abajo en el nivel de la base de datos todo se reduce a seleccionar y agrupar.

Naturalmente, la única pregunta que la base de datos puede responder es la siguiente: enumerar las actividades de actuación que involucran a estas personas, agruparlas por película y mostrar solo las películas que tengan el mismo número de actividades de actuación que las personas.

Traducido al ORM hablan que tiene este aspecto:

actors = Person.objects.filter(name__in=('Keanu Reaves', 'Laurence Fishburne')) 

qs = Movie.objects.filter(activity__name='actor', 
          activity__person__in=actors) 
qs = qs.annotate(common_actors=Count('activity')) 
all_actors_in_common = qs.filter(common_actors=actors.count()) 

La consulta que esto produce es en realidad no está mal:

SELECT "cmdb_movie"."id", "cmdb_movie"."title", COUNT("cmdb_activity"."id") AS "common_actors" 
    FROM "cmdb_movie" 
    LEFT OUTER JOIN "cmdb_activity" ON ("cmdb_movie"."id" = "cmdb_activity"."movie_id") 
    WHERE ("cmdb_activity"."person_id" IN (SELECT U0."id" FROM "cmdb_person" U0 WHERE U0."name" IN ('Keanu Reaves', 'Laurence Fishburne')) 
      AND "cmdb_activity"."name" = 'actor') 
    GROUP BY "cmdb_movie"."id", "cmdb_movie"."title", "cmdb_movie"."id", "cmdb_movie"."title" 
    HAVING COUNT("cmdb_activity"."id") = 2 

también tengo una pequeña aplicación que he utilizado para probar esto, pero No sé si alguien lo necesita ni dónde alojarlo.

+1

Me gusta esta solución. Hacer coincidir el conteo antes de las personas es pensar inteligentemente. – Gevious

+1

¡Gracias por tu respuesta! –

Cuestiones relacionadas