使用 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 字段的主要内容,如果未能解决你的问题,请参考以下文章
在上下文之间传递 objectID 时未触发 to-many 错误
html Django Admin:使用AJAX保存 - 覆盖`admin / change_form.html`模板
Django Admin Cookbook-40如何为Django Admin覆盖保存操作