【问题标题】:Is save_m2m() required in the Django forms save() method when commit=False?当commit = False时,Django表单save()方法中是否需要save_m2m()?
【发布时间】:2011-10-28 07:54:43
【问题描述】:

文档似乎很坚定,确实是这样......

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

【问题讨论】:

  • 你能把你的模型和表格也贴出来吗?您的模型中的 m2m 在哪里?
  • 我用模型和表格编辑了原始问题。 m2m 是另一个名为 SubscriberList 的模型。

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


【解决方案1】:

其中一个父类正在执行模型对象及其 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 必须在做某事。当然它可能正在处理整个@ 987654333@ 并且从来没有打过ModelFormMixin。)

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

【讨论】:

    【解决方案2】:

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

    带有 commit=False

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

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

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

    如果您想在 save(commit=False) 后使用模型对象的 m2m 数据,则需要 save_m2m()。当你使用 save(commit=False) 时你应该这样做,否则你可能会在 save(commit=False) 之后得到一个与你的数据库(或数据库模型)不正确对应的中间对象。有时可能是正常的(如果你不接触模型m2m部分处理中涉及的数据)。要恢复一致性,请随时调用 save_m2m 调用 save(commit=False)。

    查看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])
    

    【讨论】:

    • 您的回答在使用 save(commit=False) 时解释了 save_m2m 上的文档,但我真的不明白您是如何回答我的问题的。回顾一下,在我上面概述的情况下,m2m 数据已成功保存在我所做的所有测试用例中,但我没有调用 save_m2m()。如果需要 save_m2m() 怎么会这样。
    • @towerofbabel 很明显。请发布测试。
    • @towerofbabel 换句话说,您的私人测试可能不会包括所有可能的情况。
    • 看,我是 Django 和编程新手,我当然没有涵盖所有情况的测试套件。我最初的问题是“它为什么有效”。 * 文档说它不起作用 * 我的数据库显示有效的数据保存,当我添加 1-5 个订阅者列表之间的订阅者(我的测试的限制) * 你说“它可能是”我只是想非常理解这一点简单 m2m 关系的常见情况,并非所有可能的情况。
    【解决方案3】:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-10-24
      • 2010-12-29
      • 2021-11-05
      • 1970-01-01
      • 1970-01-01
      • 2010-11-02
      • 1970-01-01
      • 2014-02-01
      相关资源
      最近更新 更多