Django中如何实现不区分大小写的唯一模型字段?

73 投票
11 回答
34089 浏览
提问于 2025-04-17 04:20

我有一个用户名,它是唯一的(不区分大小写),但在显示时要保留用户输入的大小写。

我有以下要求:

  • 这个字段要和CharField兼容
  • 这个字段要唯一,但不区分大小写
  • 这个字段在搜索时要忽略大小写(避免使用iexact,因为容易忘记)
  • 这个字段要保留用户输入的大小写
  • 最好是在数据库层面强制执行
  • 最好避免存储额外的字段

在Django中,这可能吗?

我想到的唯一解决方案是“某种方式”重写模型管理器,使用一个额外的字段,或者在搜索时总是使用'iexact'。

我使用的是Django 1.3和PostgreSQL 8.4.2。

11 个回答

33

截至2021年12月,借助于Django 4.0的UniqueConstraint表达式,你可以像这样在你的模型中添加一个Meta类:

class Meta:
    constraints = [
        models.UniqueConstraint(
            Lower('<field name>'),
            name='<constraint name>'
        ),
    ]

我并不是Django的专业开发者,对这个解决方案的性能等技术问题也不太了解。希望其他人能对此发表评论。

47

从Django 1.11开始,你可以使用CITextField,这是一个专门为Postgres数据库设计的字段,用来存储不区分大小写的文本,背后使用的是citext类型。

from django.db import models
from django.contrib.postgres.fields import CITextField

class Something(models.Model):
    foo = CITextField()

Django还提供了CIEmailFieldCICharField,它们分别是不区分大小写的EmailFieldCharField版本。

31

把原始的大小写混合字符串存放在一个普通文本列中。使用text或者没有长度限制的varchar类型,而不是varchar(n)。这两者基本上是一样的,但使用varchar(n)时你需要设定一个任意的长度限制,如果以后想要更改就会很麻烦。想了解更多,可以查看手册或者Peter Eisentraut在serverfault.SE上的相关回答

lower(string)上创建一个功能性唯一索引。这就是关键点:

CREATE UNIQUE INDEX my_idx ON mytbl(lower(name));

如果你尝试INSERT一个已经存在的、大小写混合的名字(以小写形式),你会收到一个唯一键冲突的错误。
为了快速查找相等的值,可以使用这样的查询:

SELECT * FROM mytbl WHERE lower(name) = 'foo' --'foo' is lower case, of course.

使用你在索引中相同的表达式(这样查询计划器就能识别兼容性),这样会非常快。


顺便提一下:你可能想要升级到更新版本的PostgreSQL。自8.4.2以来,已经有很多重要的修复。更多信息可以在官方Postgres版本网站找到。

撰写回答