[database] Unique BooleanField Wert in Django?



4 Answers

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

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!




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)



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)



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.




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:
            qs = Character.objects.filter(is_the_chosen_one=True)
            if self.pk:
                qs = qs.exclude(pk=self.pk)
            if qs.count() != 0:
                # choose ONE of the next two lines
                self.is_the_chosen_one = False # keep the existing "chosen one"
                #qs.update(is_the_chosen_one=False) # make this obj "the chosen one"
        super(Character, self).save(*args, **kwargs)

class CharacterForm(forms.ModelForm):
    class Meta:
        model = Character

    # if you want to use the new obj as the chosen one and remove others, then
    # be sure to use the second line in the model save() above and DO NOT USE
    # the following clean method
    def clean_is_the_chosen_one(self):
        chosen = self.cleaned_data.get('is_the_chosen_one')
        if chosen:
            qs = Character.objects.filter(is_the_chosen_one=True)
            if self.instance.pk:
                qs = qs.exclude(pk=self.instance.pk)
            if qs.count() != 0:
                raise forms.ValidationError("A Chosen One already exists! You will pay for your insolence!")
        return chosen

Sie können das obige Formular auch für den Admin verwenden, einfach verwenden

class CharacterAdmin(admin.ModelAdmin):
    form = CharacterForm
admin.site.register(Character, CharacterAdmin)





Related