我有这两个模型:
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验证似乎是文档中未触及的主题。我错过了什么?
答案 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输入你的数据库中的东西的人。如果你想要这样的解决方案类型,你应该直接进入你的数据库并编写一些魔术触发器脚本。但请记住,您的数据库实际上是数据。您的后端是具有业务逻辑的后端。所以你不应该真正尝试将业务规则强加到数据库级别。通过在创建/更新发生的位置验证您的数据,将规则保留在后端。