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 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)才能使用它。