Cometchaser
Cometchaser

Reputation: 137

How to use two unique constraints in Django model?

I have a Django model for a player of a game

class Player(models.Model):
    name = models.CharField(max_length=50)
    team = models.ForeignKey('Team', on_delete=models.CASCADE, blank=True, null=True)
    game = models.ForeignKey('Game', on_delete=models.CASCADE)
    objects = GameManager()

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

I have only one unique constraint, that the name and the game are unique together.

Now, I would like to extend our page by adding registered users. So, I would add this to the model.

        user = models.ForeignKey('auth.User', on_delete=models.CASCADE, blank=True, null=True)

So, an registered user can subscribe to a game by adding a name, team, game, and his/her user. However, the user should only be able to add his account once to an game, which would be a second unique constrain

       unique_together = ('user', 'game',)

Is it possible to give in Django two unique constraints to the model? Or do I have to search in the table manually prior to saving the new entries? Or is there a better way?

Upvotes: 7

Views: 8549

Answers (3)

Sudarshan Sinha
Sudarshan Sinha

Reputation: 94

For example please refer to this :-


class Stores(models.Model):
    name = models.CharField(max_length=50)
    address = models.CharField(max_length=50)
    lat = models.FloatField()
    lng = models.FloatField()
    merchant = models.ForeignKey(Profile, on_delete=models.CASCADE, related_name="stores")

    def __str__(self):
        return "{}: {}".format(self.name, self.address)

    class Meta:
        verbose_name_plural = 'Stores'


class Items(models.Model):
    name = models.CharField(max_length=50, unique=False)
    price = models.IntegerField()
    description = models.TextField()
    stores = models.ForeignKey(Stores, on_delete=models.CASCADE, related_name="items")

    def __str__(self):
        return self.name

    class Meta:
        verbose_name_plural = "Items"
        unique_together = ('name', 'stores',)

Upvotes: 0

You should use models.UniqueConstraint (reference).

As noted in the reference:

UniqueConstraint provides more functionality than unique_together. unique_together may be deprecated in the future.

Do this:

class Meta:
    constraints = [
        models.UniqueConstraint(fields=['name', 'game'], name="unique_name_game"),
        models.UniqueConstraint(fields=['user', 'game'], name="unique_user_game"),
 ]

Upvotes: 5

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 477794

Yes, in fact by default unique_together is a collection of collections of fields that are unique together, so something like:

class Player(models.Model):
    name = models.CharField(max_length=50)
    team = models.ForeignKey('Team', on_delete=models.CASCADE, blank=True, null=True)
    game = models.ForeignKey('Game', on_delete=models.CASCADE)
    objects = GameManager()

    class Meta:
       unique_together = (('name', 'game',), ('user', 'game',))

Here we thus specify that every name, game pair is unique, and every user, game pair is unique. So it is impossible to create two Player objects for the same user and game, or for the same game and name.

It is only because a single unique_together constraint is quite common, that one can also pass a single collection of field names that should be unique together, as is written in the documentation on Options.unique_together [Django-doc]:

Sets of field names that, taken together, must be unique:

unique_together = (("driver", "restaurant"),)

This is a tuple of tuples that must be unique when considered together. It's used in the Django admin and is enforced at the database level (i.e., the appropriate UNIQUE statements are included in the CREATE TABLE statement).

For convenience, unique_together can be a single tuple when dealing with a single set of fields:

unique_together = ("driver", "restaurant")

Upvotes: 10

Related Questions