12

我的 Django 应用程序中有以下模型:

class Book(models.Model):
    name = models.CharField(max_length=100)
    keywords = models.ManyToManyField('Keyword')

class Keyword(models.Model)
    name = models.CharField(max_length=100)

我保存了以下关键字:

science-fiction
fiction
history
science
astronomy

在我的网站上,用户可以按关键字过滤书籍,访问/keyword-slug/. 在我的视图中,keyword_slug 变量被传递给一个函数,该函数按关键字过滤书籍,如下所示:

def get_books_by_keyword(keyword_slug):
    books = Book.objects.all()
    keywords = keyword_slug.split('-')
    for k in keywords:
        books = books.filter(keywords__name__icontains=k)

这在大多数情况下都有效,但是每当我使用包含在关键字表中多次出现的字符串(例如science-fictionfiction)的关键字进行过滤时,我就会得到同一本书在生成的 QuerySet 中多次出现。

我知道我可以添加distinct只返回独特的书籍,但我想知道为什么我一开始就会得到重复,并且真的想了解为什么它会以这种方式工作。由于我只调用filter()成功过滤的查询集,重复的书如何添加到结果中?

4

2 回答 2

10

您的示例中的 2 个模型用 3 个表表示:bookkeyword关系book_keyword表来管理 M2M 字段。

当您keywords__name在过滤器调用中使用时,Django 正在使用 SQL JOIN 来合并所有 3 个表。这允许您通过另一个表中的值过滤第一个表中的对象。

SQL 将是这样的:

SELECT `book`.`id`,
       `book`.`name`
FROM `book`
INNER JOIN `book_keyword` ON (`book`.`id` = `book_keyword`.`book_id`)
INNER JOIN `keyword` ON (`book_keyword`.`keyword_id` = `keyword`.`id`)
WHERE (`keyword`.`name` LIKE %fiction%)

加入后你的数据看起来像

| Book Table          | Relation table                     | Keyword table                |
|---------------------|------------------------------------|------------------------------|
| Book ID | Book name | relation_book_id | relation_key_id | Keyword ID | Keyword name    |
|---------|-----------|------------------|-----------------|------------|-----------------|
| 1       | Book 1    | 1                | 1               | 1          | Science-fiction |
| 1       | Book 1    | 1                | 2               | 2          | Fiction         |
| 2       | Book 2    | 2                | 2               | 2          | Fiction         |

然后,当数据从 DB 加载到 Python 中时,您只会从book表中接收数据。如您所见,书 1 在那里重复

这就是多对多关系和 JOIN 的工作原理

于 2015-05-15T14:46:24.690 回答
3

来自文档的直接引用:https ://docs.djangoproject.com/en/dev/topics/db/queries/#spanning-multi-valued-relationships

连续的 filter() 调用进一步限制了对象集,但对于多值关系,它们适用于链接到主模型的任何对象,不一定是那些由较早的 filter() 调用选择的对象。

在您的情况下,因为keywords是多值关系,您的.filter()调用链仅基于原始模型而不是先前的查询集进行过滤。

于 2013-08-06T05:20:25.360 回答