Unique BooleanField value in Django?

前端 未结 13 792
清酒与你
清酒与你 2020-12-07 14:11

Suppose my models.py is like so:

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


        
相关标签:
13条回答
  • 2020-12-07 14:42

    Whenever I've needed to accomplish this task, what I've done is override the save method for the model and have it check if any other model has the flag already set (and turn it off).

    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)
    
    0 讨论(0)
  • 2020-12-07 14:43

    I'd override the save method of the model and if you've set the boolean to True, make sure all others are set to False.

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

    I tried editing the similar answer by Adam, but it was rejected for changing too much of the original answer. This way is more succinct and efficient as the checking of other entries is done in a single query.

    0 讨论(0)
  • 2020-12-07 14:43

    And that's all.

    def save(self, *args, **kwargs):
        if self.default_dp:
            DownloadPageOrder.objects.all().update(**{'default_dp': False})
        super(DownloadPageOrder, self).save(*args, **kwargs)
    
    0 讨论(0)
  • 2020-12-07 14:45

    The following solution is a little bit ugly but might work:

    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)
    

    If you set is_the_chosen_one to False or None it will be always NULL. You can have NULL as much as you want, but you can only have one True.

    0 讨论(0)
  • 2020-12-07 14:45

    Using a similar approach as Saul, but slightly different purpose:

    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
    

    This implementation will raise a ValidationError when attempting to save another record with a value of True.

    Also, I have added the unique_for argument which can be set to any other field in the model, to check true-uniqueness only for records with the same value, such as:

    class Phone(models.Model):
        user = models.ForeignKey(User)
        main = TrueUniqueBooleanField(unique_for='user', default=False)
    
    0 讨论(0)
  • 2020-12-07 14:47
    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
    

    You can use the above form for admin as well, just use

    class CharacterAdmin(admin.ModelAdmin):
        form = CharacterForm
    admin.site.register(Character, CharacterAdmin)
    
    0 讨论(0)
提交回复
热议问题