【问题标题】:Django admin.py: save_model() is not called by model.save() in save method of ModelFormDjango admin.py:在 ModelForm 的保存方法中,model.save() 不调用 save_model()
【发布时间】:2012-03-10 11:34:19
【问题描述】:

我的博客文章模型有一个多对多标签字段:

tags = models.ManyToManyField(PostTag)

但是编辑起来很不舒服,我这样修改了我的模型:

def _get_tagging(self): # Returns comma separated list of tags
    tagging = []
    for tag in self.tags.all():
        tagging.append(tag.name)
    return ", ".join(tagging)

def _set_tagging (self, tagging): # Saves tags from comma separated list
    tagging = tagging.split(", ")
    self.tags.clear()
    for tag in tagging:
        if len(tag) < 1:
            continue
        try:
            self.tags.add(PostTag.objects.get(name=tag))
        except ObjectDoesNotExist:
            self.tags.create(name=tag)

tagging = property(_get_tagging, _set_tagging)

然后我修改了我的admin.py:

class BlogAdminForm (forms.ModelForm):
    tagging = forms.CharField(required=False, label="Tags", max_length=200,
                        widget=forms.TextInput(attrs={'class':'vTextField'}))

    class Meta:
        model = BlogPost

    def __init__(self, *args, **kwargs):
        super(BlogAdminForm, self).__init__(*args, **kwargs)

        if kwargs.has_key('instance'):
            instance = kwargs['instance']
            self.initial['tagging'] = instance.tagging

    def save(self, commit=True):
        model = super(BlogAdminForm, self).save(commit=False)
        model.tagging = self.cleaned_data["tagging"]

        if commit:
            model.save()

        return model

这很好用,但仅适用于编辑对象。尝试创建新对象时出现错误。为什么?因为多对多关系可以与尚未在数据库中且没有主键的对象一起使用('BlogPost' 实例需要有一个主键值才能使用多对多关系使用)。我尝试通过以这种方式编辑保存方法来解决它:

def save(self, commit=True):
    model = super(BlogAdminForm, self).save(commit=False)
    try:
        model.tagging = self.cleaned_data["tagging"]
    except ValueError:
        model.save()
        model.tagging = self.cleaned_data["tagging"]

    if commit:
        model.save()

这解决了原来的问题。但是现在model.save() 不会调用我的管理模型的save_model 方法:

class BlogAdmin (admin.ModelAdmin):
    # ... 
    form = BlogAdminForm

    def save_model(self, request, obj, form, change):
        obj.author = request.user
        obj.save()

因此,我收到一个新错误:null value in column "author_id" violates not-null constraint. 我做错了什么?我可以手动调用这个方法吗?

【问题讨论】:

    标签: python django django-forms django-admin


    【解决方案1】:

    您必须在保存实例后保存标签,这意味着在您的save_model 函数中执行此操作。这与您的标签操作代码无关:如果您查看 documentation for the Form.save method 它会说:

    当您的模型与另一个模型具有多对多关系时,会看到使用 commit=False 的另一个副作用。如果您的模型具有多对多关系,并且您在保存表单时指定commit=False,则 Django 无法立即保存多对多关系的表单数据。这是因为在实例存在于数据库中之前,无法为实例保存多对多数据。

    为了解决这个问题,每次您使用commit=False 保存表单时,Django 都会向您的ModelForm 子类添加一个save_m2m() 方法。手动保存表单生成的实例后,可以调用save_m2m()保存多对多表单数据。

    有几种方法可以解决您的问题。您可以编写一个 widget 在标签 ID 列表和逗号分隔的标签名称之间来回转换,然后在您的 save_model 方法中调用 form.save_m2m()。但是这种方法的缺点是,在从小部件中解码值时,您必须创建新标签,即使没有保存表单(可能是因为表单其他地方的验证错误)。

    所以我认为在这种情况下更好的方法是将您自己的save_tags 方法添加到表单中:

    class BlogAdminForm(forms.ModelForm):
        tagging = forms.CharField(required=False, label="Tags", max_length=200,
                                  widget=forms.TextInput(attrs={'class':'vTextField'}))
    
        class Meta:
            model = Post
    
        def __init__(self, *args, **kwargs):
            super(BlogAdminForm, self).__init__(*args, **kwargs)
            if 'instance' in kwargs:
                tags = (t.name for t in kwargs['instance'].tags.all())
                self.initial['tagging'] = ', '.join(tags)
    
        def save_tags(self, obj):
            obj.tags = (Tag.objects.get_or_create(name = tag.strip())[0]
                        for tag in self.cleaned_data['tagging'].split(','))
    
    class BlogPostAdmin(admin.ModelAdmin):
        form = BlogAdminForm
    
        def save_model(self, request, obj, form, change):
            obj.author = request.user
            obj.save()
            form.save_tags(obj)
    

    请注意,我将标记操作代码移到了表单中:我认为它属于这里,而不是在模型中,因为它都是关于用户输入的。我还做了一些风格上的改进:

    • 'instance' in kwargskwargs.has_key('instance') 简单。

    • 生成器表达式(t.name for t in kwargs['instance'].tags.all()) 比在for 循环中构建列表更简单。

    • get_or_create method 是一个方便的快捷方式,无需try: ... except ObjectDoesNotExist: ...

    • 您可以直接分配给ManyToMany 字段,而不是先调用clear,然后再调用add(此外,在标签不变的情况下效率更高)。

    【讨论】:

    • 非常感谢您提供这么好的答案!还要感谢风格改进。我会在我的工作中考虑到它们。
    猜你喜欢
    • 2010-10-23
    • 2015-10-08
    • 2013-08-02
    • 1970-01-01
    • 2015-09-18
    • 2014-01-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多