104 votes

Valeur unique de BooleanField dans Django?

Supposons que mon models.py ressemble à ceci:

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

Je souhaite qu'une seule de mes instances Character ait is_the_chosen_one == True et que toutes les autres aient is_the_chosen_one == False . Comment puis-je assurer au mieux le respect de cette contrainte d'unicité?

Les meilleures notes aux réponses qui tiennent compte de l'importance de respecter la contrainte aux niveaux de la base de données, du modèle et du formulaire (admin)!

75voto

Adam Points 2544

Chaque fois que j'ai eu besoin d'accomplir cette tâche, ce que j'ai fait est de remplacer la méthode de sauvegarde pour le modèle et de le faire vérifier si un autre modèle a déjà l'indicateur défini (et le désactiver).

 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)
 

49voto

Flyte Points 632

Je remplacerais la méthode de sauvegarde du modèle et si vous avez défini le booléen sur True, assurez-vous que tous les autres sont définis sur False.

 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:
            Character.objects.filter(
                is_the_chosen_one=True).update(is_the_chosen_one=False)
        super(Character, self).save(*args, **kwargs)
 

J'ai essayé de modifier la réponse similaire d'Adam, mais elle a été rejetée pour avoir trop modifié la réponse d'origine. Cette méthode est plus succincte et efficace, car la vérification des autres entrées est effectuée dans une requête unique.

30voto

saul.shanabrook Points 625

Au lieu d'utiliser des modèles personnalisés de nettoyage/d'économie, j'ai créé un champ personnalisé en substituant l' pre_save méthode django.db.models.BooleanField. Au lieu de lever une erreur si un autre champ a été True, j'ai fait tous les autres champs False si elle a été True. Aussi, au lieu de lever une erreur si le champ a été False et qu'aucun autre domaine a été, True, je l'ai enregistré le champ True

fields.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)

6voto

shadfc Points 1275
 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
 

Vous pouvez aussi utiliser le formulaire ci-dessus pour l’administrateur, utilisez simplement

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

4voto

nemocorp Points 21
 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")
 

Cela a rendu la validation disponible dans le formulaire d'administration de base

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X