Django中的M2M关系验证

时间:2015-04-13 21:58:07

标签: django validation django-models

我有这两个模型:

class Test(models.Model):
    problems = models.ManyToManyField('Problem')
    ...

class Problem(models.Model):
    type = models.CharField(max_length=3, choices=SOME_CHOICES)
    ...

现在,在将Problem添加到Test时,我需要限制Test中特定类型问题的数量。例如。 Test只能包含3个Problem的A类型,依此类推。

验证这一点的唯一方法似乎是在m2m_changed表上使用Test.problems.through信号。但是,要进行验证,我需要访问当前添加的Problem和现有的Problems - 这似乎不太可能。

这样做的正确方法是什么? M2M验证似乎是文档中未触及的主题。我错过了什么?

2 个答案:

答案 0 :(得分:0)

你不能覆盖M2M的保存,我害怕,但你可以实现你想要的。

使用m2m_changed信号,其中操作是pre_add '实例' kwarg将成为问题所在的测试模型。
' pk_id' kwarg将成为问题的主要关键(1个或更多) 验证逻辑将是这样的:

p_type = Problem.objects.get(id=kwargs['pk_id']).type
type_count = kwargs['instance'].problems.filter(type=p_type).count()

if p_type == 'A' and type_count == 3:
  raise Exception("cannot have more than 3 Problems of type A")

[抱歉,手头没有django验证查询]

答案 1 :(得分:0)

您必须注册m2m_changed信号函数,如下所示:

def my_callback(sender, instance, action, reverse, model, pk_set, **kwargs)

如果您阅读文档,您将看到sender是触发更改的对象模型,model是将要更改的对象模型。 pk_set将为您提供将成为模型新参考的pkeys。因此,在您的测试模型中,您必须执行以下操作:

@receiver(m2m_changed)
def my_callback(sender, instance, action, reverse, model, pk_set, **kwargs):
    if action == "pre_add":
        problem_types = [x.type for x in model.objects.filter(id__in=pk_set)]
        if problem_types.count("A") > some_number:
            raise SomeException

请注意,如果您从Django管理站点输入字段,则不会捕获该级别的异常。为了能够为django管理数据输入提供用户友好的错误,您必须将自己的表单注册为管理员表单。在您的情况下,您需要执行以下操作:

class ProblemTypeValidatorForm(ModelForm):
    def clean(self):
        super(ProblemTypeValidatorForm, self).clean()
        problem_types = [x.type for x in self.cleaned_data.get("problems") if x]
        if problem_types.count("A") > some_number:
            raise ValidationError("Cannot have more than {0} problems of type {1}"
                                  .format(len(problem_types), "A")

然后在admin.py

@admin.register(Test)
class TestAdmin(admin.ModelAdmin):
    form = ProblemTypeValidatorForm

现在请记住,这是两个不同级别的实现。没有人会保护你免受手动操作的人的伤害:

one_test_object.problems.add(*Problem.objects.all())
one_test_object.save()

个人意见:

所以请记住上面的内容,我建议你选择ModelForm& ModelAdmin方法,如果您为CRUD操作提供API,也可以在那里进行验证。没有什么可以保护你免受通过django shell输入你的数据库中的东西的人。如果你想要这样的解决方案类型,你应该直接进入你的数据库并编写一些魔术触发器脚本。但请记住,您的数据库实际上是数据。您的后端是具有业务逻辑的后端。所以你不应该真正尝试将业务规则强加到数据库级别。通过在创建/更新发生的位置验证您的数据,将规则保留在后端。