如何在Django中只保证一个布尔值对于一个具有特定ForeignKey的行?

时间:2012-08-29 01:12:43

标签: python database django foreign-keys race-condition

想象一下这样的事情:

class User(models.Model):
     name = CharField(max_length=80)

class TelephoneNumber(models.Model):
     phone_number = CharField(max_length=10)
     user = ForeignKey(User)
     is_default_number = BooleanField

假设我想保证每个用户只有一个默认电话号码。我们可以拥有is_default_number == False的任意数量,但对于每个user,应该只有一个 is_default_number == True

有没有办法通过Django在数据库级别强制执行此操作?我知道纯Python中的一个简单解决方案就是:

class TelephoneNumber(models.Model):
    phone_number = CharField(max_length=10)
    user = ForeignKey(User)
    is_default_number = BooleanField

    def save(self, *args, **kwargs):
         other_defaults_same_user = TelephoneNumber.objects.filter(
              user=self.user,
              is_default_number=True
         ).exclude(
              pk=self.pk
         )

         if self.is_default_number and other_defaults_same_user.exists():
              raise ValidationError("Can't have two phone numbers as default"
                                    " for one user.")

         super(TelephoneNumber, self).save(*args, **kwargs)

但这很容易受到竞争条件错误的影响,而且我不喜欢每次保存时我们必须多做一次额外的读取(尽管在某些时候可能需要额外的读取)。有没有办法在Django中实现这一目标?如果我们必须依赖于数据库的答案,我会有兴趣看到答案,但显然一个后端独立的答案是最好的。

2 个答案:

答案 0 :(得分:4)

为什么不在用户模型上将default_phone_number设为外键?

答案 1 :(得分:0)

使用模型字段验证 - https://docs.djangoproject.com/en/dev/ref/models/instances/#validating-objects

if self.is_default_number and TelephoneNumber.objects.filter(user=self.user, is_default_number=True).exclude(id=self.id).exists():
#throw ValidationError

我认为你不能避免额外阅读。