Django独特的约束失败?

时间:2013-07-07 07:55:25

标签: python django postgresql

使用Django 1.5.1。 Python 2.7.3。

我想用外键字段和段塞字段做一个唯一的约束约束。所以在我的模型元中,我做了

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)

但是,我仍然可以在数据库表中保存None / null和重复字符串的foreign_key。

例如,

我可以输入并保存

# 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时,一切正常。那么如果是这种情况,我应该使用哪个默认值来表示空值,还要保持唯一约束?

4 个答案:

答案 0 :(得分:20)

在Postgresql NULL中不等于任何其他NULL。因此,您创建的行不同(从Postgres的角度来看)。

<强>更新

您有几种方法可以解决它:

  • 禁止外键的Null值并使用一些默认值
  • 覆盖模型的save方法以检查是否存在此行
  • 更改SQL标准:)

答案 1 :(得分:1)

向模型添加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")

请注意,默认clean方法不会调用full_clean(或save)。

注意:如果您将此代码放在save方法中,则更新表单(如在管理员中)将不起作用:由于ValidationError异常,您将出现回溯错误。

答案 2 :(得分:0)

只需在ng-change字段上手动创建辅助索引,但仅限于slug中的NULL值:

foreign_key_id

请注意,Django不支持此功能,因此如果没有自定义表单/模型验证,您将获得纯IntegrityError / 500.

Create unique constraint with null columns

可能重复

答案 3 :(得分:0)

正如业余爱好者所说,&#34;在Postgresql中,NULL不等于任何其他NULL。因此,您创建的行不同(从Postgres&#39;透视)。&#34;

解决此挑战的另一种可能方法是在form_valid方法的视图级别添加自定义验证。

在views.py中:

&#13;
&#13;
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)
&#13;
&#13;
&#13;

如果您使用基于类的视图,此方法很有用,尤其是当您自动将值分配给要隐藏用户的字段时。

优点:

  • 您不必在数据库中创建虚拟默认值
  • 您仍然可以使用更新表单(请参阅Toff的回答)

缺点:   - 这不能防止直接在数据库级别创建的重复行。   - 如果您使用Django的管理员后端来创建新的MyModel对象,您需要将相同的验证逻辑添加到您的管理表单中。