在 GenericForeignKey 中强制执行模型限制
Enforcing model limit in GenericForeignKey
我有一个使用 GenericForeignKey 对象的简单模型。我想将允许的 content_objects
限制为一组特定的静态模型。比方说,我只希望它分别接受来自 app_a
和 app_b
的 ModelA
和 ModelB
。
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 模型层无效)。
我有一个使用 GenericForeignKey 对象的简单模型。我想将允许的 content_objects
限制为一组特定的静态模型。比方说,我只希望它分别接受来自 app_a
和 app_b
的 ModelA
和 ModelB
。
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 模型层无效)。