我有这两个模型:
class Test(models.Model):
problems = models.ManyToManyField('Problem')
...
class Problem(models.Model):
type = models.CharField(max_length=3, choices=SOME_CHOICES)
...
现在,在将问题
添加到测试
时,我需要限制测试
中特定类型问题的数量。例如。一个测试
只能包含3个A类型的问题
,依此类推。
验证这一点的唯一方法似乎是在 Test.problems.through
表上使用 m2m_changed
信号。但是,要进行验证,我需要访问当前添加的 Problem
以及现有的 Problems
- 这似乎在某种程度上是不可能的。
做这样的事情的正确方法是什么? M2M 验证似乎是文档中未涉及的主题。我错过了什么?
最佳答案
您说得对,您必须注册 m2m_changed 信号函数,如下所示:
def my_callback(sender, instance, action, reverse, model, pk_set, **kwargs)
如果您阅读文档,您会发现 sender
是触发更改的对象模型,而 model
是将要更改的对象模型。 pk_set
将为您提供 pkey,这些 pkey 将成为您模型的新引用。因此,在您的测试模型中,您必须执行以下操作:
@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 在你的数据库中输入内容的影响。如果您想要这样的解决方案类型,您应该直接进入数据库并编写某种神奇的触发脚本。但请记住,您的数据库实际上是数据。您的后端是具有业务逻辑的后端。因此,您不应该真正尝试将业务规则强加到数据库级别。通过在创建/更新发生的位置验证数据,将规则保留在后端。
关于django - Django 中的 M2M 关系验证,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29615800/