Django中唯一的BooleanField值?

假设我的models.py是这样的:

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

我只希望我的一个Character实例具有is_the_chosen_one == True而其他所有人都有is_the_chosen_one == False 。 我怎样才能最好地确保这个唯一性约束得到尊重?

考虑到尊重数据库,模型和(pipe理员)表单层级的约束的重要性的答案的顶部标记!

每当我需要完成这个任务,我所做的就是覆盖模型的保存方法,并检查是否有任何其他模型的标志已经设置(并将其closures)。

 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) 

我没有使用自定义模型清理/保存,而是创build了一个自定义字段来覆盖django.db.models.BooleanField上的pre_save方法。 如果另一个字段为True ,则不会提出错误,而是如果其他字段为True ,则将其他字段False 。 另外,如果该字段为False而没有其他字段为True ,则不会产生错误,而是将该字段保存为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) 

我会覆盖模型的保存方法,如果您已将布尔值设置为True,请确保所有其他设置为False。

 from django.db import transaction class Character(models.Model): name = models.CharField(max_length=255) is_the_chosen_one = models.BooleanField() @transaction.atomic 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) 

我尝试编辑亚当的类似答案,但由于改变太多的原始答案而被拒绝。 这种方式更简洁高效,因为其他条目的检查是在单个查询中完成的。

 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 

你也可以使用上面的表单来pipe理,只需使用

 class CharacterAdmin(admin.ModelAdmin): form = CharacterForm admin.site.register(Character, CharacterAdmin) 
 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") 

这样做使基本pipe理forms的validation

下面的解决scheme有点难看,但可能工作:

 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) 

如果将is_the_chosen_one设置为False或None,则它将始终为NULL。 您可以尽可能多的NULL,但只能有一个True。

为了达到这个目的,我发现其中一些成功地解决了同一个问题,每个问题都适用于不同的情况:

我会选:

  • @semente :尊重数据库,模型和pipe理员表单级别的约束,同时覆盖Django ORM的可能性最小。 而且可以 大概 unique_together情况下在ManyToManyFieldthrough表中使用。 (我会检查并报告)

     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) 
  • @Flyte :只需一次额外的时间点击数据库,并接受当前的条目。 清洁和优雅。

     from django.db import transaction class Character(models.Model): name = models.CharField(max_length=255) is_the_chosen_one = models.BooleanField() @transaction.atomic 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) 

其他解决scheme不适合我的情况,但可行:

@nemocorp重写clean方法来执行validation。 但是,它不报告哪个模型是“一个”,这不是用户友好的。 尽pipe如此,这是一个非常好的方法,特别是如果有人不打算像@Flyte一样积极。

@ saul.shanabrook和@Thierry J.将创build一个自定义字段,将任何其他“is_the_one”条目更改为False或引发ValidationError 。 我只是不愿意阻止我的Django安装的新function,除非它是绝对必要的。

@daigorocub :使用Django信号。 我觉得这是一个独特的方法,并给出了如何使用Django Signals的提示。 不过,我不确定这是否严格地说是“正确”使用信号,因为我不能把这个程序看作是“解耦应用程序”的一部分。

就这样。

 def save(self, *args, **kwargs): if self.default_dp: DownloadPageOrder.objects.all().update(**{'default_dp': False}) super(DownloadPageOrder, self).save(*args, **kwargs) 

我尝试了其中的一些解决scheme,并以另一种方式结束,只是为了简化代码(不必覆盖表单或保存方法)。 为了这个工作,该领域不能在它的定义是唯一的,但信号确保发生。

 # 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) 

使用类似扫罗的方法,但目的略有不同:

 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 

当尝试保存值为True的另一条logging时,此实现将引发ValidationError

此外,我还添加了unique_for参数,可以将其设置为模型中的任何其他字段,以检查具有相同值的logging的真正唯一性,例如:

 class Phone(models.Model): user = models.ForeignKey(User) main = TrueUniqueBooleanField(unique_for='user', default=False) 

我能回答我的问题吗?

问题是它发现自己在循环中,修复方法是:

  # 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()