database tutorial - Unique BooleanField Wert in Django?




model fields (10)

Ich würde die Speichermethode des Modells überschreiben und wenn Sie den Boolean auf True festgelegt haben, stellen Sie sicher, dass alle anderen auf False festgelegt sind.

from django.db import transaction

class Character(models.Model):
    name = models.CharField(max_length=255)
    is_the_chosen_one = models.BooleanField()

    @transaction.atomic
    def save(self, *args, **kwargs):
        if self.is_the_chosen_one:
            Character.objects.filter(
                is_the_chosen_one=True).update(is_the_chosen_one=False)
        super(Character, self).save(*args, **kwargs)

Ich habe versucht, die ähnliche Antwort von Adam zu bearbeiten, aber es wurde abgelehnt, zu viel von der ursprünglichen Antwort zu ändern. Dieser Weg ist prägnanter und effizienter, da die Überprüfung anderer Einträge in einer einzigen Abfrage erfolgt.

Angenommen, mein models.py ist so:

class Character(models.Model):
    name = models.CharField(max_length=255)
    is_the_chosen_one = models.BooleanField()

Ich möchte nur eine meiner Character Instanzen haben is_the_chosen_one == True und alle anderen haben is_the_chosen_one == False . Wie kann ich sicherstellen, dass diese Eindeutigkeitsbedingung eingehalten wird?

Bestnoten für Antworten, die berücksichtigen, wie wichtig es ist, die Einschränkung auf Datenbank-, Modell- und (Admin) -Formstufen zu beachten!


Mit einem ähnlichen Ansatz wie Saul, aber etwas anderes Ziel:

class TrueUniqueBooleanField(BooleanField):

    def __init__(self, unique_for=None, *args, **kwargs):
        self.unique_for = unique_for
        super(BooleanField, self).__init__(*args, **kwargs)

    def pre_save(self, model_instance, add):
        value = super(TrueUniqueBooleanField, self).pre_save(model_instance, add)

        objects = model_instance.__class__.objects

        if self.unique_for:
            objects = objects.filter(**{self.unique_for: getattr(model_instance, self.unique_for)})

        if value and objects.exclude(id=model_instance.id).filter(**{self.attname: True}):
            msg = 'Only one instance of {} can have its field {} set to True'.format(model_instance.__class__, self.attname)
            if self.unique_for:
                msg += ' for each different {}'.format(self.unique_for)
            raise ValidationError(msg)

        return value

Diese Implementierung wird einen ValidationError wenn Sie versuchen, einen anderen Datensatz mit einem Wert von True zu speichern.

Außerdem habe ich das Argument unique_for hinzugefügt, das auf ein beliebiges anderes Feld im Modell gesetzt werden kann, um die unique_for -Eindeutigkeit nur für Datensätze mit demselben Wert zu prüfen, wie zum Beispiel:

class Phone(models.Model):
    user = models.ForeignKey(User)
    main = TrueUniqueBooleanField(unique_for='user', default=False)

Anstatt benutzerdefinierte Modellreinigung / pre_save verwenden, habe ich ein benutzerdefiniertes Feld erstellt, das die pre_save Methode auf django.db.models.BooleanField . Anstatt einen Fehler zu melden, wenn ein anderes Feld True , habe ich alle anderen Felder False wenn es True . Anstatt einen Fehler zu erzeugen, wenn das Feld False und kein anderes Feld True , habe ich das Feld als True gespeichert

felder.py

from django.db.models import BooleanField


class UniqueBooleanField(BooleanField):
    def pre_save(self, model_instance, add):
        objects = model_instance.__class__.objects
        # If True then set all others as False
        if getattr(model_instance, self.attname):
            objects.update(**{self.attname: False})
        # If no true object exists that isnt saved model, save as True
        elif not objects.exclude(id=model_instance.id)\
                        .filter(**{self.attname: True}):
            return True
        return getattr(model_instance, self.attname)

# To use with South
from south.modelsinspector import add_introspection_rules
add_introspection_rules([], ["^project\.apps\.fields\.UniqueBooleanField"])

models.py

from django.db import models

from project.apps.fields import UniqueBooleanField


class UniqueBooleanModel(models.Model):
    unique_boolean = UniqueBooleanField()

    def __unicode__(self):
        return str(self.unique_boolean)

Wenn ich versuche, mit den Antworten hier über die Runden zu kommen, finde ich, dass einige von ihnen das gleiche Problem erfolgreich lösen und jedes in verschiedenen Situationen geeignet ist:

