Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to validate uniqueness constraint across foreign key (django)

I have the following (simplified) data structure:

Site
-> Zone
   -> Room
      -> name

I want the name of each Room to be unique for each Site.

I know that if I just wanted uniqueness for each Zone, I could do:

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

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

But I can't do what I really want, which is:

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

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

I tried adding a validate_unique method, as suggested by this question:

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)

but I must be misunderstanding the point/implementation of validate_unique, because it is not being called when I save a Room object.

What would be the correct way to implement this check?

like image 775
sapi Avatar asked Jan 23 '13 00:01

sapi


3 Answers

Methods are not called on their own when saving the model. One way to do this is to have a custom save method that calls the validate_unique method when a model is saved:

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)
like image 180
Seperman Avatar answered Nov 20 '22 06:11

Seperman


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

I needed to make similar program. It worked.

like image 24
aki33524 Avatar answered Nov 20 '22 05:11

aki33524


The Django Validation objects documentation explains the steps involved in validation including this snippet

Note that full_clean() will not be called automatically when you call your model's save() method

If the model instance is being created as a result of using a ModelForm, then validation will occur when the form is validated.

There are a some options in how you handle validation.

  1. Call the model instance's full_clean() manually before saving.
  2. Override the save() method of the model to perform validation on every save. You can choose how much validation should occur here, whether you want full validation or only uniqueness checks.

    class Room(models.Model):
        def save(self, *args, **kwargs):
            self.full_clean()
            super(Room, self).save(*args, **kwargs)
    
  3. Use a Django pre_save signal handler which will automatically perform validation before a save. This provides a very simple way to add validation on exisiting models without any additional model code.

    # 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')
    
like image 6
Austin Phillips Avatar answered Nov 20 '22 06:11

Austin Phillips