Django意外的IntegrityError与PostgreSQL

时间:2017-03-02 15:15:52

标签: python mysql django postgresql

我正在使用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"并点击保存时,它会显示以下错误。

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切换数据库并再试一次,它会告诉我课程已经存在。

enter image description here

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

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

CREATE EXTENSION IF NOT EXISTS citext;

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

ALTER TABLE course ALTER COLUMN title TYPE citext;

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

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

1 个答案:

答案 0 :(得分:2)

<强> MIGRATIONS

最好的办法是不要改变数据库本身的约束,而是允许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)