76๐
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)
50๐
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.
- [Django]-On Heroku, is there danger in a Django syncdb / South migrate after the instance has already restarted with changed model code?
- [Django]-Strings won't be translated in Django using format function available in Python 2.7
- [Django]-Function decorators with parameters on a class based view in Django
43๐
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')
]
- [Django]-How do I migrate a model out of one django app and into a new one?
- [Django]-Django auto_now and auto_now_add
- [Django]-Django get objects not referenced by foreign key
31๐
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)
- [Django]-ImportError: Failed to import test module:
- [Django]-RuntimeError: Model class django.contrib.sites.models.Site doesn't declare an explicit app_label and isn't in an application in INSTALLED_APPS
- [Django]-Django TypeError: get() got multiple values for keyword argument 'invoice_id'
12๐
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.
- [Django]-Mixin common fields between serializers in Django Rest Framework
- [Django]-UnicodeDecodeError: 'ascii' codec can't decode byte 0xd1 in position 2: ordinal not in range(128)
- [Django]-Function decorators with parameters on a class based view in Django
12๐
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:
-
@s*m*nte: 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 aManyToManyField
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, forBooleanField(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".
- [Django]-Django โ Rotating File Handler stuck when file is equal to maxBytes
- [Django]-How to customize activate_url on django-allauth?
- [Django]-Having Django serve downloadable files
7๐
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)
- [Django]-Django storages: Import Error โ no module named storages
- [Django]-Easiest way to rename a model using Django/South?
- [Django]-Gunicorn.errors.HaltServer: <HaltServer 'Worker failed to boot.' 3> django
6๐
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)
- [Django]-Copy a database column into another in Django
- [Django]-How to filter empty or NULL names in a QuerySet?
- [Django]-How to do math in a Django template?
5๐
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")
Doing this made the validation available in the basic admin form
- [Django]-How can I get MINIO access and secret key?
- [Django]-Django: Fat models and skinny controllers?
- [Django]-Django development server reload takes too long
3๐
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)
- [Django]-How to change field name in Django REST Framework
- [Django]-Make the first letter uppercase inside a django template
- [Django]-Django: why i can't get the tracebacks (in case of error) when i run LiveServerTestCase tests?
2๐
Do I get points for answering my question?
problem was it was finding itself in the loop, fixed by:
# is this the testimonial image, if so, unselect other images
if self.testimonial_image is True:
others = Photograph.objects.filter(project=self.project).filter(testimonial_image=True)
pdb.set_trace()
for o in others:
if o != self: ### important line
o.testimonial_image = False
o.save()
- [Django]-When saving, how can you check if a field has changed?
- [Django]-How to get Request.User in Django-Rest-Framework serializer?
- [Django]-How do I make many-to-many field optional in Django?
2๐
I tried some of these solutions, and ended up with another one, just for the sake of code shortness (donโt have to override forms or save method).
For this to work, the field canโt be unique in itโs definition but the signal makes sure that happens.
# making default_number True unique
@receiver(post_save, sender=Character)
def unique_is_the_chosen_one(sender, instance, **kwargs):
if instance.is_the_chosen_one:
Character.objects.all().exclude(pk=instance.pk).update(is_the_chosen_one=False)
- [Django]-Removing 'Sites' from Django admin page
- [Django]-How do I migrate a model out of one django app and into a new one?
- [Django]-Django 1.7 โ App 'your_app_name' does not have migrations
1๐
2020 update to make things less complicated for beginners:
class Character(models.Model):
name = models.CharField(max_length=255)
is_the_chosen_one = models.BooleanField(blank=False, null=False, default=False)
def save(self):
if self.is_the_chosen_one == True:
items = Character.objects.filter(is_the_chosen_one = True)
for x in items:
x.is_the_chosen_one = False
x.save()
super().save()
Of course, if you want the unique boolean to be False, you would just swap every instance of True with False and vice versa.
- [Django]-Using Python's os.path, how do I go up one directory?
- [Django]-Django QuerySet order
- [Django]-Class has no objects member
1๐
When implementing a solution which overwrites model.save()*, I ran into the issue of Django Admin raising an error before hitting model.save(). The cause seems to be Admin calling model.clean() (or perhaps model.full_clean(), I didnโt investigate too carefully) before calling model.save(). model.clean() in turn calls model.validate_unique() which raises a ValidationError before my custom save method can take care of the unique violation. To solve this I overwrote model.validate_unique() as follows:
def validate_unique(self, exclude=None):
try:
super().validate_unique(exclude=exclude)
except ValidationError as e:
validation_errors = e.error_dict
try:
list_validation_errors = validation_errors["is_the_chosen_one"]
for validation_error in list_validation_errors:
if validation_error.code == "unique":
list_validation_errors.remove(validation_error)
if not list_validation_errors:
validation_errors.pop(key)
except KeyError:
continue
if e.error_dict:
raise e
* the same would be true for a signal solution using pre_save, as pre_save is also not sent before .validate_unique is called
- [Django]-How to perform OR condition in django queryset?
- [Django]-How do I deploy Django on AWS?
- [Django]-CSV new-line character seen in unquoted field error