2015-02-06 3 views
0

Моей модели:Джанго фильтр модель со многими ко многим полями

class Book(models.Model): 
    title = models.CharField(max_length=254) 
    subtitle = models.CharField(max_length=254, null=True, blank=True) 
    subjects = models.ManyToManyField(Subject) 

class Subject(models.Model): 
    name = models.CharField(max_length=255) 
    description = models.CharField(max_length=254, null=True, blank=True) 

Здесь книги субъектов в многих ко-многим с Subject моделью.

Как я могу получить все книги, имеющие похожие темы. Например, все книги, имеющие предметы id [2,3,6]

+1

Это предположим, но работает 'Book.objects.filter (subject__in = [2, 3, 6])'? – fletom

+0

Это показывает все книги, имеющие предметы id 2 OR книги с предметами id 3 ИЛИ книги с предметами id 6 – wrufesh

+2

Затем что-то вроде 'Book.objects.filter (subject__id = 2) .filter (subject__id = 3) .filter (subject__id = 6) '? Вы должны добавить их в цикл for. – fletom

ответ

0

Решение с объектами Q не будет работать.

Некоторые исходные данные:

>>> from subjects.models import Subject, Book 
>>> s1 = Subject.objects.create(name='subject_1', description='description 1') 
>>> s2 = Subject.objects.create(name='subject_2', description='description 2') 
>>> s3 = Subject.objects.create(name='subject_3', description='description 3') 
>>> s4 = Subject.objects.create(name='subject_4', description='description 4') 
>>> b1 = Book.objects.create(title='one_subject_book', subtitle='one subject') 
>>> b1.subjects = [s1] 
>>> b2 = Book.objects.create(title='three_subject_book', subtitle='three subjects') 
>>> b2.subjects = [s2,s3,s4] 
>>> b3 = Book.objects.create(title='four_subject_book', subtitle='four subjects') 
>>> b3.subjects = [s1,s2,s3,s4] 

Давайте сначала проверить наивный подход с subjects__in=[s1,s2,s3]. Мы ищем только одну книгу, потому что только b3 содержит все предметы.

>>> Book.objects.filter(subjects__in=[s1,s2,s3]) 
DEBUG (0.001) 
    SELECT `subjects_book`.`id`, `subjects_book`.`title`, `subjects_book`.`subtitle` 
    FROM `subjects_book` 
    INNER JOIN `subjects_book_subjects` ON (`subjects_book`.`id` = `subjects_book_subjects`.`book_id`) 
    WHERE `subjects_book_subjects`.`subject_id` IN (1, 2, 3) 
    LIMIT 21; args=(1, 2, 3) 
[<Book: Book object>, <Book: Book object>, <Book: Book object>, <Book: Book object>, <Book: Book object>, <Book: Book object>] 

Мы просто ищем books, содержащие s1 ИЛИ s2 ИЛИ s3. Вот почему мы получаем такой результат. У нас есть повторяющиеся результаты, потому что мы не использовали .distinct()

Теперь попробуй с объектами Q.

Сразу хочу отметить, что:

Book.objects.filter(Q(subjects=s1)&Q(subjects=s2)&Q(subjects=s3)) 

так же, как:

Book.objects.filter(reduce(lambda q1,q2: q1&q2, [Q(subjects=s) for s in [s1,s2,s3]])) 

Просто более динамичной версии, где и можно легко изменить только список с subjects.

>>> Book.objects.filter(reduce(lambda q1,q2: q1&q2, [Q(subjects=s) for s in [s1,s2,s3]])) 
DEBUG (0.260) 
    SELECT `subjects_book`.`id`, `subjects_book`.`title`, `subjects_book`.`subtitle` 
    FROM `subjects_book` 
    INNER JOIN `subjects_book_subjects` ON (`subjects_book`.`id` = `subjects_book_subjects`.`book_id`) 
    WHERE (
     `subjects_book_subjects`.`subject_id` = 1 
     AND `subjects_book_subjects`.`subject_id` = 2 
     AND `subjects_book_subjects`.`subject_id` = 3 
    ) LIMIT 21; args=(1, 2, 3) 
[] 

Мы получили пустой набор результатов. Это было бы нормально, если вы проверили запрос. Мы не можем иметь строку, содержащую три разных значения subject_id одновременно. Причина, по которой мы получаем такой запрос, заключается в том, что мы применяем все фильтры Q сразу в одном выражении .filter. Узнайте больше об этом на странице Spanning multi-valued relationships.

Чтобы получить правильный результат от MySQL, мы должны ПРИСОЕДИНЯЙТЕСЬ К subjects_book_subjects таблице на каждый subject, на который мы хотим отфильтровать. В ORM перспективе это может быть достигнуто с помощью серии последовательных .filter заявлений:

Book.objects.filter(subjects=s1).filter(subjects=s2).filter(subjects=s3) 

В более моды образом это выглядит так:

books_queryset = reduce(lambda books,s: books.filter(subjects=s),[s1,s2,s3], Book.objects.all()) 

как пример:

>>> reduce(lambda books,s: books.filter(subjects=s),[s1,s2,s3], Book.objects.all()) 
DEBUG (0.001) 
    SELECT `subjects_book`.`id`, `subjects_book`.`title`, `subjects_book`.`subtitle` 
    FROM `subjects_book` 
    INNER JOIN `subjects_book_subjects` ON (`subjects_book`.`id` = `subjects_book_subjects`.`book_id`) 
    INNER JOIN `subjects_book_subjects` T4 ON (`subjects_book`.`id` = T4.`book_id`) 
    INNER JOIN `subjects_book_subjects` T6 ON (`subjects_book`.`id` = T6.`book_id`) 
    WHERE (
     `subjects_book_subjects`.`subject_id` = 1 
     AND T4.`subject_id` = 2 
     AND T6.`subject_id` = 3 
    ) LIMIT 21; args=(1, 2, 3) 
[<Book: Book object>] 
Смежные вопросы