如何在Django中更新多对多字段?
这里有个例子:
假设我有这些类:
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 个回答
>>> 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字段
使用自动生成的中间表,你可以进行两步的插入和删除,这样做让代码更容易阅读。
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
注意:这段代码会删除那个不好的'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)
。