Ich würde auswählen:

  • @semente : Respektiert die Einschränkung auf der Datenbank-, Modell- und Admin-Formularebene, während sie Django ORM so wenig wie möglich außer Kraft setzt. Darüber hinaus kann es wahrscheinlich in einer through Tabelle eines ManyToManyField in einer unique_together Situation verwendet werden. (Ich werde es überprüfen und berichten)

    class MyModel(models.Model):
        is_the_chosen_one = models.NullBooleanField(default=None, unique=True)
    
        def save(self, *args, **kwargs):
            if self.is_the_chosen_one is False:
                self.is_the_chosen_one = None
            super(MyModel, self).save(*args, **kwargs)
    
  • @Flyte : @Flyte die Datenbank nur um eine zusätzliche Zeit und akzeptiert den aktuellen Eintrag als den ausgewählten Eintrag. Sauber und elegant.

    from django.db import transaction
    
    class Character(models.Model):
        name = models.CharField(max_length=255)
        is_the_chosen_one = models.BooleanField()
    
        @transaction.atomic
        def save(self, *args, **kwargs):
            if self.is_the_chosen_one:
                Character.objects.filter(
                    is_the_chosen_one=True).update(is_the_chosen_one=False)
            super(Character, self).save(*args, **kwargs)
    

Andere Lösungen, die für meinen Fall nicht geeignet sind, aber durchführbar sind:

@nemocorp überschreibt die clean Methode, um eine Validierung durchzuführen. Es gibt jedoch nicht zurück, welches Modell "der Eine" ist und das ist nicht benutzerfreundlich. Trotzdem ist es ein sehr netter Ansatz, besonders wenn jemand nicht so aggressiv sein will wie @Flyte.

@saul.shanabrook und @Thierry J. würden ein benutzerdefiniertes Feld erstellen, das entweder einen anderen "is_the_one" -Eintrag in False ändern oder einen ValidationError auslösen würde. Ich zögere nur, neue Features für meine Django-Installation zu implementieren, es sei denn, es ist absolut notwendig.

@daigorocub : Verwendet Django-Signale. Ich finde es eine einzigartige Annäherung und gibt einen Hinweis, wie man Django Signale benutzt . Ich bin mir jedoch nicht sicher, ob es sich um eine - strikt gesprochen - "richtige" Verwendung von Signalen handelt, da ich dieses Verfahren nicht als Teil einer "entkoppelten Anwendung" betrachten kann.


Wann immer ich diese Aufgabe ausführen muss, überschreibe ich die Speichermethode für das Modell und lasse es überprüfen, ob bei einem anderen Modell das Flag bereits gesetzt ist (und es ausschalten).

class Character(models.Model):
    name = models.CharField(max_length=255)
    is_the_chosen_one = models.BooleanField()

    def save(self, *args, **kwargs):
        if self.is_the_chosen_one:
            try:
                temp = Character.objects.get(is_the_chosen_one=True)
                if self != temp:
                    temp.is_the_chosen_one = False
                    temp.save()
            except Character.DoesNotExist:
                pass
        super(Character, self).save(*args, **kwargs)

Und das ist alles.

def save(self, *args, **kwargs):
    if self.default_dp:
        DownloadPageOrder.objects.all().update(**{'default_dp': False})
    super(DownloadPageOrder, self).save(*args, **kwargs)

Die folgende Lösung ist ein bisschen hässlich, könnte aber funktionieren:

class MyModel(models.Model):
    is_the_chosen_one = models.NullBooleanField(default=None, unique=True)

    def save(self, *args, **kwargs):
        if self.is_the_chosen_one is False:
            self.is_the_chosen_one = None
        super(MyModel, self).save(*args, **kwargs)

Wenn Sie is_the_chosen_one auf False oder None setzen, wird immer NULL sein. Sie können NULL so oft haben, wie Sie möchten, aber Sie können nur ein True haben.


Bekomme ich Punkte für die Beantwortung meiner Frage?

Problem war, dass es sich in der Schleife befand, behoben durch:

    # is this the testimonial image, if so, unselect other images
    if self.testimonial_image is True:
        others = Photograph.objects.filter(project=self.project).filter(testimonial_image=True)
        pdb.set_trace()
        for o in others:
            if o != self: ### important line
                o.testimonial_image = False
                o.save()

class Character(models.Model):
    name = models.CharField(max_length=255)
    is_the_chosen_one = models.BooleanField()

    def clean(self):
        from django.core.exceptions import ValidationError
        c = Character.objects.filter(is_the_chosen_one__exact=True)  
        if c and self.is_the_chosen:
            raise ValidationError("The chosen one is already here! Too late")

Dadurch wurde die Validierung im Basis-Admin-Formular verfügbar gemacht


Es sieht so aus, als würde dieses Plugin Ihnen das geben, wonach Sie suchen, wenn Sie die kaskadierenden Löschungen in der tatsächlichen Datenbankstruktur widerspiegeln wollen:

http://www.redhillonrails.org/foreign_key_migrations.html

Das Format für die Verwendung in einer Migration wäre etwa so:

create_table :orders do |t|
  t.column :customer_id, :integer, :on_delete => :set_null, :on_update => :cascade
  ...
end






database django django-models django-admin django-forms