Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unique BooleanField value in Django?

People also ask

What is BooleanField Django?

BooleanField is a true/false field. It is like a bool field in C/C+++. The default form widget for this field is CheckboxInput, or NullBooleanSelect if null=True.

What is unique constraint in Django?

UniqueConstraint. include. New in Django 3.2. A list or tuple of the names of the fields to be included in the covering unique index as non-key columns.

What is AutoField in Django?

AutoField. An IntegerField that automatically increments according to available IDs. You usually won't need to use this directly; a primary key field will automatically be added to your model if you don't specify otherwise.


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)

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.


Instead of using custom model cleaning/saving, I created a custom field overriding the pre_save method on django.db.models.BooleanField. Instead of raising an error if another field was True, I made all other fields False if it was True. Also instead of raising an error if the field was False and no other field was True, I saved it the field as 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)

It is simpler to add this kind of constraint to your model after Django version 2.2. You can directly use UniqueConstraint.condition. Django Docs

Just override your models class Meta like this:

class Meta:
    constraints = [
        UniqueConstraint(fields=['is_the_chosen_one'], condition=Q(is_the_chosen_one=True), name='unique_is_the_chosen_one')
    ]

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.


Trying to make ends meet with the answers here, I find that some of them address the same issue successfully and each one is suitable in different situations:

I would choose:

  • @semente: Respects the constraint at the database, model and admin form levels while it overrides Django ORM the least possible. Moreover it can be used inside a through table of a ManyToManyField in aunique_together situation.

      class MyModel(models.Model):
          is_the_chosen_one = models.BooleanField(null=True, 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)
    

    Update: NullBooleanField will be deprecated by Django-4.0, for BooleanField(null=True).

  • @Ellis Percival: Hits the database only one extra time and accepts the current entry as the chosen one. Clean and elegant.

      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:
              # The use of return is explained in the comments
              return super(Character, self).save(*args, **kwargs)  
          with transaction.atomic():
              Character.objects.filter(
                  is_the_chosen_one=True).update(is_the_chosen_one=False)
              # The use of return is explained in the comments
              return super(Character, self).save(*args, **kwargs)  
    

Other solutions not suitable for my case but viable:

@nemocorp is overriding the clean method to perform a validation. However, it does not report back which model is "the one" and this is not user friendly. Despite that, it is a very nice approach especially if someone does not intend to be as aggressive as @Flyte.

@saul.shanabrook and @Thierry J. would create a custom field which would either change any other "is_the_one" entry to False or raise a ValidationError. I am just reluctant to impement new features to my Django installation unless it is absoletuly necessary.

@daigorocub: Uses Django signals. I find it a unique approach and gives a hint of how to use Django Signals. However I am not sure whether this is a -strictly speaking- "proper" use of signals since I cannot consider this procedure as part of a "decoupled application".