Ticket #13091: 13091_partial_unique_together.4.diff
File 13091_partial_unique_together.4.diff, 15.2 KB (added by , 14 years ago) |
---|
-
docs/ref/models/instances.txt
113 113 114 114 Finally, ``full_clean()`` will check any unique constraints on your model. 115 115 116 .. _model-validate-unique 117 116 118 .. method:: Model.validate_unique(exclude=None) 117 119 118 120 This method is similar to ``clean_fields``, but validates all uniqueness … … 122 124 validation. 123 125 124 126 Note that if you provide an ``exclude`` argument to ``validate_unique``, any 125 ``unique_together`` constraint that contains one of the fields you provided 126 will not be checked. 127 ``unique_together`` constraint that has all of its fields listed in ``exclude`` 128 will be ignored. An exception to this is the case of a field with its ``default`` 129 parameter defined, or ``blank`` set to True (which is equivalent to ``default=""``). 130 If such a default-enabled field is listed in ``exclude``, all ``unique_together`` 131 constraints that reference that field will also be ignored. 127 132 133 .. versionadded:: 1.4 134 Prior to Django 1.4, a ``unique_together`` constraint would be ignored if any of the fields were listed in ``exclude`` 128 135 136 129 137 Saving objects 130 138 ============== 131 139 -
docs/ref/models/options.txt
231 231 This is a list of lists of fields that must be unique when considered together. 232 232 It's used in the Django admin and is enforced at the database level (i.e., the 233 233 appropriate ``UNIQUE`` statements are included in the ``CREATE TABLE`` 234 statement). 234 statement). 235 235 236 .. versionadded:: 1.2 237 As of Django 1.2, ``unique_together`` is also used in ModelForm's :ref:`model validation <model-validate-unique>`. 238 236 239 For convenience, unique_together can be a single list when dealing with a single 237 240 set of fields:: 238 241 239 242 unique_together = ("driver", "restaurant") 240 243 244 241 245 ``verbose_name`` 242 246 ---------------- 243 247 -
django/db/models/base.py
7 7 import django.db.models.manager # Imported to register signal handler. 8 8 from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned, FieldError, ValidationError, NON_FIELD_ERRORS 9 9 from django.core import validators 10 from django.db.models.fields import AutoField, FieldDoesNotExist 10 from django.db.models.fields import AutoField, FieldDoesNotExist, NOT_PROVIDED 11 11 from django.db.models.fields.related import (OneToOneRel, ManyToOneRel, 12 12 OneToOneField, add_lazy_relation) 13 13 from django.db.models.query import Q … … 666 666 unique_togethers.append((parent_class, parent_class._meta.unique_together)) 667 667 668 668 for model_class, unique_together in unique_togethers: 669 for check in unique_together: 670 for name in check: 671 # If this is an excluded field, don't add this check. 672 if name in exclude: 673 break 674 else: 675 unique_checks.append((model_class, tuple(check))) 669 for unique_tuple in unique_together: 670 perform_check = False 671 for name in unique_tuple: 672 # if any of the fields are NOT excluded, then validate unique 673 if name not in exclude: 674 perform_check = True 675 # however, if there is a default value specified among excluded, don't 676 else: 677 field = self._meta.get_field_by_name(name)[0] 678 if field.default is not NOT_PROVIDED or field.blank: 679 perform_check = False 680 break 681 682 if perform_check: 683 unique_checks.append((model_class, tuple(unique_tuple))) 676 684 677 685 # These are checks for the unique_for_<date/year/month>. 678 686 date_checks = [] -
tests/modeltests/model_forms/tests.py
1 1 import datetime 2 2 from django.test import TestCase 3 3 from django import forms 4 from models import Category, Writer, Book, DerivedBook, Post, FlexibleDatePost 4 from models import (Category, Writer, Book, DerivedBook, Post, FlexibleDatePost, 5 BirthdayPresent) 5 6 from mforms import (ProductForm, PriceForm, BookForm, DerivedBookForm, 6 7 ExplicitPKForm, PostForm, DerivedPostForm, CustomWriterForm, 7 FlexDatePostForm) 8 FlexDatePostForm, BirthdayPresentForm, DefaultBirthdayPresentForm, 9 BlankBirthdayPresentForm, ExcludeAllBirthdayPresentForm) 8 10 9 11 10 12 class IncompleteCategoryFormWithFields(forms.ModelForm): … … 68 70 self.assertEqual(len(form.errors), 1) 69 71 self.assertEqual(form.errors['__all__'], [u'Price with this Price and Quantity already exists.']) 70 72 73 def test_excluded_unique_together(self): 74 """ 75 ModelForm test of unique_together where a unique_together 76 field is excluded from the form 77 """ 78 new_instance = BirthdayPresent(username='joe.smith') 79 form = BirthdayPresentForm({'year': '1998', 'description':'a blue bicycle'}, 80 instance=new_instance) 81 self.assertTrue(form.is_valid()) 82 form.save() 83 new_instance = BirthdayPresent(username='joe.smith') 84 form = BirthdayPresentForm({'year': '1998', 'description':'a sweater'}, 85 instance=new_instance) 86 self.assertFalse(form.is_valid()) 87 self.assertEqual(len(form.errors), 1) 88 self.assertEqual(form.errors['__all__'], [u'Birthday present with this Username and Year already exists.']) 89 90 def test_excluded_unique_together_default(self): 91 """ 92 ModelForm test of unique_together where a unique_together 93 field with a default value is excluded from the form 94 """ 95 form = DefaultBirthdayPresentForm({'year': '2000', 'description':'a blue bicycle'}) 96 self.assertTrue(form.is_valid()) 97 form.save() 98 form = DefaultBirthdayPresentForm({'year': '2000', 'description':'a sweater'}) 99 self.assertTrue(form.is_valid()) 100 101 def test_excluded_unique_together_blank(self): 102 """ 103 ModelForm test of unique_together where a unique_together 104 field with blank set to True is excluded from the form 105 """ 106 form = BlankBirthdayPresentForm({'year': '2000', 'description':'a blue bicycle'}) 107 self.assertTrue(form.is_valid()) 108 form.save() 109 form = BlankBirthdayPresentForm({'year': '2000', 'description':'a sweater'}) 110 self.assertTrue(form.is_valid()) 111 112 def test_excluded_unique_together_all(self): 113 """ 114 ModelForm test of unique_together where all specified 115 unique_together fields are excluded from the form 116 """ 117 new_instance = BirthdayPresent(username='fred.wilson', year='2010') 118 form = ExcludeAllBirthdayPresentForm({'description':'a rock'}, 119 instance=new_instance) 120 self.assertTrue(form.is_valid()) 121 form.save() 122 new_instance = BirthdayPresent(username='fred.wilson', year='2010') 123 form = ExcludeAllBirthdayPresentForm({'description':'an empty box'}, 124 instance=new_instance) 125 self.assertTrue(form.is_valid()) 126 71 127 def test_unique_null(self): 72 128 title = 'I May Be Wrong But I Doubt It' 73 129 form = BookForm({'title': title, 'author': self.writer.pk}) -
tests/modeltests/model_forms/mforms.py
2 2 from django.forms import ModelForm 3 3 4 4 from models import (Product, Price, Book, DerivedBook, ExplicitPK, Post, 5 DerivedPost, Writer, FlexibleDatePost) 5 DerivedPost, Writer, FlexibleDatePost, BirthdayPresent, 6 DefaultBirthdayPresent, BlankBirthdayPresent) 6 7 7 8 class ProductForm(ModelForm): 8 9 class Meta: … … 42 43 class FlexDatePostForm(ModelForm): 43 44 class Meta: 44 45 model = FlexibleDatePost 46 47 class BirthdayPresentForm(ModelForm): 48 class Meta: 49 model = BirthdayPresent 50 exclude = ('username',) 51 52 class DefaultBirthdayPresentForm(ModelForm): 53 class Meta: 54 model = DefaultBirthdayPresent 55 exclude = ('username',) 56 57 class BlankBirthdayPresentForm(ModelForm): 58 class Meta: 59 model = BlankBirthdayPresent 60 exclude = ('username',) 61 62 class ExcludeAllBirthdayPresentForm(ModelForm): 63 class Meta: 64 model = BirthdayPresent 65 exclude = ('username', 'year',) 66 -
tests/modeltests/model_forms/models.py
248 248 subtitle = models.CharField(max_length=50, unique_for_month='posted', blank=True) 249 249 posted = models.DateField(blank=True, null=True) 250 250 251 class BirthdayPresent(models.Model): 252 """used to test unique_together validation""" 253 username = models.CharField(max_length=30) 254 year = models.IntegerField() 255 description = models.CharField(max_length=200) 256 257 class Meta: 258 unique_together = ('username', 'year') 259 260 class DefaultBirthdayPresent(models.Model): 261 """used to test unique_together validation""" 262 username = models.CharField(max_length=30, default='jeremy.jones') 263 year = models.IntegerField() 264 description = models.CharField(max_length=200) 265 266 class Meta: 267 unique_together = (('username', 'year'),) 268 269 class BlankBirthdayPresent(models.Model): 270 """used to test unique_together validation""" 271 username = models.CharField(max_length=30, blank=True) 272 year = models.IntegerField() 273 description = models.CharField(max_length=200) 274 275 class Meta: 276 unique_together = (('username', 'year'),) 277 278 251 279 __test__ = {'API_TESTS': """ 252 280 >>> from django import forms 253 281 >>> from django.forms.models import ModelForm, model_to_dict -
tests/modeltests/model_formsets/tests.py
1066 1066 self.assertEqual(formset._non_form_errors, 1067 1067 [u'Please correct the duplicate data for price and quantity, which must be unique.']) 1068 1068 1069 # Only the price field is specified, this should skip anyunique checks since1070 # the unique_together is not fulfilled. This will fail with a KeyError if broken.1069 # Only the price field is specified, this will fail in the unique checks since 1070 # the unique_together is partially included. 1071 1071 FormSet = modelformset_factory(Price, fields=("price",), extra=2) 1072 1072 data = { 1073 1073 'form-TOTAL_FORMS': '2', … … 1077 1077 'form-1-price': '24', 1078 1078 } 1079 1079 formset = FormSet(data) 1080 self.assertTrue(formset.is_valid()) 1080 self.assertFalse(formset.is_valid()) 1081 self.assertEqual(formset._non_form_errors, 1082 [u'Please correct the duplicate data for price and quantity, which must be unique.']) 1081 1083 1082 1084 FormSet = inlineformset_factory(Author, Book, extra=0) 1083 1085 author = Author.objects.create(pk=1, name='Charles Baudelaire') -
tests/regressiontests/admin_views/tests.py
36 36 Person, Persona, Picture, Podcast, Section, Subscriber, Vodcast, 37 37 Language, Collector, Widget, Grommet, DooHickey, FancyDoodad, Whatsit, 38 38 Category, Post, Plot, FunkyTag, Chapter, Book, Promo, WorkHour, Employee, 39 Question, Answer, Inquisition, Actor, FoodDelivery, 39 Question, Answer, Inquisition, Actor, FoodDelivery, UniqueTogether, 40 40 RowLevelChangePermissionModel, Paper, CoverLetter, Story, OtherStory) 41 41 42 42 … … 1403 1403 # 1 select per object = 3 selects 1404 1404 self.assertEqual(response.content.count("<select"), 4) 1405 1405 1406 def test_partial_unique_together(self): 1407 """ Ensure that no IntegrityError is raised when editing some (but 1408 not all) of the values specified as unique_together. Refs #13091. 1409 """ 1410 UniqueTogether.objects.create(t1='a', t2='b', t3='c') 1411 UniqueTogether.objects.create(t1='b', t2='b', t3='a') 1412 UniqueTogether.objects.create(t1='c', t2='a', t3='c') 1413 data = { 1414 "form-TOTAL_FORMS": "3", 1415 "form-INITIAL_FORMS": "3", 1416 "form-MAX_NUM_FORMS": "0", 1417 1418 "form-0-t1": "a", 1419 "form-0-t2": "b", 1420 "form-0-id": "1", 1421 1422 "form-1-t1": "a", 1423 "form-1-t2": "b", 1424 "form-1-id": "2", 1425 1426 "form-2-t1": "a", 1427 "form-2-t2": "b", 1428 "form-2-id": "3", 1429 1430 "_save": "Save", 1431 } 1432 response = self.client.post('/test_admin/admin/admin_views/uniquetogether/', data) 1433 self.assertContains(response, 'Please correct the errors below.') 1434 self.assertContains(response, 'Unique together with this T1, T2 and T3 already exists.') 1435 1406 1436 def test_post_messages(self): 1407 1437 # Ticket 12707: Saving inline editable should not show admin 1408 1438 # action warnings -
tests/regressiontests/admin_views/models.py
803 803 list_display_links = ('title', 'id') # 'id' in list_display_links 804 804 list_editable = ('content', ) 805 805 806 class UniqueTogether(models.Model): 807 t1 = models.CharField(max_length=255, blank=True, null=True) 808 t2 = models.CharField(max_length=255, blank=True, null=True) 809 t3 = models.CharField(max_length=255) 810 811 class Meta: 812 unique_together = ['t1','t2','t3'] 813 814 class UniqueTogetherAdmin(admin.ModelAdmin): 815 list_display = ['t3', 't1', 't2'] 816 list_editable = ['t1', 't2',] 817 818 806 819 admin.site.register(Article, ArticleAdmin) 807 820 admin.site.register(CustomArticle, CustomArticleAdmin) 808 821 admin.site.register(Section, save_as=True, inlines=[ArticleInline]) … … 845 858 admin.site.register(CoverLetter, CoverLetterAdmin) 846 859 admin.site.register(Story, StoryAdmin) 847 860 admin.site.register(OtherStory, OtherStoryAdmin) 861 admin.site.register(UniqueTogether, UniqueTogetherAdmin) 848 862 849 863 # We intentionally register Promo and ChapterXtra1 but not Chapter nor ChapterXtra2. 850 864 # That way we cover all four cases: