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.
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.
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".
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With