假设我的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
。如何才能最好地确保这种唯一性约束得到尊重?
考虑到在数据库,模型和(管理员)表单级别遵守约束的重要性的答案的最高分标记!
答案 0 :(得分:51)
每当我需要完成此任务时,我所做的就是覆盖模型的save方法,并检查是否有其他模型已经设置了标志(并将其关闭)。
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)
答案 1 :(得分:24)
我没有使用自定义模型清理/保存,而是在pre_save
上创建了custom field覆盖django.db.models.BooleanField
方法。如果其他字段为True
,则不会引发错误,而是将所有其他字段False
设为True
。如果该字段为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)
答案 2 :(得分:19)
我会覆盖模型的save方法,如果你将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)
我尝试编辑Adam的类似答案,但是因为改变了原来的答案太多而被拒绝了。这种方式更简洁,更有效,因为在单个查询中检查其他条目。
答案 3 :(得分:8)
以下解决方案有点难看但可能会有效:
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。
答案 4 :(得分:7)
试图在这里达到目的,我发现其中一些问题成功地解决了同一个问题,并且每个问题都适用于不同的情况:
我会选择:
@semente:尊重数据库,模型和管理表单级别的约束,同时尽可能地覆盖Django ORM。此外,它可以可能在through
情况下ManyToManyField
的{{1}}表中使用。 (我会检查并报告)
unique_together
@Flyte:仅在一个额外时间点击数据库并接受当前条目作为所选条目。干净而优雅。
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)
其他解决方案不适合我的情况但可行:
@nemocorp会覆盖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)
方法以执行验证。但是,它不报告哪个模型是“那个”,这不是用户友好的。尽管如此,这是一个非常好的方法,特别是如果有人不打算像@Flyte那样具有攻击性。
@saul.shanabrook和@Thierry J.会创建一个自定义字段,该字段可以将任何其他“is_the_one”条目更改为clean
或引发False
。我只是不愿意为我的Django安装增加新功能,除非它是非常必要的。
@daigorocub:使用Django信号。我发现它是一种独特的方法,并提供了如何使用Django Signals的提示。但是我不确定这是否严格地说 - “正确”使用信号,因为我不能将此程序视为“解耦应用程序”的一部分。
答案 5 :(得分:6)
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
您也可以使用上述表单进行管理,只需使用
即可class CharacterAdmin(admin.ModelAdmin):
form = CharacterForm
admin.site.register(Character, CharacterAdmin)
答案 6 :(得分:4)
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")
这样做可以在基本管理表格中提供验证
答案 7 :(得分:4)
将这种约束添加到模型中比较简单
在Django 2.2之后。您可以直接使用UniqueConstraint.condition
。 Django Docs
只需像这样覆盖模型class Meta
:
class Meta:
constraints = [
UniqueConstraint(fields=['is_the_chosen_one'], condition=Q(is_the_chosen_one=True), name='unique_is_the_chosen_one')
]
答案 8 :(得分:2)
就是这样。
def save(self, *args, **kwargs):
if self.default_dp:
DownloadPageOrder.objects.all().update(**{'default_dp': False})
super(DownloadPageOrder, self).save(*args, **kwargs)
答案 9 :(得分:1)
我能否回答我的问题?
问题是它是在循环中发现自己,修正:
# 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()
答案 10 :(得分:1)
我尝试了其中一些解决方案,最后只使用了另一个解决方案,只是为了代码简洁(不必覆盖表单或保存方法)。 为了使其工作,该字段在其定义中不能是唯一的,但信号确保发生这种情况。
# 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)
答案 11 :(得分:1)
使用与Saul类似的方法,但目的略有不同:
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的记录时,此实现将引发ValidationError
。
另外,我添加了unique_for
参数,可以设置为模型中的任何其他字段,仅检查具有相同值的记录的真实唯一性,例如:
class Phone(models.Model):
user = models.ForeignKey(User)
main = TrueUniqueBooleanField(unique_for='user', default=False)
答案 12 :(得分:0)
2020年更新,使初学者的事情不再那么复杂:
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()
当然,如果您希望唯一的布尔值为False,则只需将True的每个实例与False交换,反之亦然。