简体   繁体   English

Django独特的约束失败?

[英]Django unique together constraint failure?

Using Django 1.5.1. 使用Django 1.5.1。 Python 2.7.3. Python 2.7.3。

I wanted to do a unique together constraint with a foreign key field and a slug field. 我想用外键字段和段塞字段做一个唯一的约束约束。 So in my model meta, I did 所以在我的模型元中,我做到了

foreign_key = models.ForeignKey("self", null=True, default=None)
slug = models.SlugField(max_length=40, unique=False)

class Meta:
    unique_together = ("foreign_key", "slug")

I even checked the table description in Postgres (9.1) and the constraint was put into the database table. 我甚至检查了Postgres(9.1)中的表描述,并将约束放入数据库表中。

-- something like
"table_name_foreign_key_id_slug_key" UNIQUE CONSTRAINT, btree (foreign_key_id, slug)

However, I could still save into the database table a foreign_key of None/null and duplicate strings. 但是,我仍然可以在数据库表中保存None / null的foreign_key和重复的字符串。

For example, 例如,

I could input and save 我可以输入并保存

# 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()

So after using unique_together, why can I still input three of the same valued rows? 所以在使用unique_together之后,为什么我仍然可以输入三个相同值的行?

I'm just guessing right now that it might have to do with the default value of None for the foreign_key field, because before the unique_together, when I just had unique=True on slug, everything worked fine. 我现在只是猜测它可能与foreign_key字段的默认值None有关,因为在unique_together之前,当我在slug上有unique = True时,一切正常。 So if that is the case, what default value should I have that indicates a null value, but also maintains the unique constraint? 因此,如果是这种情况,我应该具有哪个默认值表示空值,还保持唯一约束?

In Postgresql NULL isn't equal to any other NULL . 在Postgresql中, NULL不等于任何其他NULL Therefore the rows you create are not the same (from Postgres' perspective). 因此,您创建的行不同(从Postgres的角度来看)。

Update 更新

You have a few ways to deal with it: 你有几种方法来处理它:

  • Forbid the Null value for foreign key and use some default value 禁止外键的Null值并使用一些默认值
  • Override the save method of your model to check that no such row exists 覆盖模型的save方法以检查是否存在此类行
  • Change SQL standard :) 改变SQL标准:)

Add a clean method to your model, so you can edit an existing row. 为模型添加一个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")

Beware, clean (or full_clean ) isn't called by the default save method. 注意,默认save方法不会调用clean (或full_clean )。

NB: if you put this code in the save method, update forms (like in the admin) won't work: you will have a traceback error due to the ValidationError exception. 注意:如果您将此代码放在save方法中,更新表单(如在管理员中)将不起作用:由于ValidationError异常,您将遇到跟踪错误。

Just manually create secondary index on slug field, but only for NULL values in foreign_key_id : 只需在slug字段上手动创建二级索引,但仅限于foreign_key_id NULL值:

CREATE INDEX table_name_unique_null_foreign_key
  ON table_name (slug) WHERE foreign_key_id is NULL

Please note, that Django does not support this, so without custom form/model validation you will get pure IntegrityError / 500. 请注意,Django不支持此功能,因此如果没有自定义表单/模型验证,您将获得纯IntegrityError / 500。

Possible duplicate of Create unique constraint with null columns 使用空列创建唯一约束的可能重复

As hobbyte mentioned, "In Postgresql NULL isn't equal to any other NULL. Therefore the rows you create are not the same (from Postgres' perspective)." 正如业余爱好者所说,“在Postgresql中NULL不等于任何其他NULL。因此你创建的行不一样(从Postgres的角度来看)。”

Another possible way to address this challenge is to add custom validation at the view level in the form_valid method. 解决此挑战的另一种可能方法是在form_valid方法中的视图级别添加自定义验证。

In views.py: 在views.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) 

This approach is helpful if you are using class based views, especially if you are automatically assigning values to fields that you want to hide from the user. 如果您使用基于类的视图,此方法很有用,尤其是在您自动将值分配给要从用户隐藏的字段时。

Pros: 优点:

  • You don't have to create dummy default values in the database 您不必在数据库中创建虚拟默认值
  • You can still use update forms (see Toff's answer) 您仍然可以使用更新表单(请参阅Toff的回答)

Cons: - This doesn't protect against duplicate rows created directly at the database level. 缺点: - 这不能防止直接在数据库级别创建的重复行。 - If you use Django's admin backend to create new MyModel objects, you'll need to add this same validation logic to your admin form. - 如果您使用Django的管理后端来创建新的MyModel对象,则需要将相同的验证逻辑添加到您的管理表单中。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM