如何在Django中更新多对多字段?

31 投票
3 回答
68604 浏览
提问于 2025-04-15 13:13

这里有个例子:

假设我有这些类:

class Author(models.Model):
    name = models.CharField(max_length=45)

class Book(models.Model):
    name = models.CharField(max_length=45)
    authors = models.ManyToManyField(Author)

在数据库里,我有一个作者叫“George”,还有一个叫“Georfe”。后者是个错误。所以我想把每本书中所有作者为“Georfe”的地方都替换成作者“George”。

在SQL中,这个操作非常简单。如果“George”的ID是3,而“Georfe”的ID是7,关系表的名字是“author_book”:

UPDATE author_book SET id=3 WHERE id=7;

那么在Django ORM中能做到吗?

我找到了一种方法:我遍历所有与这个拼写错误的作者相关的书籍,然后这样做:

book.authors.add(Author.objects.get(id=3))
book.authors.remove(Author.objects.get(id=7))

不过我觉得这个方法不太优雅,也不够高效。有没有不需要循环的解决方案呢?

3 个回答

12

关于django >=1.11的文档:

>>> b = Blog.objects.get(id=1)
>>> e = Entry.objects.get(id=234)
>>> b.entry_set.add(e) # Associates Entry e with Blog b.
>>> new_list = [obj1, obj2, obj3]
>>> e.related_set.set(new_list)

这个方法有一个叫做 clear 的参数,用来控制操作的方式。如果这个参数是 False(默认值),那么在新集合中缺少的元素会被用 remove() 方法移除,只会添加新的元素。如果 clear=True,那么就会调用 clear() 方法,这样会一次性添加整个集合。

参考链接: 如何在django中更新m2m字段

23

使用自动生成的中间表,你可以进行两步的插入和删除,这样做让代码更容易阅读。

george = Author.objects.get(name='George')
georfe = Author.objects.get(name='Georfe')

book.authors.add(george)
book.authors.remove(georfe)
assert george in book.authors

如果你有一个明确的中间表(比如 authors = models.ManyToManyField(Author, through=BookAuthors)),那么你可以在 BookAuthor 上明确地改变关系。一个鲜为人知的事实是,这个模型是自动由 Django 生成的。通常情况下,只有当你有额外的数据需要存储时,才应该创建一个明确的中间模型(比如在一本多位作者的书中,记录某位作者写的章节)。

# This line is only needed without a custom through model.
BookAuthor = Book.authors.through
book_author = BookAuthor.objects.get(author=georfe, book=great_american_novel)
book_author.author = george
book_author.save()
assert george in book.authors
28

注意:这段代码会删除那个不好的'georfe'作者,同时把书籍更新为指向正确的作者。如果你不想这样做,可以使用.remove(),正如@jcdyer的回答中提到的。

你能这样做吗?

george_author = Author.objects.get(name="George")
for book in Book.objects.filter(authors__name="Georfe"):
    book.authors.add(george_author.id)
    book.authors.filter(name="Georfe").delete()

我觉得如果你有一个明确的表来连接这两个模型(使用“through”这个关键词参数),这样会更简单。在这种情况下,你可以直接访问关系表,然后可以在上面直接执行.update(id=george_author.id)

撰写回答