使用Django 1.5.1。Python 2.7.3。
我想用一个外键字段和一个段塞字段做一个唯一的together约束。所以在我的模型元中,我做了
foreign_key = models.ForeignKey("self", null=True, default=None)
slug = models.SlugField(max_length=40, unique=False)
class Meta:
unique_together = ("foreign_key", "slug")
我甚至检查了Postgres(9.1)中的表描述,并将约束放入数据库表中。
-- something like
"table_name_foreign_key_id_slug_key" UNIQUE CONSTRAINT, btree (foreign_key_id, slug)
但是,我仍然可以将一个foreign_key None/null和重复的字符串保存到数据库表中。
例如,
我可以输入并保存
# model objects with slug="python" three times; all three foreign_key(s)
# are None/null because that is their default value
MO(slug="python").save()
MO(slug="python").save()
MO(slug="python").save()
那么,在使用unique_together之后,为什么我仍然可以输入三个相同值的行呢?
我现在只是猜测,这可能与foreign_key字段的默认值None有关,因为在unique_together之前,当我在slug上使用unique=True时,一切都很好。那么,如果是这样的话,我应该有什么默认值来指示null值,同时保持唯一约束?
在Postgresql中,NULL
不等于任何其他NULL
。因此,您创建的行并不相同(从Postgres的角度来看)。
更新
你有几种方法来处理它:
- 禁止使用外键的
Null
值,并使用一些默认值 - 覆盖模型的
save
方法以检查是否不存在此类行 - 更改SQL标准:)
将clean
方法添加到模型中,这样就可以编辑现有的行。
def clean(self):
queryset = MO.objects.exclude(id=self.id).filter(slug=self.slug)
if self.foreign_key is None:
if queryset.exists():
raise ValidationError("A row already exists with this slug and no key")
else:
if queryset.filter(foreign_key=self.foreign_key).exists():
raise ValidationError("This row already exists")
注意,默认的save
方法不会调用clean
(或full_clean
)。
注意:如果你把这个代码放在save
方法中,更新表单(比如在管理员中)就不起作用了:由于ValidationError
异常,你会有一个回溯错误。
只需在slug
字段上手动创建辅助索引,但仅针对foreign_key_id
:中的NULL值
CREATE INDEX table_name_unique_null_foreign_key
ON table_name (slug) WHERE foreign_key_id is NULL
请注意,Django不支持这一点,因此如果没有自定义表单/模型验证,您将获得纯IntegrityError/500。
创建具有空列的唯一约束的可能重复
正如霍比特所提到的,"在Postgresql中,NULL不等于任何其他NULL。因此,您创建的行不相同(从Postgres的角度来看)。"
解决这一难题的另一种可能方法是在form_valid方法的视图级别添加自定义验证。
视图.py:
def form_valid(self, form):
--OTHER VALIDATION AND FIELD VALUE ASSIGNMENT LOGIC--
if ModelForm.objects.filter(slug=slug,foreign_key=foreign_key:
form.add_error('field',
forms.ValidationError( _("Validation error message that shows up in your form. "),
code='duplicate_row', ))
return self.form_invalid(form)
如果您使用基于类的视图,特别是当您自动为要向用户隐藏的字段赋值时,这种方法会很有用。
优点:
- 您不必在数据库中创建伪默认值
- 您仍然可以使用更新表单(请参阅托夫的回答)
缺点:-这不能防止在数据库级别直接创建重复行。-如果您使用Django的管理后端来创建新的MyModel对象,则需要将相同的验证逻辑添加到您的管理表单中