【问题标题】:Case insensitive unique model fields in Django?Django中不区分大小写的唯一模型字段?
【发布时间】:2011-12-08 01:40:47
【问题描述】:

我的用户名基本上是唯一的(不区分大小写),但在显示用户提供的内容时,大小写很重要。

我有以下要求:

  • 字段与 CharField 兼容
  • 字段是唯一的,但不区分大小写
  • 字段必须是可搜索的,忽略大小写(避免使用 iexact,容易忘记)
  • 字段保存大小写不变
  • 最好在数据库级别强制执行
  • 最好避免存储额外的字段

这在 Django 中可行吗?

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

我使用的是 Django 1.3 和 PostgreSQL 8.4.2。

【问题讨论】:

标签: python django postgresql model


【解决方案1】:

从 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 的不区分大小写的版本。

【讨论】:

  • 不错!但是,请注意,您必须安装一个 postgres 扩展 (citext) 才能使用它。
  • 我仍然可以制作“gYm FOOD”,然后我可以添加“gYM FOOD”,unique=True 不会给我错误。
【解决方案2】:

将原始混合大小写字符串存储在纯文本列中。使用不带长度修饰符的数据类型 textvarchar 而不是 varchar(n)。它们本质上是相同的,但是使用 varchar(n) 您必须设置任意长度限制,如果您想稍后更改,这可能会很痛苦。阅读更多关于 in the manualrelated answer by Peter Eisentraut @serverfault.SE 的信息。

lower(string) 上创建一个functional unique index。这是这里的重点:

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

如果您尝试 INSERT 一个混合大小写的名称,该名称已经以小写形式存在,您会收到唯一键违规错误。
对于快速相等搜索,请使用如下查询:

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

使用与索引中相同的表达式(以便查询规划器识别兼容性),这将非常快。


顺便说一句:您可能想要升级到更新版本的 PostgreSQL。有很多important fixes since 8.4.2。更多关于official Postgres versioning site

【讨论】:

  • 感谢您的解决方案。我最终使用了这个和下面的一个,所以现在你不能只解决代码。
  • 很好的解决方案。有没有办法使用 Django ORM 做到这一点?还是直接在 PostgreSQL 里做?
  • @fcrazy:我不是 Django 方面的专家,但是 CREATE UNIQUE INDEX ... 语句的单个 raw SQL call 应该可以完成这项工作。
  • @ErwinBrandstetter 谢谢 Erwin,我做了自己的研究,似乎在 Django 中执行此操作的一个好地方是添加文件 <appname>/sql/<modelname>.sql,其中 <appname> 是给定的应用程序,就像解释它一样在这里:docs.djangoproject.com/en/1.5/ref/django-admin/…
  • @Dre (并发)用户或事务的数量对索引使用没有不利影响。索引不会“导致碎片”。也许你的意思是索引膨胀?可以是一件事。我建议您开始一个包含所有详细信息的新问题,以澄清您的担忧。
【解决方案3】:

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

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

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

然后,您使用get_by_username('blah') 而不是get(username='blah'),您不必担心忘记iexact。当然这需要你记得使用get_by_username

第二个选项更加复杂和复杂。我什至不愿建议它,但为了完整起见,我将:覆盖filterget 这样如果您在通过用户名查询时忘记了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()

【讨论】:

  • 我更喜欢 hackier 版本,而不是自定义方法版本 +1 的 hackiness!
  • 我更喜欢这种方法,尤其是 hackier 版本,而不是接受的答案,因为它与 DBMS 无关。它让你最终坚持使用 Django 不区分大小写的 QuerySet 方法,因此无论 DBMS 后端如何,Django 仍然可以生成具有适当排序规则的 SQL 语句。
  • 它可能与数据库无关,但它不会阻止您以不同的大小写插入相同的值。因此,它不是不区分大小写的唯一模型字段的完整解决方案。在将对象存储到数据库之前,您始终可以转换为小写,但随后您会丢失原始大小写,这不一定是可以接受的。
【解决方案4】:

由于用户名总是小写,建议在 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)

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

【讨论】:

    【解决方案5】:

    您可以改用 citext postgres 类型,而不再使用任何类型的 iexact。只需在模型中记下底层字段不区分大小写即可。 更简单的解决方案。

    【讨论】:

      【解决方案6】:

      您可以在序列化程序的 UniqueValidator 中使用 lookup='iexact',如下所示: Unique model field in Django and case sensitivity (postgres)

      【讨论】:

        【解决方案7】:

        对于 2021 年的任何人,在 Django 4.0 UniqueConstraint expressions 的帮助下,您可以像这样将 Meta 类添加到您的模型中:

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

        我绝不是 Django 专业开发人员,而且我不了解有关此解决方案的性能问题等技术注意事项。希望其他人对此发表评论。

        【讨论】:

          【解决方案8】:

          您还可以通过 Django 模型字段覆盖“get_prep_value”

          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)
          

          【讨论】:

            猜你喜欢
            • 2023-03-27
            • 2017-04-15
            • 2020-08-29
            • 2012-12-09
            • 1970-01-01
            • 2015-10-21
            • 2021-08-02
            • 2014-08-08
            相关资源
            最近更新 更多