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

发布于 2024-08-13 06:27:24 字数 978 浏览 2 评论 0原文

考虑以下情况: -

假设我的应用程序允许用户创建他们的州/省 国家。为了清楚起见,我们仅考虑 ASCII 字符 这里。

在美国,用户可以创建名为“德克萨斯州”的州。如果这个应用程序 正在内部使用,假设用户不关心它是否是 拼写为“texas”或“Texas”或“teXas”

但重要的是,系统应该阻止创建“texas”,如果 “德克萨斯州”已在数据库中。

如果模型如下所示:

class State(models.Model):
    name = models.CharField(max_length=50, unique=True)

在 postgres 中唯一性将区分大小写;也就是说,postgres 将允许用户按原样创建“texas”和“Texas” 被认为是独一无二的。

在这种情况下可以采取什么措施来防止这种行为。怎么样 努力为 Django 提供不区分大小写的唯一性 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 会有帮助?也许存在其他解决方案?

Consider the following situation: -

Suppose my app allows users to create the states / provinces in their
country. Just for clarity, we are considering only ASCII characters
here.

In the US, a user could create the state called "Texas". If this app
is being used internally, let's say the user doesn't care if it is
spelled "texas" or "Texas" or "teXas"

But importantly, the system should prevent creation of "texas" if
"Texas" is already in the database.

If the model is like the following:

class State(models.Model):
    name = models.CharField(max_length=50, unique=True)

The uniqueness would be case-sensitive in postgres; that is, postgres
would allow the user to create both "texas" and "Texas" as they are
considered unique.

What can be done in this situation to prevent such behavior. How does
one go about providing case-insenstitive uniqueness with Django and
Postgres

Right now I'm doing the following to prevent creation of case-
insensitive duplicates.

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

There are a number of cases where I will have to do this check and I'm not keen on having to write similar iexact checks everywhere.

Just wondering if there is a built-in or
better way? Perhaps db_type would help? Maybe some other solution exists?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

蔚蓝源自深海 2024-08-20 06:27:24

您可以定义从 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。

这将导致对自定义字段中的唯一值进行不区分大小写的比较。

You could define a custom model field derived from models.CharField.
This field could check for duplicate values, ignoring the case.

Custom fields documentation is here http://docs.djangoproject.com/en/dev/howto/custom-model-fields/

Look at http://code.djangoproject.com/browser/django/trunk/django/db/models/fields/files.py for an example of how to create a custom field by subclassing an existing field.

You could use the citext module of PostgreSQL https://www.postgresql.org/docs/current/static/citext.html

If you use this module, the the custom field could define "db_type" as CITEXT for PostgreSQL databases.

This would lead to case insensitive comparison for unique values in the custom field.

不语却知心 2024-08-20 06:27:24

或者,您可以更改默认的查询集管理器以在字段上执行不区分大小写的查找。在尝试解决类似的问题时,我遇到了:

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

Alternatively you can change the default Query Set Manager to do case insensitive look-ups on the field. In trying to solve a similar problem I came across:

http://djangosnippets.org/snippets/305/

Code pasted here for convenience:

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
心作怪 2024-08-20 06:27:24

一个非常简单的解决方案:

class State(models.Model):
    name = models.CharField(max_length=50, unique=True)

    def clean(self):
        self.name = self.name.capitalize()

a very simple solution:

class State(models.Model):
    name = models.CharField(max_length=50, unique=True)

    def clean(self):
        self.name = self.name.capitalize()
梦里泪两行 2024-08-20 06:27:24

Mayuresh 答案的明确步骤:

  1. 在 postgres 中执行:CREATE EXTENSION citext;

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

    来自 django.db.models 导入字段
    
    类 CaseInsensitiveTextField(fields.TextField):
        def db_type(自身,连接):
            返回“citext”
    

    参考:https://github.com/zacharyvoase/ django-postgres/blob/master/django_postgres/citext.py

  3. 在您的模型中使用: name = CaseInsensitiveTextField(unique=True)

Explicit steps for Mayuresh's answer:

  1. in postgres do: CREATE EXTENSION citext;

  2. in your models.py add:

    from django.db.models import fields
    
    class CaseInsensitiveTextField(fields.TextField):
        def db_type(self, connection):
            return "citext"
    

    reference: https://github.com/zacharyvoase/django-postgres/blob/master/django_postgres/citext.py

  3. in your model use: name = CaseInsensitiveTextField(unique=True)

哭了丶谁疼 2024-08-20 06:27:24

在 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"

On the Postgres side of things, a functional unique index will let you enforce unique values without case. citext is also noted, but this will work with older versions of PostgreSQL and is a useful technique in general.

Example:

# 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"
离线来电— 2024-08-20 06:27:24

除了已经提到的覆盖保存的选项之外,您可以简单地将所有小写文本存储在数据库中并在显示时将它们大写。

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)

Besides already mentioned option to override save, you can simply store all text in lower case in database and capitalize them on displaying.

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)
债姬 2024-08-20 06:27:24

您可以在序列化器上的UniqueValidator中使用lookup='iexact',如下所示:

class StateSerializer(serializers.ModelSerializer): 
    name = serializers.CharField(validators=[
    UniqueValidator(
        queryset=models.State.objects.all(),lookup='iexact'
    )]

django版本:1.11.6

You can use lookup='iexact' in UniqueValidator on serializer, like this:

class StateSerializer(serializers.ModelSerializer): 
    name = serializers.CharField(validators=[
    UniqueValidator(
        queryset=models.State.objects.all(),lookup='iexact'
    )]

django version: 1.11.6

北方的韩爷 2024-08-20 06:27:24

如果您不想使用 postgres 特定的解决方案,可以使用 upper() 在字段上创建唯一索引,以在数据库级别强制唯一性,然后创建自定义 Field mixin 覆盖 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)

你可以阅读有关此方法的更多信息 这里

If you don't want to use a postgres-specific solution, you can create a unique index on the field with upper() to enforce uniqueness at the database level, then create a custom Field mixin that overrides get_lookup() to convert case-sensitive lookups to their case-insensitive versions. The mixin looks like this:

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)

And you use it like this:

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)

You can read more about this approach here.

千纸鹤 2024-08-20 06:27:24

您可以通过覆盖模型的保存方法来做到这一点 - 请参阅 文档。您基本上会做类似的事情:

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 分支将使我们能够更轻松地做到这一点。

You can do this by overwriting the Model's save method - see the docs. You'd basically do something like:

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)

Also, I may be wrong about this, but the upcoming model-validation SoC branch will allow us to do this more easily.

苦行僧 2024-08-20 06:27:24

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()

Solution from suhail worked for me without the need to enable citext, pretty easy solution only a clean function and instead of capitalize I used upper(). Mayuresh's solution also works but changed the field from CharField to TextField.

class State(models.Model):

    name = models.CharField(max_length=50, unique=True)

    def clean(self):
        self.name = self.name.upper()
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文