Django中的独特模型字段和区分大小写(postgres)

时间:2009-12-07 04:31:16

标签: django postgresql django-models unique

考虑以下情况: -

假设我的应用允许用户在其中创建州/省 国家。为清楚起见,我们只考虑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会有帮助吗?也许存在其他一些解决方案?

10 个答案:

答案 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回答的明确步骤:

    postgres中的
  1. :CREATE EXTENSION citext;

  2. 你的models.py中的
  3. 添加:

    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

  4. 模型中的
  5. 使用: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的更多信息。