Python Django中不区分大小写的唯一模型字段?

Python Django中不区分大小写的唯一模型字段?,python,django,postgresql,model,Python,Django,Postgresql,Model,我有一个用户名基本上是唯一的(不区分大小写),但是当按照用户提供的显示时,大小写很重要 我有以下要求: 字段与CharField兼容 字段是唯一的,但不区分大小写 字段需要可搜索,忽略大小写(避免使用iexact,容易忘记) 字段保存时保持大小写完整 最好在数据库级别强制执行 最好避免存储额外字段 在Django这可能吗 我提出的唯一解决方案是“以某种方式”重写模型管理器,使用一个额外字段,或者在搜索中始终使用“iexact” 我使用的是Django 1.3和PostgreSQL 8.4.2

我有一个用户名基本上是唯一的(不区分大小写),但是当按照用户提供的显示时,大小写很重要

我有以下要求:

  • 字段与CharField兼容
  • 字段是唯一的,但不区分大小写
  • 字段需要可搜索,忽略大小写(避免使用iexact,容易忘记)
  • 字段保存时保持大小写完整
  • 最好在数据库级别强制执行
  • 最好避免存储额外字段
在Django这可能吗

我提出的唯一解决方案是“以某种方式”重写模型管理器,使用一个额外字段,或者在搜索中始终使用“iexact”


我使用的是Django 1.3和PostgreSQL 8.4.2。

将原始的大小写混合字符串存储在纯文本列中。使用不带长度修饰符的数据类型
text
varchar
,而不是
varchar(n)
。它们本质上是相同的,但是对于varchar(n),您必须设置任意的长度限制,如果您以后想要更改,这可能是一个难题。阅读更多关于这方面的信息

下部(字符串)
上创建一个。这就是这里的要点:

CREATE UNIQUE INDEX my_idx ON mytbl(lower(name));
如果您尝试
插入一个小写的混合大小写名称,则会出现唯一的密钥冲突错误。
对于快速相等搜索,请使用如下查询:

SELECT * FROM mytbl WHERE lower(name) = 'foo' --'foo' is lower case, of course.
使用与索引中相同的表达式(以便查询计划器识别兼容性),这将非常快



顺便说一句:您可能需要升级到更新版本的PostgreSQL。有很多。有关的详细信息。

覆盖模型管理器后,您有两个选项。首先是创建一个新的查找方法:

class MyModelManager(models.Manager):
   def get_by_username(self, username):
       return self.get(username__iexact=username)

class MyModel(models.Model):
   ...
   objects = MyModelManager()
然后,使用
get\u by_username('blah')
而不是
get(username='blah')
,您不必担心忘记
iexact
。当然,这需要记住使用
get\u by\u username

第二种选择则更加老套和复杂。我甚至不太愿意建议它,但为了完整性起见,我会:覆盖
过滤器
获取
,这样如果您在按用户名查询时忘记了
iexact
,它会为您添加它

class MyModelManager(models.Manager):
    def filter(self, **kwargs):
        if 'username' in kwargs:
            kwargs['username__iexact'] = kwargs['username']
            del kwargs['username']
        return super(MyModelManager, self).filter(**kwargs)

    def get(self, **kwargs):
        if 'username' in kwargs:
            kwargs['username__iexact'] = kwargs['username']
            del kwargs['username']
        return super(MyModelManager, self).get(**kwargs)

class MyModel(models.Model):
   ...
   objects = MyModelManager()

您可以使用citext postgres类型,而不必再为任何类型的iexact而烦恼。只需在模型中注意底层字段不区分大小写。
更简单的解决方案。

因为用户名总是小写的,所以建议在Django中使用自定义的小写模型字段。为了便于访问和代码整洁,请在应用程序文件夹中创建一个新文件
fields.py

from django.db import models
from django.utils.six import with_metaclass

# Custom lowecase CharField

class LowerCharField(with_metaclass(models.SubfieldBase, models.CharField)):
    def __init__(self, *args, **kwargs):
        self.is_lowercase = kwargs.pop('lowercase', False)
        super(LowerCharField, self).__init__(*args, **kwargs)

    def get_prep_value(self, value):
        value = super(LowerCharField, self).get_prep_value(value)
        if self.is_lowercase:
            return value.lower()
        return value
models.py中的用法

from django.db import models
from your_app_name.fields import LowerCharField

class TheUser(models.Model):
    username = LowerCharField(max_length=128, lowercase=True, null=False, unique=True)

结束说明:您可以使用此方法在数据库中存储小写值,而不用担心
\uu iexact

从Django 1.11开始,您可以使用一个Postgres特定字段,用于支持citext类型的不区分大小写的文本

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

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

Django还提供了
CIEmailField
CICharField
,这是
EmailField
CharField
的不区分大小写版本,您可以在序列化程序的UniqueValidator中使用lookup='iexact',如下所示:

您还可以通过Django Models字段覆盖“获取准备值”

class LowerCaseField:
    def get_prep_value(self, value):
        if isinstance(value, Promise):
            value = value._proxy____cast()
        if value:
            value = value.strip().lower()
        return value


class LCSlugField(LowerCaseField, models.SlugField):
    pass


class LCEmailField(LowerCaseField, models.EmailField):
    pass

email = LCEmailField(max_length=255, unique=True)

谢谢你的解决方案。我最终使用了这一个和下面的一个,所以现在你不能只处理代码了。很好的解决方案。有没有一种方法可以使用Django ORM实现这一点?或者我应该直接在PostgreSQL中执行此操作?@fcrazy:我不是Django方面的专家,但是
CREATE UNIQUE INDEX…
语句中的单个语句应该可以完成此任务。@ErwinBrandstetter感谢Erwin,我做了自己的研究,似乎在Django中执行此操作的一个好地方是添加文件
@Dre数量(并发)用户或交易对索引使用没有负面影响。索引不会“导致碎片”。也许你是说指数膨胀?可能是一件事。我建议你开始一个新的问题,所有的细节,以澄清你的担心。我喜欢黑客版本比自定义方法版本+1更黑客!我更喜欢这种方法,尤其是黑客版本,而不是公认的答案,因为这是DBMS不可知的。它最终会使您坚持使用Django的不区分大小写的QuerySet方法,因此Django仍然可以使用适当的排序规则强制生成SQL语句,而不管DBMS后端如何。它可能与数据库无关,但并不阻止您使用不同的大小写插入相同的值。因此,对于不区分大小写的唯一模型字段,它不是一个完整的解决方案。在将对象存储到数据库中之前,您始终可以将其转换为小写,但随后您会丢失原来的大小写,这不一定是可以接受的。可能是nice的重复!但是,请注意,您必须安装postgres扩展(citext)才能使用它。