5

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?

Alex Waygood
  • 6,304
  • 3
  • 24
  • 46
Cometchaser
  • 117
  • 2
  • 12

3 Answers3

7

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")
Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555
  • Thanks, for this answer. But with this solution I have an issue: I would like to make the linking to the user account optional. (the field can be blank) However, unique together ends into the fact that only one non-linked player can join. – Cometchaser Jan 06 '19 at 13:51
  • @Cometchaser: normally unique together will allow `NULL` to have multiple values, so I don't see the issue. Note that `blank` is more relevant to *forms* than to the model itself. It is sufficient to say `null=True` to allow `NULL` values. – Willem Van Onsem Jan 06 '19 at 13:56
  • Check this JIC: https://stackoverflow.com/a/62774209/1312850 – Gonzalo Oct 15 '21 at 13:33
4

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"),
 ]
djangomachine
  • 619
  • 4
  • 15
  • Hi! Could you maybe edit your answer to add an explanation of how/why this improves on the accepted answer that has already been there for a while? – Alex Waygood Sep 18 '21 at 08:34
  • You should give each UniqueConstraint a different name otherwise this example would fail, e.g. `unique_name_game` and `unique_user_game`. – Melipone Jun 06 '22 at 16:10
0

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