Django - form_valid()与save()的区别

14
在Django表单中,为了保存其他数据,我通常使用form_valid(),但我也可以使用表单类的save()方法。
今天,我重写了save()而不是form_valid(),遇到了关于manytomanyfield的问题。
当我使用save()时,manytomanyfield的值没有被保存,但当我使用form_valid()时,它们开始保存。有人能告诉我原因以及两种方法之间的区别是什么,哪种方法最方便,在什么情况下使用?
这是我重写的save()方法:
class ProductCreateForm(forms.ModelForm):
    sizes = make_ajax_field(ProductCreateModel,'sizes','sizes')
    colours = make_ajax_field(ProductCreateModel,'colours','colours')

    class Meta:
        model = ProductCreateModel

        fields = ('title','category',
                    'regions',)

    def __init__(self,*args,**kwargs):
        self.request = kwargs.pop("request")
        super(ProductCreateForm, self).__init__(*args, **kwargs)

    def save(self):
        product = super(ProductCreateForm, self).save(commit=False)
        user =  self.request.user

        product.location = user.user_location
        product.save()
        return product

当我重写 form_valid() 方法时:

   def get_form_kwargs(self):
       kwargs = super(ProductCreateView,self).get_form_kwargs()
       kwargs.update({'request':self.request})
       return kwargs

   def form_valid(self, form):
       product = form.save(commit=False)
       user =  self.request.user
       form.instance.user = user
       form.instance.location = user.user_location
       form.save()
       return super(ProductCreateView, self).form_valid(form)

sizes, coloursregions是m2m字段,就像我在重写save()时提到的一样,m2m值不会保存,但是当我重写form_valid时,它们开始保存。


请问您能否发布您的覆盖代码?另外,能否解释一下您所说的“它们开始保存”是什么意思?您在数据库中看到它们吗?它们出现在您的表单上吗? - scharette
@scharette 我添加了我的两种方法。 - Pankaj Sharma
2个回答

11

如果您在保存表单时使用commit=False,则必须调用表单的save_m2m方法以保存多对多数据。有关更多信息,请参见文档

如果您决定使用form_valid方法,则需要进行以下更改:

  • 更新form.save()返回的实例并保存它,而不是再次调用form.save()
  • 明确调用form.save_m2m()
  • 返回重定向响应,而不是调用super().form_valid()(这将再次保存表单)

把这些放在一起,你就得到了:

def form_valid(self, form):
    product = form.save(commit=False)
    product.user =  self.request.user
    product.location.location = user.user_location
    product.save()
    form.save_m2m()
    return redirect('/success-url/')

还要补充一点,推荐使用 redirect 应该基于选择,因为通用的 view.form_valid 已经返回了一个 HttpResponseRedirect 到 success_url。 - jackotonye
1
@jackotonye,我已经解释过为什么建议返回重定向响应 - 如果调用super().form_valid(),则表单将再次保存。有替代方法(例如,response = super(ProductCreateView, self).form_valid(form); form.save_m2m; return response),但我更喜欢显式返回。 - Alasdair

1
关于你的manytomany问题,我猜测问题出在它们执行的顺序上... 表单 > 管理员 > 模型,在使用form_valid时,它是在检查链中的其他事项之前执行的第一件事情,而在使用save时,它是最后一个执行的,可能还有其他原因...

最好的方法始终是使用form_valid而不是原始保存

form_valid 首先检查Clean函数是否存在任何本地验证错误或自定义验证,然后才保存您的模型

保存 只是保存它,而不进行验证,然后使用您的表单和您的验证

例子

from django import forms

class ContactForm(forms.Form):
    # Everything as before.
    ...

    def clean(self):
        cleaned_data = super().clean()
        cc_myself = cleaned_data.get("cc_myself")
        subject = cleaned_data.get("subject")

        if cc_myself and subject:
            # Only do something if both fields are valid so far.
            if "help" not in subject:
                raise forms.ValidationError(
                    "Did not send for 'help' in the subject despite "
                    "CC'ing yourself."
                )

来源:https://docs.djangoproject.com/en/2.0/ref/forms/validation/


谢谢你的回答,但请不要说form_valid是第一个。当我在两个方法中都写了打印语句时,save()方法的打印语句先执行,然后才是form_valid。请修改你的回答,不要猜测。 - Pankaj Sharma
你知道在form_valid内部可以调用saves...但是如果将commit=false放置,保存不会被调用,你需要手动调用它...所以是的,form_valid首先被调用,然后在其中保存。所以我不是在猜测。 - Diego Vinícius
就像我说的一样...首先检查是否有效,在其中运行Clean,然后保存。 - Diego Vinícius
好的,你是对的。因为在form_valid保存()方法中执行了super和打印语句,所以打印语句会先被执行。 - Pankaj Sharma

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接