Django中不区分大小写的独特模型字段?

noo*_*zie 43 python django postgresql model

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

我有以下要求:

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

Django有可能吗?

我想出的唯一解决方案是"以某种方式"覆盖模型管理器,使用额外的字段,或者总是在搜索中使用'iexact'.

我在Django 1.3和PostgreSQL 8.4.2上.

Ali*_*ani 32

截至 2021 年 12 月,借助Django 4.0 UniqueConstraint 表达式,您可以向模型添加 Meta 类,如下所示:

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

我绝不是 Django 专业开发人员,我不了解此解决方案的技术考虑因素,例如性能问题。希望其他人对此发表评论。


Erw*_*ter 26

将原始的大小写混合字符串存储在纯文本列中.使用数据类型textvarchar不使用长度修饰符而不是varchar(n).它们本质上是相同的,但是使用varchar(n)你必须设置一个任意长度限制,如果你想稍后改变,这可能会很痛苦.请阅读Peter Eisentraut @ serverfault.SE 手册相关答案中的更多相关内容.

创建一个功能独特的指数lower(string).这是主要观点:

CREATE UNIQUE INDEX my_idx ON mytbl(lower(name));
Run Code Online (Sandbox Code Playgroud)

如果您尝试以INSERT小写形式存在的混合大小写名称,则会出现唯一的密钥违例错误.
对于快速相等搜索,请使用如下查询:

SELECT * FROM mytbl WHERE lower(name) = 'foo' --'foo' is lower case, of course.
Run Code Online (Sandbox Code Playgroud)

使用索引中的相同表达式(以便查询计划程序识别兼容性),这将非常快.


暂且不说:您可能希望升级到更新版本的PostgreSQL.自8.4.2以来,已经有很多重要的修复.更多关于官方Postgres版本网站.


Rod*_*olz 24

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

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

class Something(models.Model):
    foo = CITextField()
Run Code Online (Sandbox Code Playgroud)

在Django中还提供了CIEmailFieldCICharField,这是不区分大小写的版本EmailFieldCharField.

  • 好的!但是,请注意,您必须安装 postgres 扩展 (citext) 才能使用它。 (2认同)

Chr*_*att 18

通过覆盖模型管理器,您有两种选择.首先是创建一个新的查找方法:

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

class MyModel(models.Model):
   ...
   objects = MyModelManager()
Run Code Online (Sandbox Code Playgroud)

然后,你使用get_by_username('blah')而不是get(username='blah'),你不必担心忘记iexact.当然那要求你记得使用get_by_username.

第二种选择更加黑客和令人费解.我甚至对它的建议犹豫不决,但为了完整起见,我将:覆盖filter并且get如果你忘记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()
Run Code Online (Sandbox Code Playgroud)

  • 我更喜欢这种方法,特别是hackier版本,而不是接受的答案,因为这是DBMS不可知的.它使您最终坚持使用Django不区分大小写的QuerySet方法,因此无论DBMS后端如何,Django仍然可以使用正确的归类强制生成SQL语句. (7认同)
  • 我喜欢hackier版本比自定义方法版本+1更好的hackiness! (3认同)
  • 它可能与数据库无关,但它不会阻止您使用不同的大小写插入相同的值.因此,对于不区分大小写的唯一模型字段,它不是一个完整的解决方案.在将对象存储在数据库中之前,您始终可以转换为小写,但随后您将丢失原始案例,这不一定是可接受的. (3认同)

Sur*_*raj 6

由于用户名总是小写,建议在 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
Run Code Online (Sandbox Code Playgroud)

使用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)
Run Code Online (Sandbox Code Playgroud)

尾注:您可以使用此方法将小写值存储在数据库中,而不必担心__iexact.