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
情况下在ManyToManyField
的through
表中使用。(我会检查并报告)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()