Django唯一的together约束失败



使用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对象,则需要将相同的验证逻辑添加到您的管理表单中

最新更新