为啥在 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】:

由于问题似乎是由管理员保留的,我尝试在ModelAdminsave_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:如何覆盖 form.save()?

如何从 Django 模型(覆盖)save() 函数向视图发送警报或消息?

覆盖 Django InlineModelAdmin 上的 save_model

为啥 django ORM 的`save` 方法不返回保存的对象?

为啥 django 的 model.save() 不调用 full_clean()?

Django覆盖保存