【问题标题】:Django unexpected IntegrityError with PostgreSQLDjango 意外的 IntegrityError 与 PostgreSQL
【发布时间】:2017-07-22 09:34:28
【问题描述】:

我将postgreSQLDjango 1.10python 3.4 一起使用。我有一个这样定义的Course 模型:

class Course(models.Model):

    title = models.CharField(max_length=200, unique=True)
    content = models.TextField(max_length=200)

然后我使用以下命令手动将唯一索引添加到 title 列。

CREATE UNIQUE INDEX title_unique on wiki_course (LOWER(title));

假设数据库已经有"Programming Basics" 作为标题。当我将 "programming basics" 添加到 title 并点击 save 时,它​​会显示以下错误。

IntegrityError at /admin/wiki/course/add/
duplicate key value violates unique constraint "title_unique"
DETAIL:  Key (lower(title::text))=(programming basics) already exists.
Request Method: POST
Request URL:    http://127.0.0.1:8000/admin/wiki/course/add/
Django Version: 1.10.5
Exception Type: IntegrityError
Exception Value:    
duplicate key value violates unique constraint "title_unique"

另一方面,如果我从 MySQL 切换数据库并重试,它会告诉我课程已经存在。

有没有办法在 PostgreSQL 中实现这种行为?


更新:一种解决方案是使用 citext 类型字段作为标题。首先,您必须使用以下命令启用 citext 扩展。

CREATE EXTENSION IF NOT EXISTS citext;

然后使用alter语句来改变所需列的类型。

ALTER TABLE course ALTER COLUMN title TYPE citext;

执行这两个查询后。 Django 在表单中显示错误,而不是抛出 IntegrityError 异常。

如果有人知道更好的解决方案,请发布。

【问题讨论】:

  • 因为unique=True 在您的title 字段中,所以如果您有相同名称的重复title,它应该返回错误..
  • 是的,我知道,但我想在表单中显示错误,而不是例外。 MySQL 开箱即用。有什么办法可以在 PostgreSQL 中完成这件事。我在 Google 群组的某个地方读到过有关此问题的信息,他们建议在 PostgreSQL 中创建函数索引或其他内容。是这样解决这个问题的吗?
  • @SancaKembang 我更新了问题请看一下

标签: python mysql django postgresql


【解决方案1】:

迁移

最好不要改变数据库本身的约束,而是允许 Django 处理对模型的更改。

假设你已经有了这个。

class Course(models.Model):
    title = models.CharField(max_length=200)

然后,您决定让title 独一无二。

class Course(models.Model):
    title = models.CharField(max_length=200, unique=True)

要强制执行此操作,您无需进入数据库并直接调用命令。相反,我们允许 Django 为我们处理迁移。

$ ./manage.py makemigrations
$ ./manage.py migrate

解决方案 #1 - 保存

class Course(models.Model):
    title = models.CharField(max_length=200, unique=True)

    def save(self, *args, **kwargs):
        try:
            Course.objects.get(title__iexact=self.title)
            raise models.ValidationError
        except Course.DoesNotExist:
            pass
        super().save(*args, **kwargs)

这个方法的问题是每次保存都会再次调用你的数据库。不理想。

解决方案 #2 - 两个字段

您还可以有一种虚拟字段,它始终存储小写字符串并提供唯一的。

class Course(models.Model):
    title = models.CharField(max_length=200)
    lower_title = models.CharField(max_length=200, unique=True, blank=True)

    def save(self, *args, **kwargs):
        self.lower_title = self.title.lower()
        super().save(*args, **kwargs)

在这里,我们设置了blank=True,这样lower_title 就不会因为为空而引发错误。

这样做的一个缺点是它会在您的数据库中产生更多的开销,因为标题存储在两个位置。这里的想法是我们对一个我们总是知道是小写的字段运行唯一约束。然后,只需在需要的地方使用title

同样,我们可以使用 django 的 slugify 来实现类似的结果。

from django.template.defaultfilters import slugify


class Course(models.Model):
    title = models.CharField(max_length=200)
    slug = models.SlugField(max_length=200, unique=True)

    def save(self, *args, **kwargs):
        self.slug = slugify(self.title)
        super().save(*args, **kwargs)

【讨论】:

  • 我知道什么是迁移。我想在表单中显示错误而不是异常
  • 此外,如果我不手动发出CREATE UNIQUE INDEX title_unique on wiki_course (LOWER(title)); 命令,postgresql 甚至不会检测到“编程基础”和“编程基础”之间的区别。它只会将后者添加到数据库中。
  • 然后,处理该问题的“django”方法是使用 save 方法。或者,如果您正在谈论关于验证的表单。当我回到电脑前时,我会尝试发布我脑海中的代码,并更新我的答案。
  • 是的,请发布您的答案。谢谢
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-10-12
  • 2011-08-02
  • 2012-07-23
  • 1970-01-01
  • 2021-12-25
  • 1970-01-01
相关资源
最近更新 更多