Как попросить менеджера Django Manytomany согласовать несколько отношений одновременно? - PullRequest
8 голосов
/ 19 сентября 2011

У меня есть эта модель:

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

И я хотел бы сравнить фильм, в котором есть те же актеры, что и у другого.Not one actor in common, but all the actors in common.

Так что я не хочу этого:

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

Я хочу что-то, что бы "Матрица I" соответствовала "Матрице II", потому что они разделяют "Киану Ривз" и«Laurence Fishburne», но не соответствует «Speed», потому что они разделяют «Keanu Reeves», но не «Laurence Fishburne».

1 Ответ

7 голосов
/ 05 января 2012

Менеджер «многие ко многим» не может соответствовать нескольким отношениям одновременно.На уровне базы данных все сводится к выбору и группировке.

Таким образом, естественно, единственный вопрос, на который база данных может ответить, это: перечислите действия, в которых участвуют эти люди, сгруппируйте их по фильмам и покажите толькоэти фильмы имеют то же количество действующих лиц, что и персонажи.

В переводе на ORM говорят, что это выглядит так:

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

Этот запрос на самом деле неплохой:

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

У меня также есть небольшое приложение, которое я использовал для проверки этого, но я не знаю, кому это нужно, и где его разместить.

...