Ticket #12881: 12881.diff

File 12881.diff, 22.9 KB (added by Karen Tracey, 14 years ago)
  • django/db/models/base.py

     
    682682        if exclude is None:
    683683            exclude = []
    684684        unique_checks = []
    685         for check in self._meta.unique_together:
    686             for name in check:
    687                 # If this is an excluded field, don't add this check.
    688                 if name in exclude:
    689                     break
    690             else:
    691                 unique_checks.append(tuple(check))
    692685
     686        unique_togethers = [(self.__class__, self._meta.unique_together)]
     687        for parent_class in self._meta.parents.keys():
     688            if parent_class._meta.unique_together:
     689                unique_togethers.append((parent_class, parent_class._meta.unique_together))
     690
     691        for model_class, unique_together in unique_togethers:
     692            for check in unique_together:
     693                for name in check:
     694                    # If this is an excluded field, don't add this check.
     695                    if name in exclude:
     696                        break
     697                else:
     698                    unique_checks.append((model_class, tuple(check)))
     699
    693700        # These are checks for the unique_for_<date/year/month>.
    694701        date_checks = []
    695702
    696703        # Gather a list of checks for fields declared as unique and add them to
    697704        # the list of checks.
    698         for f in self._meta.fields:
    699             name = f.name
    700             if name in exclude:
    701                 continue
    702             if f.unique:
    703                 unique_checks.append((name,))
    704             if f.unique_for_date:
    705                 date_checks.append(('date', name, f.unique_for_date))
    706             if f.unique_for_year:
    707                 date_checks.append(('year', name, f.unique_for_year))
    708             if f.unique_for_month:
    709                 date_checks.append(('month', name, f.unique_for_month))
     705
     706        fields_with_class = [(self.__class__, self._meta.local_fields)]
     707        for parent_class in self._meta.parents.keys():
     708            fields_with_class.append((parent_class, parent_class._meta.local_fields))
     709
     710        for model_class, fields in fields_with_class:
     711            for f in fields:
     712                name = f.name
     713                if name in exclude:
     714                    continue
     715                if f.unique:
     716                    unique_checks.append((model_class, (name,)))
     717                if f.unique_for_date:
     718                    date_checks.append((model_class, 'date', name, f.unique_for_date))
     719                if f.unique_for_year:
     720                    date_checks.append((model_class, 'year', name, f.unique_for_year))
     721                if f.unique_for_month:
     722                    date_checks.append((model_class, 'month', name, f.unique_for_month))
    710723        return unique_checks, date_checks
    711724
    712725    def _perform_unique_checks(self, unique_checks):
    713726        errors = {}
    714727
    715         for unique_check in unique_checks:
     728        for model_class, unique_check in unique_checks:
    716729            # Try to look up an existing object with the same values as this
    717730            # object's values for all the unique field.
    718731
     
    732745            if len(unique_check) != len(lookup_kwargs.keys()):
    733746                continue
    734747
    735             qs = self.__class__._default_manager.filter(**lookup_kwargs)
     748            qs = model_class._default_manager.filter(**lookup_kwargs)
    736749
    737750            # Exclude the current object from the query if we are editing an
    738751            # instance (as opposed to creating a new one)
     
    744757                    key = unique_check[0]
    745758                else:
    746759                    key = NON_FIELD_ERRORS
    747                 errors.setdefault(key, []).append(self.unique_error_message(unique_check))
     760                errors.setdefault(key, []).append(self.unique_error_message(model_class, unique_check))
    748761
    749762        return errors
    750763
    751764    def _perform_date_checks(self, date_checks):
    752765        errors = {}
    753         for lookup_type, field, unique_for in date_checks:
     766        for model_class, lookup_type, field, unique_for in date_checks:
    754767            lookup_kwargs = {}
    755768            # there's a ticket to add a date lookup, we can remove this special
    756769            # case if that makes it's way in
     
    763776                lookup_kwargs['%s__%s' % (unique_for, lookup_type)] = getattr(date, lookup_type)
    764777            lookup_kwargs[field] = getattr(self, field)
    765778
    766             qs = self.__class__._default_manager.filter(**lookup_kwargs)
     779            qs = model_class._default_manager.filter(**lookup_kwargs)
    767780            # Exclude the current object from the query if we are editing an
    768781            # instance (as opposed to creating a new one)
    769782            if not getattr(self, '_adding', False) and self.pk is not None:
     
    783796            'lookup': lookup_type,
    784797        }
    785798
    786     def unique_error_message(self, unique_check):
    787         opts = self._meta
     799    def unique_error_message(self, model_class, unique_check):
     800        opts = model_class._meta
    788801        model_name = capfirst(opts.verbose_name)
    789802
    790803        # A unique field
  • django/forms/models.py

     
    488488
    489489        errors = []
    490490        # Do each of the unique checks (unique and unique_together)
    491         for unique_check in all_unique_checks:
     491        for uclass, unique_check in all_unique_checks:
    492492            seen_data = set()
    493493            for form in self.forms:
    494494                # if the form doesn't have cleaned_data then we ignore it,
     
    512512        # iterate over each of the date checks now
    513513        for date_check in all_date_checks:
    514514            seen_data = set()
    515             lookup, field, unique_for = date_check
     515            uclass, lookup, field, unique_for = date_check
    516516            for form in self.forms:
    517517                # if the form doesn't have cleaned_data then we ignore it,
    518518                # it's already invalid
     
    556556    def get_date_error_message(self, date_check):
    557557        return ugettext("Please correct the duplicate data for %(field_name)s "
    558558            "which must be unique for the %(lookup)s in %(date_field)s.") % {
    559             'field_name': date_check[1],
    560             'date_field': date_check[2],
    561             'lookup': unicode(date_check[0]),
     559            'field_name': date_check[2],
     560            'date_field': date_check[3],
     561            'lookup': unicode(date_check[1]),
    562562        }
    563563
    564564    def get_form_error(self):
  • tests/modeltests/model_forms/tests.py

     
     1import datetime
    12from django.test import TestCase
    23from django import forms
    3 from models import Category
     4from models import Category, Writer, Book, DerivedBook, Post
     5from mforms import ProductForm, PriceForm, BookForm, DerivedBookForm, ExplicitPKForm, PostForm, DerivedPostForm
    46
    57
    68class IncompleteCategoryFormWithFields(forms.ModelForm):
     
    3537        form = IncompleteCategoryFormWithExclude(data={'name': 'some name', 'slug': 'some-slug'})
    3638        assert form.is_valid()
    3739
     40# unique/unique_together validation
     41class UniqueTest(TestCase):
     42    def setUp(self):
     43        self.writer = Writer.objects.create(name='Mike Royko')
     44
     45    def test_simple_unique(self):
     46        form = ProductForm({'slug': 'teddy-bear-blue'})
     47        self.assertTrue(form.is_valid())
     48        obj = form.save()
     49        form = ProductForm({'slug': 'teddy-bear-blue'})
     50        self.assertEqual(len(form.errors), 1)
     51        self.assertEqual(form.errors['slug'], [u'Product with this Slug already exists.'])
     52        form = ProductForm({'slug': 'teddy-bear-blue'}, instance=obj)
     53        self.assertTrue(form.is_valid())
     54
     55    def test_unique_together(self):
     56        """ModelForm test of unique_together constraint"""
     57        form = PriceForm({'price': '6.00', 'quantity': '1'})
     58        self.assertTrue(form.is_valid())
     59        form.save()
     60        form = PriceForm({'price': '6.00', 'quantity': '1'})
     61        self.assertFalse(form.is_valid())
     62        self.assertEqual(len(form.errors), 1)
     63        self.assertEqual(form.errors['__all__'], [u'Price with this Price and Quantity already exists.'])
     64
     65    def test_unique_null(self):
     66        title = 'I May Be Wrong But I Doubt It'
     67        form = BookForm({'title': title, 'author': self.writer.pk})
     68        self.assertTrue(form.is_valid())
     69        form.save()
     70        form = BookForm({'title': title, 'author': self.writer.pk})
     71        self.assertFalse(form.is_valid())
     72        self.assertEqual(len(form.errors), 1)
     73        self.assertEqual(form.errors['__all__'], [u'Book with this Title and Author already exists.'])
     74        form = BookForm({'title': title})
     75        self.assertTrue(form.is_valid())
     76        form.save()
     77        form = BookForm({'title': title})
     78        self.assertTrue(form.is_valid())
     79
     80    def test_inherited_unique(self):
     81        title = 'Boss'
     82        Book.objects.create(title=title, author=self.writer, special_id=1)
     83        form = DerivedBookForm({'title': 'Other', 'author': self.writer.pk, 'special_id': u'1', 'isbn': '12345'})
     84        self.assertFalse(form.is_valid())
     85        self.assertEqual(len(form.errors), 1)
     86        self.assertEqual(form.errors['special_id'], [u'Book with this Special id already exists.'])
     87
     88    def test_inherited_unique_together(self):
     89        title = 'Boss'
     90        form = BookForm({'title': title, 'author': self.writer.pk})
     91        self.assertTrue(form.is_valid())
     92        form.save()
     93        form = DerivedBookForm({'title': title, 'author': self.writer.pk, 'isbn': '12345'})
     94        self.assertFalse(form.is_valid())
     95        self.assertEqual(len(form.errors), 1)
     96        self.assertEqual(form.errors['__all__'], [u'Book with this Title and Author already exists.'])
     97
     98    def test_abstract_inherited_unique(self):
     99        title = 'Boss'
     100        isbn = '12345'
     101        dbook = DerivedBook.objects.create(title=title, author=self.writer, isbn=isbn)
     102        form = DerivedBookForm({'title': 'Other', 'author': self.writer.pk, 'isbn': isbn})
     103        self.assertFalse(form.is_valid())
     104        self.assertEqual(len(form.errors), 1)
     105        self.assertEqual(form.errors['isbn'], [u'Derived book with this Isbn already exists.'])
     106
     107    def test_abstract_inherited_unique_together(self):
     108        title = 'Boss'
     109        isbn = '12345'
     110        dbook = DerivedBook.objects.create(title=title, author=self.writer, isbn=isbn)
     111        form = DerivedBookForm({'title': 'Other', 'author': self.writer.pk, 'isbn': '9876', 'suffix1': u'0', 'suffix2': u'0'})
     112        self.assertFalse(form.is_valid())
     113        self.assertEqual(len(form.errors), 1)
     114        self.assertEqual(form.errors['__all__'], [u'Derived book with this Suffix1 and Suffix2 already exists.'])
     115
     116    def test_explicitpk_unspecified(self):
     117        """Test for primary_key being in the form and failing validation."""
     118        form = ExplicitPKForm({'key': u'', 'desc': u'' })
     119        self.assertFalse(form.is_valid())
     120
     121    def test_explicitpk_unique(self):
     122        """Ensure keys and blank character strings are tested for uniqueness."""
     123        form = ExplicitPKForm({'key': u'key1', 'desc': u''})
     124        self.assertTrue(form.is_valid())
     125        form.save()
     126        form = ExplicitPKForm({'key': u'key1', 'desc': u''})
     127        self.assertFalse(form.is_valid())
     128        self.assertEqual(len(form.errors), 3)
     129        self.assertEqual(form.errors['__all__'], [u'Explicit pk with this Key and Desc already exists.'])
     130        self.assertEqual(form.errors['desc'], [u'Explicit pk with this Desc already exists.'])
     131        self.assertEqual(form.errors['key'], [u'Explicit pk with this Key already exists.'])
     132
     133    def test_unique_for_date(self):
     134        p = Post.objects.create(title="Django 1.0 is released",
     135            slug="Django 1.0", subtitle="Finally", posted=datetime.date(2008, 9, 3))
     136        form = PostForm({'title': "Django 1.0 is released", 'posted': '2008-09-03'})
     137        self.assertFalse(form.is_valid())
     138        self.assertEqual(len(form.errors), 1)
     139        self.assertEqual(form.errors['title'], [u'Title must be unique for Posted date.'])
     140        form = PostForm({'title': "Work on Django 1.1 begins", 'posted': '2008-09-03'})
     141        self.assertTrue(form.is_valid())
     142        form = PostForm({'title': "Django 1.0 is released", 'posted': '2008-09-04'})
     143        self.assertTrue(form.is_valid())
     144        form = PostForm({'slug': "Django 1.0", 'posted': '2008-01-01'})
     145        self.assertFalse(form.is_valid())
     146        self.assertEqual(len(form.errors), 1)
     147        self.assertEqual(form.errors['slug'], [u'Slug must be unique for Posted year.'])
     148        form = PostForm({'subtitle': "Finally", 'posted': '2008-09-30'})
     149        self.assertFalse(form.is_valid())
     150        self.assertEqual(form.errors['subtitle'], [u'Subtitle must be unique for Posted month.'])
     151        form = PostForm({'subtitle': "Finally", "title": "Django 1.0 is released",
     152            "slug": "Django 1.0", 'posted': '2008-09-03'}, instance=p)
     153        self.assertTrue(form.is_valid())
     154
     155    def test_inherited_unique_for_date(self):
     156        p = Post.objects.create(title="Django 1.0 is released",
     157            slug="Django 1.0", subtitle="Finally", posted=datetime.date(2008, 9, 3))
     158        form = DerivedPostForm({'title': "Django 1.0 is released", 'posted': '2008-09-03'})
     159        self.assertFalse(form.is_valid())
     160        self.assertEqual(len(form.errors), 1)
     161        self.assertEqual(form.errors['title'], [u'Title must be unique for Posted date.'])
     162        form = DerivedPostForm({'title': "Work on Django 1.1 begins", 'posted': '2008-09-03'})
     163        self.assertTrue(form.is_valid())
     164        form = DerivedPostForm({'title': "Django 1.0 is released", 'posted': '2008-09-04'})
     165        self.assertTrue(form.is_valid())
     166        form = DerivedPostForm({'slug': "Django 1.0", 'posted': '2008-01-01'})
     167        self.assertFalse(form.is_valid())
     168        self.assertEqual(len(form.errors), 1)
     169        self.assertEqual(form.errors['slug'], [u'Slug must be unique for Posted year.'])
     170        form = DerivedPostForm({'subtitle': "Finally", 'posted': '2008-09-30'})
     171        self.assertFalse(form.is_valid())
     172        self.assertEqual(form.errors['subtitle'], [u'Subtitle must be unique for Posted month.'])
     173        form = DerivedPostForm({'subtitle': "Finally", "title": "Django 1.0 is released",
     174            "slug": "Django 1.0", 'posted': '2008-09-03'}, instance=p)
     175        self.assertTrue(form.is_valid())
     176
  • tests/modeltests/model_forms/models.py

     
    101101        from PIL import Image, _imaging
    102102    except ImportError:
    103103        import Image, _imaging
    104    
     104
    105105    test_images = True
    106106
    107107    class ImageFile(models.Model):
     
    181181    class Meta:
    182182        unique_together = ('title', 'author')
    183183
     184class BookXtra(models.Model):
     185    isbn = models.CharField(max_length=16, unique=True)
     186    suffix1 = models.IntegerField(blank=True, default=0)
     187    suffix2 = models.IntegerField(blank=True, default=0)
     188
     189    class Meta:
     190        unique_together = (('suffix1', 'suffix2'))
     191        abstract = True
     192
     193class DerivedBook(Book, BookXtra):
     194    pass
     195
    184196class ExplicitPK(models.Model):
    185197    key = models.CharField(max_length=20, primary_key=True)
    186198    desc = models.CharField(max_length=20, blank=True, unique=True)
     
    199211    def __unicode__(self):
    200212        return self.name
    201213
     214class DerivedPost(Post):
     215    pass
     216
    202217class BigInt(models.Model):
    203218    biggie = models.BigIntegerField()
    204219
     
    14241439>>> f.cleaned_data
    14251440{'field': u'1'}
    14261441
    1427 # unique/unique_together validation
    1428 
    1429 >>> class ProductForm(ModelForm):
    1430 ...     class Meta:
    1431 ...         model = Product
    1432 >>> form = ProductForm({'slug': 'teddy-bear-blue'})
    1433 >>> form.is_valid()
    1434 True
    1435 >>> obj = form.save()
    1436 >>> obj
    1437 <Product: teddy-bear-blue>
    1438 >>> form = ProductForm({'slug': 'teddy-bear-blue'})
    1439 >>> form.is_valid()
    1440 False
    1441 >>> form._errors
    1442 {'slug': [u'Product with this Slug already exists.']}
    1443 >>> form = ProductForm({'slug': 'teddy-bear-blue'}, instance=obj)
    1444 >>> form.is_valid()
    1445 True
    1446 
    1447 # ModelForm test of unique_together constraint
    1448 >>> class PriceForm(ModelForm):
    1449 ...     class Meta:
    1450 ...         model = Price
    1451 >>> form = PriceForm({'price': '6.00', 'quantity': '1'})
    1452 >>> form.is_valid()
    1453 True
    1454 >>> form.save()
    1455 <Price: 1 for 6.00>
    1456 >>> form = PriceForm({'price': '6.00', 'quantity': '1'})
    1457 >>> form.is_valid()
    1458 False
    1459 >>> form._errors
    1460 {'__all__': [u'Price with this Price and Quantity already exists.']}
    1461 
    14621442This Price instance generated by this form is not valid because the quantity
    14631443field is required, but the form is valid because the field is excluded from
    14641444the form. This is for backwards compatibility.
     
    14951475>>> form.instance.pk is None
    14961476True
    14971477
    1498 # Unique & unique together with null values
    1499 >>> class BookForm(ModelForm):
    1500 ...     class Meta:
    1501 ...        model = Book
    1502 >>> w = Writer.objects.get(name='Mike Royko')
    1503 >>> form = BookForm({'title': 'I May Be Wrong But I Doubt It', 'author' : w.pk})
    1504 >>> form.is_valid()
    1505 True
    1506 >>> form.save()
    1507 <Book: Book object>
    1508 >>> form = BookForm({'title': 'I May Be Wrong But I Doubt It', 'author' : w.pk})
    1509 >>> form.is_valid()
    1510 False
    1511 >>> form._errors
    1512 {'__all__': [u'Book with this Title and Author already exists.']}
    1513 >>> form = BookForm({'title': 'I May Be Wrong But I Doubt It'})
    1514 >>> form.is_valid()
    1515 True
    1516 >>> form.save()
    1517 <Book: Book object>
    1518 >>> form = BookForm({'title': 'I May Be Wrong But I Doubt It'})
    1519 >>> form.is_valid()
    1520 True
    1521 
    1522 # Test for primary_key being in the form and failing validation.
    1523 >>> class ExplicitPKForm(ModelForm):
    1524 ...     class Meta:
    1525 ...         model = ExplicitPK
    1526 ...         fields = ('key', 'desc',)
    1527 >>> form = ExplicitPKForm({'key': u'', 'desc': u'' })
    1528 >>> form.is_valid()
    1529 False
    1530 
    1531 # Ensure keys and blank character strings are tested for uniqueness.
    1532 >>> form = ExplicitPKForm({'key': u'key1', 'desc': u''})
    1533 >>> form.is_valid()
    1534 True
    1535 >>> form.save()
    1536 <ExplicitPK: key1>
    1537 >>> form = ExplicitPKForm({'key': u'key1', 'desc': u''})
    1538 >>> form.is_valid()
    1539 False
    1540 >>> sorted(form.errors.items())
    1541 [('__all__', [u'Explicit pk with this Key and Desc already exists.']), ('desc', [u'Explicit pk with this Desc already exists.']), ('key', [u'Explicit pk with this Key already exists.'])]
    1542 
    15431478# Choices on CharField and IntegerField
    15441479>>> class ArticleForm(ModelForm):
    15451480...     class Meta:
     
    16051540<tr><th><label for="id_description">Description:</label></th><td><input type="text" name="description" id="id_description" /></td></tr>
    16061541<tr><th><label for="id_url">The URL:</label></th><td><input id="id_url" type="text" name="url" maxlength="40" /></td></tr>
    16071542
    1608 ### Validation on unique_for_date
    1609 
    1610 >>> p = Post.objects.create(title="Django 1.0 is released", slug="Django 1.0", subtitle="Finally", posted=datetime.date(2008, 9, 3))
    1611 >>> class PostForm(ModelForm):
    1612 ...     class Meta:
    1613 ...         model = Post
    1614 
    1615 >>> f = PostForm({'title': "Django 1.0 is released", 'posted': '2008-09-03'})
    1616 >>> f.is_valid()
    1617 False
    1618 >>> f.errors
    1619 {'title': [u'Title must be unique for Posted date.']}
    1620 >>> f = PostForm({'title': "Work on Django 1.1 begins", 'posted': '2008-09-03'})
    1621 >>> f.is_valid()
    1622 True
    1623 >>> f = PostForm({'title': "Django 1.0 is released", 'posted': '2008-09-04'})
    1624 >>> f.is_valid()
    1625 True
    1626 >>> f = PostForm({'slug': "Django 1.0", 'posted': '2008-01-01'})
    1627 >>> f.is_valid()
    1628 False
    1629 >>> f.errors
    1630 {'slug': [u'Slug must be unique for Posted year.']}
    1631 >>> f = PostForm({'subtitle': "Finally", 'posted': '2008-09-30'})
    1632 >>> f.is_valid()
    1633 False
    1634 >>> f.errors
    1635 {'subtitle': [u'Subtitle must be unique for Posted month.']}
    1636 >>> f = PostForm({'subtitle': "Finally", "title": "Django 1.0 is released", "slug": "Django 1.0", 'posted': '2008-09-03'}, instance=p)
    1637 >>> f.is_valid()
    1638 True
    1639 
    16401543# Clean up
    16411544>>> import shutil
    16421545>>> shutil.rmtree(temp_storage_dir)
  • tests/modeltests/model_forms/mforms.py

     
     1from django.forms import ModelForm
     2
     3from models import Product, Price, Book, DerivedBook, ExplicitPK, Post, DerivedPost
     4
     5class ProductForm(ModelForm):
     6    class Meta:
     7        model = Product
     8
     9class PriceForm(ModelForm):
     10    class Meta:
     11        model = Price
     12
     13class BookForm(ModelForm):
     14    class Meta:
     15       model = Book
     16
     17class DerivedBookForm(ModelForm):
     18    class Meta:
     19        model = DerivedBook
     20
     21class ExplicitPKForm(ModelForm):
     22    class Meta:
     23        model = ExplicitPK
     24        fields = ('key', 'desc',)
     25
     26class PostForm(ModelForm):
     27    class Meta:
     28        model = Post
     29
     30class DerivedPostForm(ModelForm):
     31    class Meta:
     32        model = DerivedPost
  • tests/modeltests/validation/test_unique.py

    Property changes on: tests/modeltests/model_forms/mforms.py
    ___________________________________________________________________
    Added: svn:executable
       + *
    Added: svn:eol-style
       + native
    
     
    99    def test_unique_fields_get_collected(self):
    1010        m = UniqueFieldsModel()
    1111        self.assertEqual(
    12             ([('id',), ('unique_charfield',), ('unique_integerfield',)], []),
     12            ([(UniqueFieldsModel, ('id',)),
     13              (UniqueFieldsModel, ('unique_charfield',)),
     14              (UniqueFieldsModel, ('unique_integerfield',))],
     15             []),
    1316            m._get_unique_checks()
    1417        )
    1518
    1619    def test_unique_together_gets_picked_up_and_converted_to_tuple(self):
    1720        m = UniqueTogetherModel()
    1821        self.assertEqual(
    19             ([('ifield', 'cfield',),('ifield', 'efield'), ('id',), ], []),
     22            ([(UniqueTogetherModel, ('ifield', 'cfield',)),
     23              (UniqueTogetherModel, ('ifield', 'efield')),
     24              (UniqueTogetherModel, ('id',)), ],
     25             []),
    2026            m._get_unique_checks()
    2127        )
    2228
    2329    def test_primary_key_is_considered_unique(self):
    2430        m = CustomPKModel()
    25         self.assertEqual(([('my_pk_field',)], []), m._get_unique_checks())
     31        self.assertEqual(([(CustomPKModel, ('my_pk_field',))], []), m._get_unique_checks())
    2632
    2733    def test_unique_for_date_gets_picked_up(self):
    2834        m = UniqueForDateModel()
    2935        self.assertEqual((
    30             [('id',)],
    31             [('date', 'count', 'start_date'), ('year', 'count', 'end_date'), ('month', 'order', 'end_date')]
     36            [(UniqueForDateModel, ('id',))],
     37            [(UniqueForDateModel, 'date', 'count', 'start_date'),
     38             (UniqueForDateModel, 'year', 'count', 'end_date'),
     39             (UniqueForDateModel, 'month', 'order', 'end_date')]
    3240            ), m._get_unique_checks()
    3341        )
    3442
Back to Top