使用 Django admin 时未保存 ManyToMany 字段

Posted

技术标签:

【中文标题】使用 Django admin 时未保存 ManyToMany 字段【英文标题】:ManyToMany field not saved when using Django admin 【发布时间】:2011-09-06 05:06:36 【问题描述】:

我遇到了一个奇怪的问题,希望这里的人能够解释一下。

我正在重写模型的 save() 方法,以便在运行 super() 后向 ManyToMany 字段添加一些值。我的问题是,当我在 Django admin 中保存时,这些值似乎被添加到关系中,但随后又为空。

但是,如果我从 manage.py shell 执行此操作,则它可以正常工作。

我在那里放了两个打印语句,无论我是通过 Django admin 还是通过 shell 运行,它们都会产生完全相同的输出。

class Store(models.Model):
    holidays = models.ManyToManyField(StoreHoliday, blank=True)
    copy_holidays_from = models.ForeignKey('Store', blank=True, null=True)

    def save(self):
        print '====  BEFORE SAVE:', self.holidays.all()
        super(Store, self).save()
        self.copy_holidays()
        print '====  AFTER SAVE:', self.holidays.all()

    def copy_holidays(self):
        if self.pk and self.copy_holidays_from:
            self.holidays.clear()
            for h in self.copy_holidays_from.holidays.all():
                self.holidays.add( h )

这是print 语句的输出:

====  BEFORE SAVE: []
====  AFTER SAVE: [<StoreHoliday: 10 Mar 2010, Chuck Norris birthday (Closed)>]

有人对可能导致此问题的原因有任何建议吗?

编辑: 在通过管理界面保存时,Django 似乎放弃了 save() 中对 m2m 关系的所有手动更改。这与它处理表单的方式有关吗?

【问题讨论】:

我不知道你的问题出在哪里,但是self.holidays = self.copy_holidays_from.holidays.all() 看起来比清除和迭代要好得多。 感谢您的提示,我不知道这是可能的。你可以在下面看到我做错了什么。 【参考方案1】:

所以事实证明上述方法不是正确的实现方法。代码属于 StoreAdmin,通过覆盖 model_save()。

我就是这样解决的:

class StoreAdmin(admin.ModelAdmin):
    def save_model(self, request, obj, form, change):
        if obj.copy_holidays_from:
            form.cleaned_data['holidays'] = obj.copy_holidays_from.holidays.all()

        super(StoreAdmin, self).save_model(request, obj, form, change)

【讨论】:

【参考方案2】:

我今天可能遇到了同样的行为,是的,你认为它与 django 处理数据的方式有关是正确的。

django 管理员对 ManyToMany 字段的更改与更改实际对象是分开的。 (请记住,m2m 保存在不同的数据库表中)。

在我的例子中,如果我没有在管理站点的 ManyToMany 字段中选择任何内容,这将转化为对 ManyToMany 关系的 clear() 操作。你在 save() 方法中所做的一切都会被这个 clear 立即删除。我在 post_save 信号处理程序中所做的事情也是如此。

解决方案(对我而言)是将 ManyToMany 字段分隔为内联字段,这样在修改对象时它不会自动保存为空。

【讨论】:

您好,Jyrsa,我遇到了与 Django Admin 类似的问题。我们有一个 m2m 字段,有时在保存页面的其余部分时会清除该字段。我不知道 Django 单独保存它。您能否将我链接到有关如何处理 m2m 的更多信息?您能否更具体地说明在什么情况下清除保存方法的操作?谢谢!【参考方案3】:

在 django 2,1,4 我的解决方案是使用 save_related()

def save_related(self, request, form, formsets, change):
    super().save_related(request, form, formsets, change)
    form.instance.permissions.add(request.user)

【讨论】:

【参考方案4】:

对我来说,管理员只保存了许多字段的最后一个选择实例(最后一个“假期”选择)。所以我不得不重写 save_model 方法,例如:

@admin.register(Store)
class StoreAdmin(admin.ModelAdmin):

    def save_model(self, request, obj, form, change):
        form.cleaned_data['holidays'] = StoreHoliday.objects.filter(pk__in=dict(request.POST).get('holidays'))
        super(StoreAdmin, self).save_model(request, obj, form, change)

我花了很多时间在上面,其他解决方案都不起作用,所以我希望它会有所帮助。

【讨论】:

【参考方案5】:

更新 m2m 的解决方案之一,以及更新您的模型之一。

Django 1.11 and higher

您在更新期间可以观察到的行为,当您对 m2m 记录所做的更改未保存时,即使您在模型或信号中的保存方法中进行了更改,仅因为 m2m 表单重写所有记录而发生主对象更新后。

这就是为什么,一步一步:

    主对象已更新。

    您的代码(在保存方法或信号中)进行了更改(您可以 看看他们,只需在ModelAdmin中放一个断点):

 def save_related(self, request, form, formsets, change):
     breakpoint()
     form.save_m2m()
     for formset in formsets:
         self.save_formset(request, form, formset, change=change)
    form.save_m2m() 获取放置在页面上的所有 m2m 值(粗略地说)并通过相关管理器替换所有 m2m 记录。这就是您在交易结束时看不到更改的原因。

有一个解决方案:通过 m2m 进行更改 transaction.on_commit。 transaction.on_commit 将进行您的更改 提交事务时在 form.save_m2m() 之后。

不幸的是,此解决方案的缺点 - 您对 m2m 的更改将在单独的事务中执行。

【讨论】:

以上是关于使用 Django admin 时未保存 ManyToMany 字段的主要内容,如果未能解决你的问题,请参考以下文章

如何在 django admin 上添加只读内联

在上下文之间传递 objectID 时未触发 to-many 错误

html Django Admin:使用AJAX保存 - 覆盖`admin / change_form.html`模板

Django Admin Cookbook-40如何为Django Admin覆盖保存操作

Django-Admin TabularInline 在保存前修改内联项属性

在 Django Admin 中使用 DateTimeField 保存模型实例会丢失微秒分辨率