Как проверить ограничение уникальности по внешнему ключу (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.

Каким будет правильный способ реализовать эту проверку?

Ответ 1

Методы не вызывают самостоятельно при сохранении модели. Один из способов сделать это - создать собственный метод сохранения, который вызывает метод 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)

Ответ 2

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

Мне нужно было сделать подобную программу. Это сработало.

Ответ 3

Документация Django Validation описывает шаги, связанные с проверкой, включая этот фрагмент

Обратите внимание, что full_clean() не будет вызываться автоматически, когда вы вызываете метод сохранения модели()

Если экземпляр модели создается в результате использования ModelForm, тогда проверка будет выполняться, когда форма будет проверена.

Есть несколько вариантов того, как вы обрабатываете проверку.

  • Перед сохранением вызовите экземпляр модели full_clean() вручную.
  • Переопределите метод save() модели для выполнения проверки при каждом сохранении. Вы можете выбрать, какая валидация должна произойти здесь, хотите ли вы полной проверки или только проверки уникальности.

    class Room(models.Model):
        def save(self, *args, **kwargs):
            self.full_clean()
            super(Room, self).save(*args, **kwargs)
    
  • Используйте обработчик сигнала 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')