考虑以下情况: -
假设我的应用允许用户在其中创建州/省 国家。为清楚起见,我们只考虑ASCII字符 这里。
在美国,用户可以创建名为“Texas”的州。如果这个程序 正在内部使用,假设用户不关心它是否正在使用 拼写“德克萨斯”或“德克萨斯”或“teXas”
但重要的是,如果,系统应该阻止“德克萨斯”的创建 “Texas”已经在数据库中。
如果模型如下:
class State(models.Model):
name = models.CharField(max_length=50, unique=True)
postgres中的唯一性区分大小写; postgres 允许用户按原样创建“德克萨斯”和“德克萨斯” 被认为是独特的。
在这种情况下可以采取哪些措施来防止此类行为。如何 一个用Django和。提供case- insenstitive 唯一性 Postgres
现在我正在做以下事情以防止创建案例 - 不敏感的重复。
class CreateStateForm(forms.ModelForm):
def clean_name(self):
name = self.cleaned_data['name']
try:
State.objects.get(name__iexact=name)
except ObjectDoesNotExist:
return name
raise forms.ValidationError('State already exists.')
class Meta:
model = State
在许多情况下,我将不得不进行此项检查,而且我并不热衷于在任何地方编写类似的iexact检查。
只是想知道是否有内置或 更好的方法?也许db_type会有帮助吗?也许存在其他一些解决方案?
答案 0 :(得分:29)
您可以定义从models.CharField
派生的自定义模型字段。
此字段可以检查重复值,忽略大小写。
自定义字段文档位于http://docs.djangoproject.com/en/dev/howto/custom-model-fields/
查看http://code.djangoproject.com/browser/django/trunk/django/db/models/fields/files.py,了解如何通过继承现有字段来创建自定义字段的示例。
您可以使用PostgreSQL的{citext}模块https://www.postgresql.org/docs/current/static/citext.html
如果您使用此模块,则自定义字段可以将“db_type”定义为PostgreSQL数据库的CITEXT。
这将导致对自定义字段中的唯一值进行不区分大小写的比较。
答案 1 :(得分:7)
或者,您可以更改默认的查询集管理器,以对字段执行不区分大小写的查找。在尝试解决类似的问题时,我遇到了:
http://djangosnippets.org/snippets/305/
为方便起见,此处粘贴了代码:
from django.db.models import Manager
from django.db.models.query import QuerySet
class CaseInsensitiveQuerySet(QuerySet):
def _filter_or_exclude(self, mapper, *args, **kwargs):
# 'name' is a field in your Model whose lookups you want case-insensitive by default
if 'name' in kwargs:
kwargs['name__iexact'] = kwargs['name']
del kwargs['name']
return super(CaseInsensitiveQuerySet, self)._filter_or_exclude(mapper, *args, **kwargs)
# custom manager that overrides the initial query set
class TagManager(Manager):
def get_query_set(self):
return CaseInsensitiveQuerySet(self.model)
# and the model itself
class Tag(models.Model):
name = models.CharField(maxlength=50, unique=True, db_index=True)
objects = TagManager()
def __str__(self):
return self.name
答案 2 :(得分:6)
在Postgres方面,功能唯一索引将允许您在没有大小写的情况下强制执行唯一值。还注意到citext,但这适用于旧版本的PostgreSQL,并且通常是一种有用的技术。
示例:
# create table foo(bar text);
CREATE TABLE
# create unique index foo_bar on foo(lower(bar));
CREATE INDEX
# insert into foo values ('Texas');
INSERT 0 1
# insert into foo values ('texas');
ERROR: duplicate key value violates unique constraint "foo_bar"
答案 3 :(得分:5)
Mayuresh回答的明确步骤:
:CREATE EXTENSION citext;
添加:
from django.db.models import fields
class CaseInsensitiveTextField(fields.TextField):
def db_type(self, connection):
return "citext"
参考:https://github.com/zacharyvoase/django-postgres/blob/master/django_postgres/citext.py
使用:name = CaseInsensitiveTextField(unique = True)
答案 4 :(得分:3)
除了已经提到的覆盖保存选项之外,您可以简单地将所有文本以小写形式存储在数据库中,并在显示时将其大写。
class State(models.Model):
name = models.CharField(max_length=50, unique=True)
def save(self, force_insert=False, force_update=False):
self.name = self.name.lower()
super(State, self).save(force_insert, force_update)
答案 5 :(得分:3)
一个非常简单的解决方案:
class State(models.Model):
name = models.CharField(max_length=50, unique=True)
def clean(self):
self.name = self.name.capitalize()
答案 6 :(得分:1)
您可以通过覆盖模型的保存方法来完成此操作 - 请参阅docs。你基本上会做类似的事情:
class State(models.Model):
name = models.CharField(max_length=50, unique=True)
def save(self, force_insert=False, force_update=False):
if State.objects.get(name__iexact = self.name):
return
else:
super(State, self).save(force_insert, force_update)
此外,我可能错了,但即将推出的模型验证SoC分支将使我们能够更轻松地完成此任务。
答案 7 :(得分:1)
您可以使用lookup =' iexact'在序列化器上的UniqueValidator中,如下所示:
class StateSerializer(serializers.ModelSerializer):
name = serializers.CharField(validators=[
UniqueValidator(
queryset=models.State.objects.all(),lookup='iexact'
)]
django版本:1.11.6
答案 8 :(得分:0)
来自suhail的解决方案为我工作而无需启用citext,非常简单的解决方案只是一个干净的功能而不是大写我使用upper()
。 Mayuresh的解决方案也有效但将字段从CharField
更改为TextField
。
class State(models.Model):
name = models.CharField(max_length=50, unique=True)
def clean(self):
self.name = self.name.upper()
答案 9 :(得分:0)
如果您不想使用特定于Postgres的解决方案,则可以使用upper()
在字段上创建唯一索引以在数据库级别上实施唯一性,然后创建自定义Field
混合覆盖get_lookup()
,以将区分大小写的查找转换为不区分大小写的版本。 mixin看起来像这样:
class CaseInsensitiveFieldMixin:
"""
Field mixin that uses case-insensitive lookup alternatives if they exist.
"""
LOOKUP_CONVERSIONS = {
'exact': 'iexact',
'contains': 'icontains',
'startswith': 'istartswith',
'endswith': 'iendswith',
'regex': 'iregex',
}
def get_lookup(self, lookup_name):
converted = self.LOOKUP_CONVERSIONS.get(lookup_name, lookup_name)
return super().get_lookup(converted)
您可以这样使用它:
from django.db import models
class CICharField(CaseInsensitiveFieldMixin, models.CharField):
pass
class CIEmailField(CaseInsensitiveFieldMixin, models.EmailField):
pass
class TestModel(models.Model):
name = CICharField(unique=True, max_length=20)
email = CIEmailField(unique=True)
您可以了解有关此方法here的更多信息。