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


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

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')

我尝试添加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对象时它没有被调用。

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


12317
2018-01-23 00:36


起源



答案:


保存模型时,不会自行调用方法。 一种方法是使用自定义保存方法,在保存模型时调用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)

8
2018-01-23 01:25



添加save方法确实解决了我的问题,谢谢。我很好奇,实际上自动调用validate_unique的时候。文档读给我看,好像它(和clean_x)是在保存模型实例时自动调用的,但这实际上只是在由于表单数据而发生编辑/保存时? - sapi
是的,这些干净的方法在ModelForm中调用。因此,在保存模型之前会进行自动验证。在您的情况下,您在保存模型时也会这样做。因此,即使用户在终端中保存模型而不是在表单中输入数据,也会进行验证。 - Seperman
我猜是有道理的。当我没有计划展示任何形式时,没有那么有用,但是我不喜欢:P - sapi
不是真的。 Django自动执行 validate_unique() 在打电话给 save() 方法。 - dragostis
@dragostis:我没有看到 validate_unique() 打电话给 Model.save() 方法。所以,不,它在保存模型时不会自动执行:) - José L. Patiño


答案:


保存模型时,不会自行调用方法。 一种方法是使用自定义保存方法,在保存模型时调用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)

8
2018-01-23 01:25



添加save方法确实解决了我的问题,谢谢。我很好奇,实际上自动调用validate_unique的时候。文档读给我看,好像它(和clean_x)是在保存模型实例时自动调用的,但这实际上只是在由于表单数据而发生编辑/保存时? - sapi
是的,这些干净的方法在ModelForm中调用。因此,在保存模型之前会进行自动验证。在您的情况下,您在保存模型时也会这样做。因此,即使用户在终端中保存模型而不是在表单中输入数据,也会进行验证。 - Seperman
我猜是有道理的。当我没有计划展示任何形式时,没有那么有用,但是我不喜欢:P - sapi
不是真的。 Django自动执行 validate_unique() 在打电话给 save() 方法。 - dragostis
@dragostis:我没有看到 validate_unique() 打电话给 Model.save() 方法。所以,不,它在保存模型时不会自动执行:) - José L. Patiño


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',]})

我需要制作类似的节目。有效。


4
2018-02-27 11:33





Django 验证对象 文档解释了验证中涉及的步骤,包括此代码段

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

如果由于使用a而创建模型实例 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')
    

3
2018-01-23 04:11