Ticket #8209: django-8209.2.diff
File django-8209.2.diff, 12.1 KB (added by , 16 years ago) |
---|
-
django/forms/models.py
diff --git a/django/forms/models.py b/django/forms/models.py index 4563ace..6165305 100644
a b Helper functions for creating Form classes from Django models 3 3 and database field objects. 4 4 """ 5 5 6 from django.utils.translation import ugettext_lazy as _7 6 from django.utils.encoding import smart_unicode 8 7 from django.utils.datastructures import SortedDict 8 from django.utils.text import get_text_list 9 from django.utils.translation import ugettext_lazy as _ 9 10 10 11 from util import ValidationError, ErrorList 11 12 from forms import BaseForm, get_declared_fields … … __all__ = ( 20 21 'ModelMultipleChoiceField', 21 22 ) 22 23 24 try: 25 any 26 except NameError: 27 from django.utils.itercompat import any # pre-2.5 28 23 29 def save_instance(form, instance, fields=None, fail_message='saved', 24 30 commit=True, exclude=None): 25 31 """ … … class BaseModelForm(BaseForm): 202 208 object_data.update(initial) 203 209 super(BaseModelForm, self).__init__(data, files, auto_id, prefix, object_data, 204 210 error_class, label_suffix, empty_permitted) 211 def clean(self): 212 self.validate_unique() 213 return self.cleaned_data 214 215 def validate_unique(self): 216 from django.db.models.fields import FieldDoesNotExist 217 unique_checks = list(self.instance._meta.unique_together[:]) 218 form_errors = [] 219 for name, field in self.fields.items(): 220 try: 221 f = self.instance._meta.get_field_by_name(name)[0] 222 except FieldDoesNotExist: 223 # This is an extra field that's not on the model, ignore it 224 continue 225 if name in self.cleaned_data and f.unique: 226 unique_checks.append((name,)) 227 # we ignore fields that already have an error 228 for unique_check in [check for check in unique_checks if not any([x in self._errors for x in check])]: 229 # generate are parameters for the query 230 kwargs = dict([(field_name, self.cleaned_data[field_name]) for field_name in unique_check]) 231 qs = self.instance.__class__._default_manager.filter(**kwargs) 232 if self.instance.pk is not None: 233 # exclude the current object from the query if we are editing an 234 # instance 235 qs = qs.exclude(pk=self.instance.pk) 236 if qs.extra(select={'a': 1}).values('a').order_by(): 237 model_name = self.instance._meta.verbose_name.title() 238 # a unique field 239 if len(unique_check) == 1: 240 field_name = unique_check[0] 241 field_label = self.fields[field_name].label 242 # insert the error into the error dict, very sneaky 243 self._errors[field_name] = ErrorList([ 244 _("%(model_name)s with this %(field_label)s already exists.") % \ 245 {'model_name': model_name, 'field_label': field_label} 246 ]) 247 # a unique together 248 else: 249 field_labels = [self.fields[field_name].label for field_name in unique_check] 250 field_labels = get_text_list(field_labels, _('and')) 251 form_errors.append( 252 _("%(model_name)s with this %(field_label)s already exists.") % \ 253 {'model_name': model_name, 'field_label': field_labels} 254 ) 255 for field_name in unique_check: 256 # remove the data from the cleaned_data dict since it was 257 # invalid 258 del self.cleaned_data[field_name] 259 if form_errors: 260 # raise the unique together errors since they are considered 261 # form wide. 262 raise ValidationError(form_errors) 205 263 206 264 def save(self, commit=True): 207 265 """ … … class BaseModelFormSet(BaseFormSet): 246 304 queryset=None, **kwargs): 247 305 self.queryset = queryset 248 306 defaults = {'data': data, 'files': files, 'auto_id': auto_id, 'prefix': prefix} 249 if self.max_num > 0: 250 qs = self.get_queryset()[:self.max_num] 251 else: 252 qs = self.get_queryset() 253 defaults['initial'] = [model_to_dict(obj) for obj in qs] 307 defaults['initial'] = [model_to_dict(obj) for obj in self.get_queryset()] 254 308 defaults.update(kwargs) 255 309 super(BaseModelFormSet, self).__init__(**defaults) 256 310 311 def _construct_form(self, i, **kwargs): 312 if i < self._initial_form_count: 313 kwargs['instance'] = self.get_queryset()[i] 314 return super(BaseModelFormSet, self)._construct_form(i, **kwargs) 315 257 316 def get_queryset(self): 258 if self.queryset is not None: 259 return self.queryset 260 return self.model._default_manager.get_query_set() 261 317 if not hasattr(self, '_queryset'): 318 if self.queryset is not None: 319 qs = self.queryset 320 else: 321 qs = self.model._default_manager.get_query_set() 322 if self.max_num > 0: 323 self._queryset = qs[:self.max_num] 324 else: 325 self._queryset = qs 326 return self._queryset 327 262 328 def save_new(self, form, commit=True): 263 329 """Saves and returns a new model instance for the given form.""" 264 330 return save_instance(form, self.model(), exclude=[self._pk_field.name], commit=commit) … … class BaseInlineFormSet(BaseModelFormSet): 358 424 self._total_form_count = self._initial_form_count 359 425 self._initial_form_count = 0 360 426 super(BaseInlineFormSet, self)._construct_forms() 427 428 def _construct_form(self, i, **kwargs): 429 form = super(BaseInlineFormSet, self)._construct_form(i, **kwargs) 430 if self.save_as_new: 431 # Remove the primary key from the form's data, we are only 432 # creating new instances 433 form.data[form.add_prefix(self._pk_field.name)] = None 434 return form 361 435 362 436 def get_queryset(self): 363 437 """ -
django/utils/itercompat.py
diff --git a/django/utils/itercompat.py b/django/utils/itercompat.py index c166da3..75d384b 100644
a b def sorted(in_value): 72 72 out_value = in_value[:] 73 73 out_value.sort() 74 74 return out_value 75 76 def any(seq): 77 """any implementation.""" 78 for x in seq: 79 if x: 80 return True 81 return False -
docs/topics/forms/modelforms.txt
diff --git a/docs/topics/forms/modelforms.txt b/docs/topics/forms/modelforms.txt index d161b3f..163c428 100644
a b parameter when declaring the form field:: 338 338 ... class Meta: 339 339 ... model = Article 340 340 341 Overriding the clean() method 342 ----------------------------- 343 344 You can overide the ``clean()`` method on a model form to provide additional 345 validation in the same way you can on a normal form. However, by default the 346 ``clean()`` method validates the uniqueness of fields that are marked as unique 347 on the model, and those marked as unque_together, if you would like to overide 348 the ``clean()`` method and maintain the default validation you must call the 349 parent class's ``clean()`` method. 350 341 351 Form inheritance 342 352 ---------------- 343 353 … … books of a specific author. Here is how you could accomplish this:: 500 510 >>> from django.forms.models import inlineformset_factory 501 511 >>> BookFormSet = inlineformset_factory(Author, Book) 502 512 >>> author = Author.objects.get(name=u'Orson Scott Card') 503 >>> formset = BookFormSet(instance=author) 504 No newline at end of file 513 >>> formset = BookFormSet(instance=author) -
tests/modeltests/model_forms/models.py
diff --git a/tests/modeltests/model_forms/models.py b/tests/modeltests/model_forms/models.py index 5f714fb..9a306aa 100644
a b class CommaSeparatedInteger(models.Model): 117 117 def __unicode__(self): 118 118 return self.field 119 119 120 class Product(models.Model): 121 slug = models.SlugField(unique=True) 122 123 def __unicode__(self): 124 return self.slug 125 126 class Price(models.Model): 127 price = models.DecimalField(max_digits=10, decimal_places=2) 128 quantity = models.PositiveIntegerField() 129 130 def __unicode__(self): 131 return u"%s for %s" % (self.quantity, self.price) 132 133 class Meta: 134 unique_together = (('price', 'quantity'),) 135 120 136 class ArticleStatus(models.Model): 121 137 status = models.CharField(max_length=2, choices=ARTICLE_STATUS_CHAR, blank=True, null=True) 122 138 139 123 140 __test__ = {'API_TESTS': """ 124 141 >>> from django import forms 125 142 >>> from django.forms.models import ModelForm, model_to_dict … … u'1,,2' 1132 1149 >>> f.clean('1') 1133 1150 u'1' 1134 1151 1135 # Choices on CharField and IntegerField1152 # unique/unique_together validation 1136 1153 1154 >>> class ProductForm(ModelForm): 1155 ... class Meta: 1156 ... model = Product 1157 >>> form = ProductForm({'slug': 'teddy-bear-blue'}) 1158 >>> form.is_valid() 1159 True 1160 >>> obj = form.save() 1161 >>> obj 1162 <Product: teddy-bear-blue> 1163 >>> form = ProductForm({'slug': 'teddy-bear-blue'}) 1164 >>> form.is_valid() 1165 False 1166 >>> form._errors 1167 {'slug': [u'Product with this Slug already exists.']} 1168 >>> form = ProductForm({'slug': 'teddy-bear-blue'}, instance=obj) 1169 >>> form.is_valid() 1170 True 1171 1172 # ModelForm test of unique_together constraint 1173 >>> class PriceForm(ModelForm): 1174 ... class Meta: 1175 ... model = Price 1176 >>> form = PriceForm({'price': '6.00', 'quantity': '1'}) 1177 >>> form.is_valid() 1178 True 1179 >>> form.save() 1180 <Price: 1 for 6.00> 1181 >>> form = PriceForm({'price': '6.00', 'quantity': '1'}) 1182 >>> form.is_valid() 1183 False 1184 >>> form._errors 1185 {'__all__': [u'Price with this Price and Quantity already exists.']} 1186 1187 # Choices on CharField and IntegerField 1137 1188 >>> class ArticleForm(ModelForm): 1138 1189 ... class Meta: 1139 1190 ... model = Article -
tests/modeltests/model_formsets/models.py
diff --git a/tests/modeltests/model_formsets/models.py b/tests/modeltests/model_formsets/models.py index 332c5a7..1e25baa 100644
a b class Restaurant(Place): 73 73 def __unicode__(self): 74 74 return self.name 75 75 76 class Product(models.Model): 77 slug = models.SlugField(unique=True) 78 79 def __unicode__(self): 80 return self.slug 81 82 class Price(models.Model): 83 price = models.DecimalField(max_digits=10, decimal_places=2) 84 quantity = models.PositiveIntegerField() 85 86 def __unicode__(self): 87 return u"%s for %s" % (self.quantity, self.price) 88 89 class Meta: 90 unique_together = (('price', 'quantity'),) 91 76 92 class MexicanRestaurant(Restaurant): 77 93 serves_tacos = models.BooleanField() 78 94 … … True 553 569 >>> type(_get_foreign_key(MexicanRestaurant, Owner)) 554 570 <class 'django.db.models.fields.related.ForeignKey'> 555 571 572 # unique/unique_together validation ########################################### 573 574 >>> FormSet = modelformset_factory(Product, extra=1) 575 >>> data = { 576 ... 'form-TOTAL_FORMS': '1', 577 ... 'form-INITIAL_FORMS': '0', 578 ... 'form-0-slug': 'car-red', 579 ... } 580 >>> formset = FormSet(data) 581 >>> formset.is_valid() 582 True 583 >>> formset.save() 584 [<Product: car-red>] 585 586 >>> data = { 587 ... 'form-TOTAL_FORMS': '1', 588 ... 'form-INITIAL_FORMS': '0', 589 ... 'form-0-slug': 'car-red', 590 ... } 591 >>> formset = FormSet(data) 592 >>> formset.is_valid() 593 False 594 >>> formset.errors 595 [{'slug': [u'Product with this Slug already exists.']}] 596 597 # unique_together 598 599 >>> FormSet = modelformset_factory(Price, extra=1) 600 >>> data = { 601 ... 'form-TOTAL_FORMS': '1', 602 ... 'form-INITIAL_FORMS': '0', 603 ... 'form-0-price': u'12.00', 604 ... 'form-0-quantity': '1', 605 ... } 606 >>> formset = FormSet(data) 607 >>> formset.is_valid() 608 True 609 >>> formset.save() 610 [<Price: 1 for 12.00>] 611 612 >>> data = { 613 ... 'form-TOTAL_FORMS': '1', 614 ... 'form-INITIAL_FORMS': '0', 615 ... 'form-0-price': u'12.00', 616 ... 'form-0-quantity': '1', 617 ... } 618 >>> formset = FormSet(data) 619 >>> formset.is_valid() 620 False 621 >>> formset.errors 622 [{'__all__': [u'Price with this Price and Quantity already exists.']}] 623 556 624 """}