为啥在 Django 管理员的 save() 覆盖中将站点添加到对象似乎不起作用?
Posted
技术标签:
【中文标题】为啥在 Django 管理员的 save() 覆盖中将站点添加到对象似乎不起作用?【英文标题】:Why is adding site to an object doesn't seem to work in a save() override in the Django admin?为什么在 Django 管理员的 save() 覆盖中将站点添加到对象似乎不起作用? 【发布时间】:2011-05-12 18:04:22 【问题描述】:我已经覆盖了我的一个模型的 save()
方法,因此它可以从 sites object 继承并从其父级继承标签。
def save(self, *args, **kwargs):
ret = models.Model.save(self, *args, **kwargs)
if self.id:
for site in self.parent.sites.all():
self.sites.add(site.id)
for tag in self.parent.tags_set.all():
Tag.objects.add_tag(self, tag)
使用ipdb
,我可以看到self.sites.all()
在方法结束时确实返回了4 个站点,但奇怪的是,一旦请求完成,同样的self.sites.all()
不再返回任何内容。
我不使用事务(至少明确地),我使用的是 Django 1.3 和 Ubuntu 11.04
编辑:发现它可以在除管理员之外的任何地方工作。管理员调用不保存吗?如果没有,我该如何挂钩对象的创建/更新?
EDIT2:经过测试,确实调用了保存。我有打印声明来证明这一点。但它不会添加站点。这是个谜。
【问题讨论】:
【参考方案1】:事实上,如果您使用 Django 管理员,在保存模型时以编程方式添加多对多关系是一个问题。
Django 通过调用 'clear' 将 m2m 关系删除,然后再次设置它们来保存管理中的 m2m 关系。这意味着表单会销毁任何附加到对象的数据(包括您以编程方式附加的数据),然后添加您在管理员中输入的数据。
它在管理员之外工作,因为我们不使用清除 m2m 关系的管理员表单。
它适用于管理标签的原因是标签应用程序不使用 m2m,而是通过将带有外键的 TaggedItem 对象放置到标签和具有通用关系的模型来模拟它。另外,它是一个内联字段包含。
我尝试了很多东西,最后不得不查看 Django 源代码,才意识到 Django 不会以通常的方式处理管理表单。它的作用:
-
调用
ModelAdmin.save_form
:它使用commit = False
调用form.save
,返回一个未保存的实例并向表单添加save_m2m
方法。
调用实际调用实例save
方法的ModelAdmin.save_model
。
致电form.save_m2m
为此:
您无法覆盖您的save
方法,因为在调用 save_m2m
并清除 m2m 关系之后。
出于同样的原因,您不能覆盖 save_model
。
您不能覆盖save_m2m
,因为它是通过猴子补丁添加到form.save
中的表单模型中,从而删除了您自己的方法定义。
我没有找到干净的解决方案,但可行的是:
为 ModelAdmin 类提供一个表单,并使用您自己的方法覆盖 save_m2m
:
class MyModelForm(forms.ModelForm):
class Meta:
model = MyModel
def set_m2m_method(self, update_tags_and_sites=True):
alias = self.save_m2m
def new_save_m2m(): # this is your new method
alias() # we need to call the original method as well
self.instance.add_niche_sites_and_tags()
self.save_m2m = new_save_m2m # we erase Django erasing :-)
在 ModelAdmin.model_save
覆盖中调用此方法:
class MyModelAdmin(admin.ModelAdmin):
form = MyModelForm
def save_model(self, request, obj, form, change):
obj.save()
form.set_m2m_method()
这会导致以下情况:
-
Django 调用
save_model
,用你的替换它的猴子补丁
django 调用我们的 form.save_m2m
,它首先调用其清除关系的旧方法,然后将 m2m 附加到对象。
我完全愿意接受任何更好的方法来做到这一点,因为这既扭曲又丑陋。
【讨论】:
【参考方案2】:由于问题似乎是由管理员保留的,我尝试在ModelAdmin
的save_model
方法中添加一些逻辑来执行此操作,但它似乎根本没有帮助:
class SomeModelAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
obj.save()
for site in Site.objects.all():
obj.sites.add(site.id)
print obj.sites.all()
奇怪的是print obj.sites.all()
确实列出了所有网站,但是它们并没有保存下来。可能是某种 M2M 问题?
【讨论】:
无论哪种方式都可以,但是,这也是我调用超级方法的方式。 Super 不会改变任何东西,因为站点关系在对象本身之外。另外,我不能将自定义逻辑放在保存方法的顶部,因为我需要在创建对象时引用保存的对象才能在其上附加任何内容。 是的,我不认为它会有所帮助(除非你有一些非常疯狂的事情发生),但我仍然认为这应该是首选方法(但我想这是一个品味问题)。如果您为您的模型共享更多代码(您如何获得parent
?)它可能会揭示一些东西。刚刚使用Site.objects.all()
进行了测试,以自动为save
中的实例添加所有站点,并且效果很好。所以也许你的parent
有什么奇怪的地方?
发现它与 django 管理员有关。更新了问题。
奇特,我至少会尝试验证。以上是关于为啥在 Django 管理员的 save() 覆盖中将站点添加到对象似乎不起作用?的主要内容,如果未能解决你的问题,请参考以下文章
如何从 Django 模型(覆盖)save() 函数向视图发送警报或消息?
覆盖 Django InlineModelAdmin 上的 save_model
为啥 django ORM 的`save` 方法不返回保存的对象?