如何跨外键验证唯一性约束(django)

时间:2013-01-23 00:36:47

标签: python django django-models

我有以下(简化)数据结构:

Site
-> Zone
   -> Room
      -> name

我希望每个房间的名称对于每个网站都是唯一的。

我知道如果我只想要每个区域的唯一性,我可以这样做:

class Room(models.Model):
    zone = models.ForeignKey(Zone)
    name = models.CharField(max_length=255) 

    class Meta:
        unique_together = ('name', 'zone')

但我不能做我真正想要的事情,那就是:

class Room(models.Model):
    zone = models.ForeignKey(Zone)
    name = models.CharField(max_length=255) 

    class Meta:
        unique_together = ('name', 'zone__site')

我尝试按照this question的建议添加了validate_unique方法:

class Room(models.Model):
    zone = models.ForeignKey(Zone)
    name = models.CharField(max_length=255) 

    def validate_unique(self, exclude=None):
        qs = Room.objects.filter(name=self.name)
        if qs.filter(zone__site=self.zone__site).exists():
            raise ValidationError('Name must be unique per site')

        models.Model.validate_unique(self, exclude=exclude)

但我必须误解validate_unique的要点/实现,因为在保存Room对象时没有调用它。

实施此检查的正确方法是什么?

3 个答案:

答案 0 :(得分:9)

保存模型时不会自行调用方法。 一种方法是使用自定义保存方法,在保存模型时调用validate_unique方法:

class Room(models.Model):
    zone = models.ForeignKey(Zone)
    name = models.CharField(max_length=255) 

    def validate_unique(self, exclude=None):
        qs = Room.objects.filter(name=self.name)
        if qs.filter(zone__site=self.zone__site).exists():
            raise ValidationError('Name must be unique per site')


    def save(self, *args, **kwargs):

        self.validate_unique()

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

答案 1 :(得分:5)

class Room(models.Model):
    zone = models.ForeignKey(Zone)
    name = models.CharField(max_length=255)

    def validate_unique(self, *args, **kwargs):
        super(Room, self).validate_unique(*args, **kwargs)
        qs = Room.objects.filter(name=self.name)
        if qs.filter(zone__site=self.zone__site).exists():
            raise ValidationError({'name':['Name must be unique per site',]})

我需要制作类似的节目。它奏效了。

答案 2 :(得分:4)

Django Validation objects文档解释了验证所涉及的步骤,包括此代码段

  

请注意,调用模型的save()方法时不会自动调用full_clean()

如果使用ModelForm创建模型实例,则在验证表单时将进行验证。

您可以选择如何处理验证。

  1. 在保存之前手动调用模型实例的full_clean()
  2. 覆盖模型的save()方法,以便在每次保存时执行验证。您可以选择在此处进行多少验证,无论您是要完整验证还是仅进行唯一性检查。

    class Room(models.Model):
        def save(self, *args, **kwargs):
            self.full_clean()
            super(Room, self).save(*args, **kwargs)
    
  3. 使用Django pre_save信号处理程序,它将在保存之前自动执行验证。这提供了一种在没有任何其他模型代码的情况下在现有模型上添加验证的非常简单的方法。

    # In your models.py
    from django.db.models.signals import pre_save
    
    def validate_model_signal_handler(sender, **kwargs):
        """
        Signal handler to validate a model before it is saved to database.
        """
        # Ignore raw saves.
        if not kwargs.get('raw', False):
            kwargs['instance'].full_clean()
    
    
    pre_save.connect(validate_model_signal_handler,
      sender=Room,
      dispatch_uid='validate_model_room')