在 GenericForeignKey 中强制执行模型限制

Enforcing model limit in GenericForeignKey

我有一个使用 GenericForeignKey 对象的简单模型。我想将允许的 content_objects 限制为一组特定的静态模型。比方说,我只希望它分别接受来自 app_aapp_bModelAModelB

I 运行 across this question 这基本上描述了我想要实现的目标。我实施了建议的解决方案,最终得到了一个看起来像这样的模型:

class TaggedItem(models.Model):
    tag = models.SlugField()
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    limit = models.Q(app_label='app_a', model='modela') \
            | models.Q(app_label='app_b', model='modelb')
    content_type = models.ForeignKey(ContentType, limit_choices_to=limit)
    content_object = GenericForeignKey('content_type', 'object_id')

这实际上似乎在使用 /admin/ 面板添加对象时正常工作,content_type 从我的可用选项中提取。但是,当我编写单元测试或使用 shell 时,它似乎并没有强制执行此操作。

例如,我希望:

TaggedItem.objects.create(content_object=(ModelZ()))

引发异常。然而,事实并非如此。是否有任何 django-istic 方法强制 content_objects 成为 limit_choices_to 中给出的模型的实例?

在 Django 中,默认情况下 choices=... 在模型层中 强制执行。因此,如果您 .save().create(..) 模型对象,则列中的值可能不是相应 choices 的成员。然而,ModelForm 在对象上执行 full_clean(..),从而强制执行此操作。

Django 模型 但是有一种方法可以强制执行此操作:如果您调用 .full_clean(..) 函数,如果值不是有效选择,它将引发错误。所以我们可以用以下方法修补单个模型:

class TaggedItem(models.Model):
    tag = models.SlugField()
    object_id = models.PositiveIntegerField()
    limit = models.Q(app_label='app_a', model='modela') \
            | models.Q(app_label='app_b', model='modelb')
    content_type = models.ForeignKey(ContentType, limit_choices_to=limit)
    content_object = GenericForeignKey('content_type', 'object_id')

    def save(self, *args, **kwargs):
        <b>self.full_clean()</b>
        super().save(*args, **kwargs)

这将因此在您每次 .save(..) 函数时检查选项。 This question 有几个答案提供了在特定模型或所有模型上执行此操作的替代方法。

但请记住,Django ORM 仍然允许绕过它。例如 TaggedItem.objects.update(content_type=1425) 仍然可以成功(因为它被直接映射到 SQL 查询),没有办法以通用方式强制执行此操作(跨所有数据库系统)。 Django ORM 允许 - 部分出于性能原因,部分出于向后兼容性 - 进行可以使数据库进入 "invalid state" 的查询(本身对数据库无效,但对 Django 模型层无效)。