31 Stimmen

Eindeutiges Modellfeld in Django und Groß-/Kleinschreibung (Postgres)

Stellen Sie sich die folgende Situation vor: -

Angenommen, meine Anwendung ermöglicht es Benutzern, die Staaten / Provinzen in ihren Land zu erstellen. Nur zur Klarstellung: Wir betrachten hier nur ASCII-Zeichen hier.

In den USA könnte ein Benutzer einen Staat namens "Texas" erstellen. Wenn diese Anwendung intern verwendet wird, ist es dem Benutzer egal, ob der Staat "texas" oder "Texas" oder "teXas" geschrieben wird

Wichtig ist jedoch, dass das System die Erstellung von "texas" verhindern sollte, wenn "Texas" bereits in der Datenbank vorhanden ist.

Wenn das Modell wie folgt aussieht:

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

Die Einzigartigkeit würde in Postgres zwischen Groß- und Kleinschreibung unterscheiden, d.h. Postgres würde dem Benutzer erlauben, sowohl "texas" als auch "Texas" zu erstellen, da sie als eindeutig gelten.

Was kann in dieser Situation getan werden, um ein solches Verhalten zu verhindern? Wie kann man vorgehen, wenn man Fall- unauffällig Einzigartigkeit mit Django und Postgres

Im Moment tue ich Folgendes, um die Erstellung von Fällen zu verhindern. unsensiblen Duplikaten zu verhindern.

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

Es gibt eine Reihe von Fällen, in denen ich diese Prüfung durchführen muss, und ich bin nicht scharf darauf, überall ähnliche iexact-Prüfungen schreiben zu müssen.

Ich frage mich nur, ob es eine eingebaute oder besseren Weg gibt? Vielleicht würde db_type helfen? Vielleicht gibt es auch eine andere Lösung?

32voto

Mayuresh Punkte 1042

Sie können ein benutzerdefiniertes Modellfeld definieren, das von models.CharField . Dieses Feld kann auf doppelte Werte geprüft werden, wobei der Fall ignoriert wird.

Dokumentation zu benutzerdefinierten Feldern finden Sie hier http://docs.djangoproject.com/en/dev/howto/custom-model-fields/

Blick auf http://code.djangoproject.com/browser/django/trunk/django/db/models/fields/files.py für ein Beispiel, wie man ein benutzerdefiniertes Feld durch Unterklassifizierung eines vorhandenen Feldes erstellt.

Sie können das citext-Modul von PostgreSQL verwenden https://www.postgresql.org/docs/current/static/citext.html

Wenn Sie dieses Modul verwenden, kann das benutzerdefinierte Feld "db_type" als CITEXT für PostgreSQL-Datenbanken definiert werden.

Dies würde dazu führen, dass bei eindeutigen Werten im benutzerdefinierten Feld die Groß- und Kleinschreibung nicht berücksichtigt wird.

8voto

Foo Punkte 81

Alternativ können Sie den standardmäßigen Abfragesatzmanager so ändern, dass Groß- und Kleinschreibung bei der Suche nach dem Feld nicht berücksichtigt wird. Bei dem Versuch, ein ähnliches Problem zu lösen, stieß ich auf:

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

Code der Einfachheit halber hier eingefügt:

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

7voto

suhailvs Punkte 17111

Eine sehr einfache Lösung:

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

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

7voto

o17t H1H' S'k Punkte 2184

Explizite Schritte für die Antwort von Mayuresh:

  1. in postgres tun: CREATE EXTENSION citext;

  2. in Ihrer models.py hinzufügen:

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

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

  3. in Ihrem Modell verwenden: name = CaseInsensitiveTextField(unique=True)

6voto

Alex Brasetvik Punkte 10472

Auf der Postgres-Seite können Sie mit einem funktionalen eindeutigen Index eindeutige Werte ohne Groß- und Kleinschreibung erzwingen. citext wird ebenfalls erwähnt, aber dies funktioniert auch mit älteren Versionen von PostgreSQL und ist generell eine nützliche Technik.

Ejemplo:

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

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X