[英]Case insensitive unique model fields in Django?
我基本上有一个用户名是唯一的(不区分大小写),但是在用户提供的显示时大小写很重要。
我有以下要求:
这在 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 还提供了CIEmailField
和CICharField
,它们是EmailField
和CharField
的不区分大小写的版本。
将原始大小写混合字符串存储在纯文本列中。 使用没有长度修饰符的数据类型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 官方版本控制站点的更多信息。
通过覆盖模型管理器,您有两个选择。 首先是创建一个新的查找方法:
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和复杂。 我什至不愿意建议它,但为了完整起见,我将:覆盖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()
由于用户名总是小写,建议在 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.