我的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变量将传递给我的视图中的函数,该函数按关键字过滤Books,如下所示:
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-fiction
和fiction
),我就会得到相同的结果本书在生成的QuerySet中出现不止一次。
我知道我可以添加distinct
只返回独特的书籍,但我想知道为什么我要开始重复,并且真的想要理解为什么这样做会起作用。由于我只是在成功过滤的QuerySet上调用filter()
,因此如何将重复的书籍添加到结果中?
答案 0 :(得分:4)
直接引用文档:https://docs.djangoproject.com/en/dev/topics/db/queries/#spanning-multi-valued-relationships
连续的filter()调用进一步限制了 一组对象,但对于多值关系,它们适用于任何对象 链接到主模型的对象,不一定是那些对象 由早期的filter()调用选择。
在您的情况下,由于keywords
是一个多值关系,因此.filter()
链仅基于原始模型而不是之前的查询集调用过滤器。
答案 1 :(得分:4)
示例中的2个模型用3个表格表示:book
,keyword
和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的工作方式