Why is adding site to an object doesn't seem to work in a save() override in the Django admin?

时光怂恿深爱的人放手 提交于 2019-12-06 13:21:14

问题


I have overrided the save() method of one of my model so it can inherit from the sites object and tags from its parent.

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)

Using ipdb, I can see that self.sites.all() DOES return 4 sites at the end of the method, but strangely, once the request is finish, the same self.sites.all() does not return anything anymore.

I don't use transactions (at least explicitly), and I'm using Django 1.3 and Ubuntu 11.04

EDIT: found out that it works anywhere but in the admin. Doesn't the admin call save? If not, how can I hook to the object creation / update?

EDIT2: tested, and does call save. I have print statements to prove it. But it doesn't add the sites. It's a mystery.


回答1:


In fact, this is a problem about adding programatically many to many relationships when saving a model if you use the Django admin.

Django save m2m relationships in the admin by calling 'clear' to wipe them out, then setting them again. It means that the form destroy any attached data (including your programatically attached) to the object then add the ones you entered in the admin.

It works outside the admin because we don't use the admin form that clear the m2m relationship.

The reason it works for tags in the admin is that the tagging application doesn't use m2m but emulate it by placing a TaggedItem object with a foreign key to a tag and to your model with a generic relation. Plus it's an inline field inclusion.

I tried a lot of things and finally had to look at the Django source code to realize that Django does not process admin forms in the usual way. What it does it:

  1. call ModelAdmin.save_form: it calls form.save with commit = False, returning an unsaved instance and adding a save_m2m method to the form.
  2. call ModelAdmin.save_model that actually calls the instance save method.
  3. call form.save_m2m

Therefor:

  • you can't override your save method since save_m2m is called after and clear the m2m relations.
  • you can't override save_model for the same reason.
  • you can't override save_m2m because it is added by monkey patch to the form model in form.save, erasing you own method definition.

I didn't find a clean solution, but something that works is:

Provide a form for the ModelAdmin class with a method to override save_m2m with your own method:

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

Call this method in a ModelAdmin.model_save override:

class MyModelAdmin(admin.ModelAdmin):

    form = MyModelForm

    def save_model(self, request, obj, form, change):
        obj.save()
        form.set_m2m_method()

This cause the following:

  1. Django calls save_model, replacing its monkey patch by yours
  2. django calls our form.save_m2m that first call its old method that clears relations, then attach the m2m to the object.

I'm completely open to any better way to do this as this is twisted and plain ugly.




回答2:


Since the problem seems to be reserved to admin, I tried to add some logic to do this in the ModelAdmin's save_model method, but it doesn't seem to help at all:

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

Oddly print obj.sites.all() does list all the sites, however, they don't stay saved. Some sort of M2M issue perhaps?



来源:https://stackoverflow.com/questions/5982347/why-is-adding-site-to-an-object-doesnt-seem-to-work-in-a-save-override-in-the

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!