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

发布于 2024-11-30 07:02:23 字数 3234 浏览 0 评论 0原文

文档似乎非常坚定地表明情况确实如此......

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 关系实际上是在处理表单时保存的。)

The docs seem pretty firm that this is indeed the case....

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

And I specifically refer to this section:

Another side effect of using commit=False is seen when your model has a many-to-many relation with another model. If your model has a many-to-many relation and you specify commit=False when you save a form, Django cannot immediately save the form data for the many-to-many relation. This is because it isn't possible to save many-to-many data for an instance until the instance exists in the database.

To work around this problem, every time you save a form using commit=False, Django adds a save_m2m() method to your ModelForm subclass. After you've manually saved the instance produced by the form, you can invoke save_m2m() to save the many-to-many form data.

I am pretty new to django and stumbled upon this information yesterday.

However, I have a view where I do not invoke the save_m2m() method but it does in fact save the m2m data.

Here is my view:

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)

My model:

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

My form:

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

Why does my view work, then? (meaning, the m2m relation of one of the fields in the form is in fact saved when the form is processed.)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

极致的悲 2024-12-07 07:02:23

父类之一正在执行模型对象及其 m2m 关系的完整保存。我不能确定,因为我没有 AuthCreateView 的声明,但命名约定表明它源于“CreateView”。如果是这样,你的视图的继承就像这样,SubscriberCreateView -> AuthCreateView->创建视图-> BaseCreateView-> ModelFormMixin。 ModelFormMixin 有一个 form_valid() 方法,您(可能)使用 super() 调用该方法。

这是 Django 1.4 的完整 方法 :

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< /code> 必须在做某事。当然,它可能正在处理整个 save() 并且根本不会命中 ModelFormMixin。)

为了避免这种混乱,只需不要这样做将您的实例分配给self.object。也许: validate_obj = form.save(commit=False)

One of the parent classes is performing the full save of the model object and its m2m relations. I can't know for sure because I don't have the declaration of AuthCreateView, but the naming convention indicates that it stems from "CreateView". If so, the inheritance of your View goes like this, SubscriberCreateView -> AuthCreateView -> CreateView -> BaseCreateView -> ModelFormMixin. ModelFormMixin has a form_valid() method that you are (probably) calling with super().

Here's the entire method from Django 1.4:

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

So there you have it. However, let me point some potential confusion. @Wogan is astute when pointing out that you don't save your object. The way your code stands, you use your unsaved model instance for validation and then it is discarded because ModelFormMixin re-assigns self.object.

  1. This means self.object might not be what you expect if you access it later on.
  2. You lose the self.object.user information. (Because user is a required field on the Model and you exclude it in the Form, I would expect the save() to fail. So the parent AuthCreateView must be doing something. Of course it might be handling the entire save() and never hitting ModelFormMixin at all.)

To avoid this confusion, simply don't assign your instance to self.object. Perhaps: validate_obj = form.save(commit=False)

对岸观火 2024-12-07 07:02:23

我注意到您实际上并没有保存通过 form.save(commit=False) 调用获得的对象。所以在我看来,您的数据实际上保存在其他地方 - 可能在 AuthCreateView 的 form_valid 方法(或另一个祖先类)中。这可以解释为什么多对多对象被正确保存。

I noticed that you do not actually save the object that you get via your form.save(commit=False) call. So it appears to me that your data is actually being saved somewhere else - probably in AuthCreateView's form_valid method (or another ancestor class). This would explain why the many-to-many objects are being correctly saved.

落在眉间の轻吻 2024-12-07 07:02:23

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

带有 commit=Falsesave 方法不会更改您的数据库:

“如果您使用 commit=False 调用 save(),那么它将返回一个尚未被保存的对象。在这种情况下,您可以对生成的模型实例调用 save(),如果您想在保存对象之前对其进行自定义处理,或者您想使用其中之一,则这很有用。专门的模型保存选项。”

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

如果您想在 save(commit=False) 后使用模型对象的 m2m 数据,则需要 save_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])

It's a simple answer if we know how to use save(commit=False).

save method with commit=False does not change your database:

"If you call save() with commit=False, then it will return an object that hasn't yet been saved to the database. In this case, it's up to you to call save() on the resulting model instance. This is useful if you want to do custom processing on the object before saving it, or if you want to use one of the specialized model saving options."

So in the case of many-to-many relationship it's impossible to save m2m data without saving an object to a database. Usual save( commit=False ) can change an object as you saved part of data (not data that assign to m2m relationship). You really can't store m2m relationship in memory only.

If you want to work with that m2m data of model object after save(commit=False) save_m2m() required. You should do this when you use save(commit=False) otherwise you may get an intermediate object after save(commit=False) that does not correspond to your database (or database model) properly. Sometimes it may be normal (if you don't touch the data which are involved in processing of model's m2m part). To restore the conformity call save_m2m any time you call save(commit=False).

View the save_m2m implementation:

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])
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文