【问题标题】:How to update manytomany field in Django?如何更新 Django 中的多对多字段?
【发布时间】:2010-11-14 17:59:27
【问题描述】:

这是一个例子:

如果我有这些课程

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))

但我并不觉得这个解决方案非常优雅和高效。有没有没有循环的解决方案?

【问题讨论】:

  • 如果这是修复一些错别字的一次性交易,为什么不直接运行原始 SQL?
  • 数据库中的数据来自旧的 filemaker 数据库,有很多错别字需要修复。我不想自己修复所有的错别字。我希望我的客户可以在管理界面中做到这一点。

标签: python django


【解决方案1】:

for django >=1.11 documentation:

>>> 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() 方法并一次性添加整个集合。

并参考:How to .update m2m field in django

【讨论】:

    【解决方案2】:

    通过自动生成的表格,您可以进行两步插入和删除,这对可读性很好。

    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
    

    如果你有一个明确定义的 through 表 (authors = models.ManyToManyField(Author, through=BookAuthors) 那么你可以在 BookAuthor 上明确地改变关系。一个鲜为人知的事实是这个 Model 已经存在,它是由 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
    

    【讨论】:

    • 哈,我们几乎同时写了同样的东西!但是我不知道ManyToManyField的“低谷”论点。我会调查的。但是 Ian 使用带有更新的关系表的方式似乎更有效。
    【解决方案3】:

    注意:此代码将删除错误的“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()
    

    我怀疑如果你有一个明确的表连接两个模型(使用“通过”关键字 arg),这会更容易——在这种情况下,你可以直接访问关系表,并且可以做一个.update(id=george_author.id) 就可以了。

    【讨论】:

    • 查看我对 jcd 的评论,您的解决方案基本相同(除了删除,但我不想删除输入错误的作者,所以删除在我的情况下更好)。我认为您的更新解决方案是正确的方法。我接受这个答案。不幸的是,我的情况更复杂,因为我扩展了 ModelAdmin 并且所有内容都必须是通用的,而且我认为我不能直接使用关系表(因为我不确定是否设置了“低谷”argumentmnt)。我是对的?
    • 在一个实例上,您可以看到“通过”参数:book.authors.through = 'ModelName'——不过,我不确定您是否可以通过类本身来获得它。
    • 我检查了一个实例上的“through”参数,如果它没有在 ManyToManyField 中设置,through 是 None。所以目前我将通过添加/删除(或删除)来保留循环的解决方案。也许它不够优雅和高效,但它足以满足我们的需求。谢谢伊恩,jcd
    • .filter(...).delete()的使用实际上删除了作者,而不仅仅是多对多的关系。 jcdyer 有正确答案:致电remove()
    • 我将更新答案以注意 - 在原始问题中,指出“Georfe”作者是一个错误,所以我认为正确的做法是删除该记录,在更新依赖的书籍记录之后。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-06-08
    • 2012-12-01
    • 2022-08-10
    • 2016-08-17
    • 2021-04-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多