Django中唯一的BooleanField值?

sam*_*per 76 database django django-models django-forms django-admin

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

class Character(models.Model):
    name = models.CharField(max_length=255)
    is_the_chosen_one = models.BooleanField()
Run Code Online (Sandbox Code Playgroud)

我只想要我的一个Character实例is_the_chosen_one == True和其他所有实例is_the_chosen_one == False.如何才能最好地确保这种唯一性约束得到尊重?

考虑到尊重数据库,模型和(管理员)表单级别约束的重要性的答案的最高分!

Ada*_*dam 55

每当我需要完成这个任务时,我所做的就是覆盖模型的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)
Run Code Online (Sandbox Code Playgroud)

  • 我尝试编辑以消除try/except的需要并使进程更有效但它被拒绝了..而不是`get()`对象的对象,然后`save()`再来一次,你只需要过滤和更新,它只生成一个SQL查询并帮助保持数据库一致:`if self.is_the_chosen_one:`<newline>`Character.objects.filter(is_the_chosen_one = True).update(is_the_chosen_one = False)`<newline>`超(字符,自我).save(*args,**kwargs)` (13认同)
  • 我试图编辑这个以将`save(self)`更改为`save(self,*args,**kwargs)`但编辑被拒绝了.是否有任何评论者需要时间来解释原因 - 因为这似乎与Django最佳实践一致. (8认同)
  • 我只是将'def save(self):'改为:'def save(self,*args,**kwargs):' (3认同)
  • 我不能建议任何更好的方法来完成该任务,但我想说的是,如果您正在运行一个 Web 应用程序,您可能会在同一时刻向端点发送一些请求,请不要相信 save 或 clean 方法。您仍然必须在数据库级别实现更安全的方法。 (2认同)
  • 下面有一个更好的答案。埃利斯·珀西瓦尔(Ellis Percival)的回答使用了“transaction.atomic”,这在这里很重要。使用单个查询也更有效。 (2认同)

Ell*_*val 26

我会覆盖模型的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()

    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)
Run Code Online (Sandbox Code Playgroud)

我尝试编辑Adam的类似答案,但因为改变了原来的答案而被拒绝了.这种方式更简洁,更有效,因为在单个查询中完成对其他条目的检查.

  • 我认为这是最好的答案,但我建议将`save`包装成`@ transaction.atomic`事务.因为可能会删除所有标记,但是保存失败并且最终会找到未选择的所有字符. (7认同)
  • 最好的解决方案! (2认同)

sau*_*ook 25

我没有使用自定义模型清理/保存,而是创建了一个覆盖方法的自定义字段.反而提高了一个错误,如果另一场是的,我所做的所有其他领域,如果它是.如果字段是并且没有其他字段,则不是引发错误,而是将字段保存为pre_savedjango.db.models.BooleanFieldTrueFalseTrueFalseTrueTrue

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"])
Run Code Online (Sandbox Code Playgroud)

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)
Run Code Online (Sandbox Code Playgroud)

  • 这看起来比其他方法更干净 (2认同)
  • 我也喜欢这个解决方案,尽管在将模型UniqueBoolean设置为True的情况下将objects.update设置为False似乎有潜在危险.如果UniqueBooleanField采用可选参数来指示其他对象是否应该设置为False或者是否应该引发错误(另一个明智的替代方案),那就更好了.另外,鉴于您在elif中的注释,您要将属性设置为true,我认为您应该将`Return True`更改为`setattr(model_instance,self.attname,True)` (2认同)
  • UniqueBooleanField并不是唯一的,因为您可以拥有任意数量的False值.不知道更好的名字是什么...... OneTrueBooleanField?我真正想要的是能够将它与外键结合使用,这样我就可以拥有一个每个关系只允许为True的BooleanField(例如,一个CreditCard有一个"主"字段和一个FK到用户和每次使用时,用户/主要组合为True.对于那种情况,我认为亚当的回答压倒性保存对我来说会更直截了当. (2认同)

Cem*_*nel 14

在 Django 2.2 版之后,将这种约束添加到您的模型中会更简单。可以直接使用UniqueConstraint.conditionDjango 文档

只需class Meta像这样覆盖您的模型:

class Meta:
    constraints = [
        UniqueConstraint(fields=['is_the_chosen_one'], condition=Q(is_the_chosen_one=True), name='unique_is_the_chosen_one')
    ]
Run Code Online (Sandbox Code Playgroud)

  • 这简单又简洁。伟大的!谢谢。 (2认同)

sem*_*nte 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)
Run Code Online (Sandbox Code Playgroud)

如果将is_the_chosen_one设置为False或None,则它将始终为NULL.您可以根据需要使用NULL,但只能有一个True.


rar*_*iru 8

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

我会选:

  • @semente:尊重数据库,模型和管理表单级别的约束,同时尽可能地覆盖Django ORM.而且它可以大概一个内部使用 through的表ManyToManyFieldunique_together情况.(我会检查并报告)

    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)
    
    Run Code Online (Sandbox Code Playgroud)
  • @Flyte:仅在一个额外时间点击数据库并接受当前条目作为所选条目.干净优雅.

    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)  
    
    Run Code Online (Sandbox Code Playgroud)

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

@nemocorp正在覆盖clean执行验证的方法.但是,它不报告哪个模型是"那个",这不是用户友好的.尽管如此,这是一个非常好的方法,特别是如果有人不打算像@Flyte那样具有攻击性.

@ saul.shanabrook@Thierry J.会创建一个自定义字段,可以将任何其他"is_the_one"条目更改为False或者引发一个ValidationError.我只是不愿意为我的Django安装提供新功能,除非它是非常必要的.

@daigorocub:使用Django信号.我发现它是一种独特的方法,并提供了如何使用Django信号的提示.但是我不确定这是否严格地说 - "正确"使用信号,因为我不能将此过程视为"解耦应用程序"的一部分.


sha*_*dfc 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
Run Code Online (Sandbox Code Playgroud)

你也可以使用上面的表格来管理,只需使用

class CharacterAdmin(admin.ModelAdmin):
    form = CharacterForm
admin.site.register(Character, CharacterAdmin)
Run Code Online (Sandbox Code Playgroud)


小智 6

就这样。

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


小智 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")
Run Code Online (Sandbox Code Playgroud)

这样做可以在基本管理表单中进行验证