diff --git a/django/db/models/options.py b/django/db/models/options.py index d4d72c81b5..5c4a9dcad7 100644 --- a/django/db/models/options.py +++ b/django/db/models/options.py @@ -32,6 +32,7 @@ def normalize_unique_together(unique_together): tuple of two strings. Normalize it to a tuple of tuples, so that calling code can uniformly expect that. """ + unique_together = tuple(unique_together) if unique_together and not isinstance(unique_together[0], (tuple, list)): unique_together = (unique_together,) return unique_together diff --git a/tests/validation/test_unique.py b/tests/validation/test_unique.py index 14ac5a2471..b3d389e4ec 100644 --- a/tests/validation/test_unique.py +++ b/tests/validation/test_unique.py @@ -4,6 +4,8 @@ import datetime import unittest from django.core.exceptions import ValidationError +from django.db import models +from django.db.models.loading import BaseAppCache from django.test import TestCase from .models import (CustomPKModel, UniqueTogetherModel, UniqueFieldsModel, @@ -25,13 +27,45 @@ class GetUniqueCheckTests(unittest.TestCase): def test_unique_together_gets_picked_up_and_converted_to_tuple(self): m = UniqueTogetherModel() self.assertEqual( - ([(UniqueTogetherModel, ('ifield', 'cfield',)), + ([(UniqueTogetherModel, ('ifield', 'cfield')), (UniqueTogetherModel, ('ifield', 'efield')), (UniqueTogetherModel, ('id',)), ], []), m._get_unique_checks() ) + def test_unique_together_normalization(self): + """ + Test the Meta.unique_together normalization with different sorts of + objects. + """ + data = { + '2-tuple': (('foo', 'bar'), + (('foo', 'bar'),)), + 'list': (['foo', 'bar'], + (('foo', 'bar'),)), + 'already normalized': ((('foo', 'bar'), ('bar', 'baz')), + (('foo', 'bar'), ('bar', 'baz'))), + 'set': ({('foo', 'bar'), ('bar', 'baz')}, # Ref #21469 + (('foo', 'bar'), ('bar', 'baz'))), + } + + for test_name, (unique_together, normalized) in data.items(): + class M(models.Model): + foo = models.IntegerField() + bar = models.IntegerField() + baz = models.IntegerField() + + Meta = type(str('Meta'), (), { + 'unique_together': unique_together, + 'app_cache': BaseAppCache() + }) + + checks, _ = M()._get_unique_checks() + for t in normalized: + check = (M, t) + self.assertIn(check, checks) + def test_primary_key_is_considered_unique(self): m = CustomPKModel() self.assertEqual(([(CustomPKModel, ('my_pk_field',))], []), m._get_unique_checks())