当commit = False时,Django中是否需要save_m2m()表单save()方法?

时间:2011-08-16 18:30:33

标签: django django-models django-forms django-views

文档看起来确实如此......

https://docs.djangoproject.com/en/dev/topics/forms/modelforms/#the-save-method

我特别提到这一部分:

  

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

     

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

我对django很新,昨天偶然发现了这些信息。

但是,我有一个视图,我没有调用save_m2m()方法,但它实际上保存了m2m数据。

以下是我的观点:

class SubscriberCreateView(AuthCreateView):
    model = Subscriber
    template_name = "forms/app.html"
    form_class = SubscriberForm
    success_url = "/app/subscribers/"

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

    def form_valid(self, form):
        self.object = form.save(commit=False)
        self.object.user = self.request.user
        try:
            self.object.full_clean()
        except ValidationError:
            form._errors["email"] = ErrorList([u"This subscriber email is already in your account."])
            return super(SubscriberCreateView, self).form_invalid(form)
        return super(SubscriberCreateView, self).form_valid(form)

我的模特:

class Subscriber(models.Model):

    STATUS_CHOICES = (
        (1, ('Subscribed')),
        (2, ('Unsubscribed')),
        (3, ('Marked as Spam')),
        (4, ('Bounced')),
        (5, ('Blocked')),
        (6, ('Disabled')),
    )

    user = models.ForeignKey(User)
    status = models.IntegerField(('status'), choices=STATUS_CHOICES, default=1)
    email = models.EmailField()
    subscriber_list = models.ManyToManyField('SubscriberList')
    first_name = models.CharField(max_length=70, blank=True)
    last_name = models.CharField(max_length=70, blank=True)
    phone = models.CharField(max_length=20, blank=True)
    facebook_id = models.CharField(max_length=40, blank=True)
    twitter_id = models.CharField(max_length=40, blank=True)
    address1 = models.CharField(max_length=100, blank=True)
    address2 = models.CharField(max_length=100, blank=True)
    postcode = models.CharField(max_length=10, blank=True)
    city = models.CharField(max_length=30, blank=True)
    country = models.CharField(max_length=30, blank=True)
    date_joined = models.DateTimeField(auto_now_add=True)
    date_updated = models.DateTimeField(auto_now=True)

    class Meta:
        unique_together = (
            ('user', 'email',),
        )

    def __unicode__(self):
        return self.email

我的表格:

class SubscriberForm(ModelForm):
    def __init__(self, user, *args, **kwargs):
        super (SubscriberForm, self).__init__(*args, **kwargs)
        self.fields['subscriber_list'].queryset = SubscriberList.objects.filter(user=user)

    class Meta:
        model = Subscriber
        exclude = ('user', 'facebook_id', 'twitter_id')

为什么我的观点有用呢? (意思是,表单中某个字段的m2m关系实际上是在处理表单时保存的。)

3 个答案:

答案 0 :(得分:8)

其中一个父类正在执行模型对象及其m2m关系的完全保存。我不能确定,因为我没有AuthCreateView的声明,但是命名约定表明它来自" CreateView"。如果是这样,View的继承就像这样SubscriberCreateView -> AuthCreateView -> CreateView -> BaseCreateView -> ModelFormMixin。 ModelFormMixin有一个 form_valid() 方法(您可能)使用 super() 进行调用。

这里是来自Django 1.4的整个method

def form_valid(self, form):
    self.object = form.save()
    return super(ModelFormMixin, self).form_valid(form)

所以你有它。但是,让我指出一些潜在的混乱。 @Wogan在指出你没有保存你的物体时很精明。代码的方式,您使用未保存的模型实例进行验证,然后被丢弃,因为ModelFormMixin 重新分配 self.object

  1. 这意味着如果您稍后访问它,self.object可能不是您所期望的。
  2. 您丢失了self.object.user个信息。 (因为user是模型上的必填字段,并且您在表单中将其排除,我希望save()失败。所以父AuthCreateView必须做某事。当然它可能正在处理整个save(),而根本没有点击ModelFormMixin。)
  3. 为避免这种混淆,请不要将您的实例分配给self.object。也许:validate_obj = form.save(commit=False)

答案 1 :(得分:0)

如果我们知道如何使用 save(commit = False),这是一个简单的答案。

使用 commit = False

保存方法不会更改您的数据库:

“如果用commit = False调用save(),那么它将返回一个尚未保存到数据库的对象。在这种情况下,你可以在生成的模型实例上调用save() 。如果要在保存对象之前对对象进行自定义处理,或者如果要使用其中一种专用模型保存选项,这将非常有用。“

因此,在多对多关系的情况下,如果不将对象保存到数据库,则无法保存m2m数据。通常保存(commit = False)可以在保存部分数据时更改对象(而不是分配给m2m关系的数据)。你真的不能将m2m关系存储在内存中。

如果要在 save(commit = False) save_m2m()之后使用模型对象的m2m数据。您应该在使用 save(commit = False)时执行此操作,否则您可能会在save(commit = False)之后获得与您的数据库(或数据库模型)不正确对应的中间对象。有时它可能是正常的(如果您不触摸处理模型的m2m部分所涉及的数据)。要在每次调用save(commit = False)时恢复符合性调用save_m2m。

查看save_m2m实施:

def save_m2m():
    cleaned_data = form.cleaned_data
    for f in opts.many_to_many:
        if fields and f.name not in fields:
            continue
        if f.name in cleaned_data:
            f.save_form_data(instance, cleaned_data[f.name])

答案 2 :(得分:0)

我注意到您实际上并未保存通过form.save(commit=False)电话获得的对象。因此在我看来,您的数据实际上正在其他地方保存 - 可能在AuthCreateView的{​​{1}}方法(或另一个祖先类)中。这可以解释为什么正确保存多对多对象的原因。