繁体   English   中英

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

[英]Case insensitive unique model fields in Django?

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

我有以下要求:

  • 字段与 CharField 兼容<\/li>
  • 字段是唯一的,但不区分大小写<\/li>
  • 字段需要可搜索忽略大小写(避免使用 iexact,容易忘记)<\/li>
  • 字段与 case 原封不动地存储<\/li>
  • 最好在数据库级别执行<\/li>
  • 最好避免存储额外的字段<\/li><\/ul>

    这在 Django 中可能吗?

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

    我在 Django 1.3 和 PostgreSQL 8.4.2 上。

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

将原始大小写混合字符串存储在纯文本列中 使用没有长度修饰符的数据类型textvarchar而不是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 官方版本控制站点的更多信息。

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

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

第二种选择更加hackier和复杂。 我什至不愿意建议它,但为了完整起见,我将:覆盖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()

由于用户名总是小写,建议在 Django 中使用自定义的小写模型字段。 为了便于访问和代码整洁, fields.py在您的应用程序文件夹中创建一个新文件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

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

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

您还可以通过 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)

对于 2021 年的任何人,在Django 4.0 UniqueConstraint 表达式的帮助下,您可以将 Meta 类添加到您的模型中,如下所示:

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

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

我喜欢 Chris Pratt 的答案,但它对我不起作用,因为models.Manager<\/code> -class 没有get(...)<\/code>或filter(...)<\/code>方法。 我不得不通过自定义QuerySet<\/code>采取额外的步骤:

from django.contrib.auth.base_user import BaseUserManager
from django.db.models import QuerySet

class CustomUserManager(BaseUserManager):

    # Use the custom QuerySet where get and filter will change 'email'
    def get_queryset(self):
        return UserQuerySet(self.model, using=self._db)

    def create_user(self, email, password, **extra_fields):
        ...

    def create_superuser(self, email, password, **extra_fields):
        ...

class UserQuerySet(QuerySet):

    def filter(self, *args, **kwargs):
        if 'email' in kwargs:
            # Probably also have to replace...
            #   email_contains -> email_icontains,
            #   email_exact -> email_iexact,
            #   etc.
            kwargs['email__iexact'] = kwargs['email']
            del kwargs['email']
        return super().filter(*args, **kwargs)

    def get(self, *args, **kwargs):
        if 'email' in kwargs:
            kwargs['email__iexact'] = kwargs['email']
            del kwargs['email']
        return super().get(*args, **kwargs)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM