67
votes

I have a situation where I want to use the Meta options of unique_together to enforce a certain rule, here's the intermediary model:

class UserProfileExtension(models.Model):
    extension = models.ForeignKey(Extension, unique=False)
    userprofile = models.ForeignKey(UserProfile, unique=False)
    user = models.ForeignKey(User, unique=False)  

    class Meta:
        unique_together = (("userprofile", "extension"),
                           ("user", "extension"),
                           # How can I enforce UserProfile's Client 
                           # and Extension to be unique? This obviously
                           # doesn't work, but is this idea possible without
                           # creating another FK in my intermediary model 
                           ("userprofile__client", "extension"))

and here's UserProfile:

class UserProfile(models.Model):
    user = models.ForeignKey(User, unique=True)
    client = models.ForeignKey(Client)

Thanks.

6

6 Answers

81
votes

You can't.

The unique_together clause is directly translated to the SQL unique index. And you can only set those on columns of a single table, not a combination of several tables.

You can add validation for it yourself though, simply overwrite the validate_unique method and add this validation to it.

Docs: http://docs.djangoproject.com/en/dev/ref/models/instances/#django.db.models.Model.validate_unique

11
votes

My solution was to use Django's get_or_create. By using get_or_create, a useless get will occur if the row already exists in the database, and the row will be created if it does not exist.

Example:

 
extension = Extension.objects.get(pk=someExtensionPK)
userProfile = UserProfile.objects.get(pk=someUserProfilePK)
UserProfileExtension.objects.get_or_create(extension=extension, userprofile=userProfile)
10
votes

My 2 cents, complementing the accepted response from @Wolph

You can add validation for it yourself though, simply overwrite the validate_unique method and add this validation to it.

This is a working example code someone could find usefull.

from django.core.exceptions import ValidationError


class MyModel(models.Model):

    fk = models.ForeignKey(AnotherModel, on_delete=models.CASCADE)

    my_field = models.CharField(...)  # whatever

    def validate_unique(self, *args, **kwargs):
        super(MyModel, self).validate_unique(*args, **kwargs)

        if self.__class__.objects.\
                filter(fk=self.fk, my_field=self.my_field).\
                exists():
            raise ValidationError(
                message='MyModel with this (fk, my_field) already exists.',
                code='unique_together',
            )
2
votes

You need to call Models.full_clean() method to call validate_unique for foreignKey. You can override save() to call this

class UserProfileExtension(models.Model):
    extension = models.ForeignKey(Extension, unique=False)
    userprofile = models.ForeignKey(UserProfile, unique=False)
    user = models.ForeignKey(User, unique=False)  


    def save(self, *args, **kwargs):
        self.full_clean()
        super().save(*args, **kwargs)

    class Meta:
        unique_together = (("userprofile", "extension"),
                       ("user", "extension"),
                       # How can I enforce UserProfile's Client 
                       # and Extension to be unique? This obviously
                       # doesn't work, but is this idea possible without
                       # creating another FK in my intermediary model 
                       ("userprofile__client", "extension"))
1
votes
from django.core.exceptions import ValidationError

.....

class UserProfileExtension(models.Model):
    extension = models.ForeignKey(Extension, unique=False)
    userprofile = models.ForeignKey(UserProfile, unique=False)
    user = models.ForeignKey(User, unique=False)  

    def validate_unique(self, *args, **kwargs):
        super(UserProfileExtension, self).validate_unique(*args, **kwargs)
        query = UserProfileExtension.objects.filter(extension=self.extension)
        if query.filter(userprofile__client=self.userprofile.client).exists():
            raise ValidationError({'extension':['Extension already exits for userprofile__client',]})

The first query is to filter all records in UserProfileExtension model which has the same extension we are putting in the current record.

Then we filter the query returned to find if it already contains userprofile__client which we are passing in the current record.