Django:如何验证m2m关系?

时间:2017-09-22 10:13:27

标签: python django validation

我们说我有一个Basket模型,我想验证只能添加5 Item个:

class Basket(models.Model):
    items = models.ManyToManyField('Item')

    def save(self, *args, **kwargs):
        self.full_clean()
        super(Basket, self).save(*args, **kwargs)

    def clean(self):
        super(Basket, self).clean()
        if self.items.count() > 5:
            raise ValidationError('This basket can\'t have so many items')

但是当尝试保存Basket时会抛出RuntimeError,因为超出了最大递归深度。

错误如下:

ValueError: "<Basket: Basket>" needs to have a value for field "basket" before this many-to-many relationship can be used.

它发生在if self.items.count() > 5:行。

显然,Django的复杂功能只是让您在保存模型时无法验证m2m关系。我怎样才能验证它们呢?

2 个答案:

答案 0 :(得分:1)

您可以从不验证模型的clean方法中的关系。这是因为在清洁时,模型可能尚不存在,就像您的购物篮一样。不存在的东西,也可能没有关系。

您需要根据@bhattravii指出的form data进行验证,或者调用form.save(commit=False)并实现一个名为save_m2m的方法,该方法实现了限制。

要在模型级别强制执行限制,您需要收听m2m_changed信号。请注意,向最终用户提供反馈要困难得多,但它确实可以防止通过不同方式过度填充篮子。

答案 1 :(得分:1)

我一直在Django Developers列表上讨论此问题,实际上已经提出了一种这样做的方法,以一种或另一种形式在Django核心中考虑。该方法尚未经过全面测试或最终确定,但目前的结果令人鼓舞,我在我的站点上成功使用了该方法。

原则上,它依赖于:

  1. 使用PostgreSQL作为数据库引擎(我们相当确定它不会 在Lightdb或MySQL上工作,但热衷于任何人进行测试) 在此处输入代码

  2. 覆盖您的(基于类)视图的post()方法,使其:

    1. 打开一个原子交易
    2. 保存表单
    3. 保存所有表单集(如果有)
    4. 调用Model.clean()或其他类似Model.full_clean()
  3. 然后在模型中,在上面的2.4中调用的方法中,您将看到所有的多对多和一对多关系。您可以验证它们并抛出ValidationError来查看整个事务回滚,并且对数据库没有影响。

这对我来说很棒:

def post(self, request, *args, **kwargs):
    # The self.object atttribute MUST exist and be None in a CreateView. 
    self.object = None
    self.form = self.get_form()     
    self.success_url = reverse_lazy('view', kwargs=self.kwargs)

    if connection.vendor == 'postgresql':
        if self.form.is_valid():
            try:
                with transaction.atomic():
                    self.object = self.form.save()
                    save_related_forms(self) # A separate routine that collects all the formsets in the request and saves them

                    if (hasattr(self.object, 'full_clean') and callable(self.object.full_clean)):
                        self.object.full_clean()
            except (IntegrityError, ValidationError) as e:
                if hasattr(e, 'error_dict') and isinstance(e.error_dict, dict):
                    for field, errors in e.error_dict.items():
                        for error in errors:
                            self.form.add_error(field, error)
                return self.form_invalid(self.form)                    

            return self.form_valid(self.form)
        else:
            return self.form_invalid(self.form)

    else:
        # The standard Djangop post() method
        if self.form.is_valid():
            self.object = self.form.save()
            save_related_forms(self)
            return self.form_valid(self.form)
        else:
            return self.form_invalid(self.form)

“开发者”列表上的对话在这里:

https://groups.google.com/forum/#!topic/django-developers/pQ-8LmFhXFg

如果您想贡献您通过尝试此操作(也许使用其他数据库后端)所获得的任何经验。

上述方法的一个重要警告是,它将保存委托给post()方法,该方法在默认视图中由form_valid()方法完成,因此您还需要覆盖form_valid(),否则需要覆盖post() ),就像上面的一样,您会看到两次保存表格。在UpdateView上只是浪费时间,而在CreateView上却是灾难性的。