【问题标题】:Django unique together constraint failure?Django唯一一起约束失败?
【发布时间】:2013-07-04 19:38:59
【问题描述】:

使用 Django 1.5.1。 Python 2.7.3。

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

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

【问题讨论】:

    标签: python django postgresql


    【解决方案1】:

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

    更新

    你有几种方法来处理它:

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

    【讨论】:

    • 我该如何纠正这个问题?如果外键为 NULL,我是否可以为外键字段分配一个值或类似 slug 上的唯一值?
    • postgres 中的外键是否有默认值,这样该值将永远不会被自然使用?
    • 我不认为有这样的事情。它会破坏架构。我会在关系的另一边创建一个虚拟对象。
    • 我喜欢选项 3(更改 SQL 标准)。这种行为很痛苦。
    • 我认为 django 应该通过检测迁移中的问题来警告用户,这样我们就可以避免这些陷阱
    【解决方案2】:

    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 异常,您将遇到回溯错误。

    【讨论】:

      【解决方案3】:

      只需在 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。

      Create unique constraint with null columns 可能重复

      【讨论】:

        【解决方案4】:

        正如 hobbyte 所提到的,“在 Postgresql 中,NULL 不等于任何其他 NULL。因此,您创建的行不一样(从 Postgres 的角度来看)。”

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

        在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)

        如果您使用基于类的视图,这种方法很有帮助,尤其是当您自动为要对用户隐藏的字段分配值时。

        优点:

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

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-04-28
          • 2020-10-22
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多