Changeset 8805
- Timestamp:
- 09/01/08 14:08:08 (3 months ago)
- Files:
-
- django/trunk/django/forms/models.py (modified) (5 diffs)
- django/trunk/django/utils/itercompat.py (modified) (1 diff)
- django/trunk/docs/topics/forms/modelforms.txt (modified) (1 diff)
- django/trunk/tests/modeltests/model_formsets/models.py (modified) (2 diffs)
- django/trunk/tests/modeltests/model_forms/models.py (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
django/trunk/django/forms/models.py
r8775 r8805 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 … … 20 21 'ModelMultipleChoiceField', 21 22 ) 23 22 24 23 25 def save_instance(form, instance, fields=None, fail_message='saved', … … 203 205 super(BaseModelForm, self).__init__(data, files, auto_id, prefix, object_data, 204 206 error_class, label_suffix, empty_permitted) 207 def clean(self): 208 self.validate_unique() 209 return self.cleaned_data 210 211 def validate_unique(self): 212 from django.db.models.fields import FieldDoesNotExist 213 unique_checks = list(self.instance._meta.unique_together[:]) 214 form_errors = [] 215 216 # Make sure the unique checks apply to actual fields on the ModelForm 217 for name, field in self.fields.items(): 218 try: 219 f = self.instance._meta.get_field_by_name(name)[0] 220 except FieldDoesNotExist: 221 # This is an extra field that's not on the ModelForm, ignore it 222 continue 223 # MySQL can't handle ... WHERE pk IS NULL, so make sure we don't 224 # don't generate queries of that form. 225 is_null_pk = f.primary_key and self.cleaned_data[name] is None 226 if name in self.cleaned_data and f.unique and not is_null_pk: 227 unique_checks.append((name,)) 228 229 # Don't run unique checks on fields that already have an error. 230 unique_checks = [check for check in unique_checks if not [x in self._errors for x in check if x in self._errors]] 231 232 for unique_check in unique_checks: 233 # Try to look up an existing object with the same values as this 234 # object's values for all the unique field. 235 236 lookup_kwargs = {} 237 for field_name in unique_check: 238 lookup_kwargs[field_name] = self.cleaned_data[field_name] 239 240 qs = self.instance.__class__._default_manager.filter(**lookup_kwargs) 241 242 # Exclude the current object from the query if we are editing an 243 # instance (as opposed to creating a new one) 244 if self.instance.pk is not None: 245 qs = qs.exclude(pk=self.instance.pk) 246 247 # This cute trick with extra/values is the most efficiant way to 248 # tell if a particular query returns any results. 249 if qs.extra(select={'a': 1}).values('a').order_by(): 250 model_name = self.instance._meta.verbose_name.title() 251 252 # A unique field 253 if len(unique_check) == 1: 254 field_name = unique_check[0] 255 field_label = self.fields[field_name].label 256 # Insert the error into the error dict, very sneaky 257 self._errors[field_name] = ErrorList([ 258 _("%(model_name)s with this %(field_label)s already exists.") % \ 259 {'model_name': model_name, 'field_label': field_label} 260 ]) 261 # unique_together 262 else: 263 field_labels = [self.fields[field_name].label for field_name in unique_check] 264 field_labels = get_text_list(field_labels, _('and')) 265 form_errors.append( 266 _("%(model_name)s with this %(field_label)s already exists.") % \ 267 {'model_name': model_name, 'field_label': field_labels} 268 ) 269 270 # Remove the data from the cleaned_data dict since it was invalid 271 for field_name in unique_check: 272 del self.cleaned_data[field_name] 273 274 if form_errors: 275 # Raise the unique together errors since they are considered form-wide. 276 raise ValidationError(form_errors) 205 277 206 278 def save(self, commit=True): … … 247 319 self.queryset = queryset 248 320 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] 321 defaults['initial'] = [model_to_dict(obj) for obj in self.get_queryset()] 254 322 defaults.update(kwargs) 255 323 super(BaseModelFormSet, self).__init__(**defaults) 256 324 325 def _construct_form(self, i, **kwargs): 326 if i < self._initial_form_count: 327 kwargs['instance'] = self.get_queryset()[i] 328 return super(BaseModelFormSet, self)._construct_form(i, **kwargs) 329 257 330 def get_queryset(self): 258 if self.queryset is not None: 259 return self.queryset 260 return self.model._default_manager.get_query_set() 331 if not hasattr(self, '_queryset'): 332 if self.queryset is not None: 333 qs = self.queryset 334 else: 335 qs = self.model._default_manager.get_query_set() 336 if self.max_num > 0: 337 self._queryset = qs[:self.max_num] 338 else: 339 self._queryset = qs 340 return self._queryset 261 341 262 342 def save_new(self, form, commit=True): … … 359 439 self._initial_form_count = 0 360 440 super(BaseInlineFormSet, self)._construct_forms() 441 442 def _construct_form(self, i, **kwargs): 443 form = super(BaseInlineFormSet, self)._construct_form(i, **kwargs) 444 if self.save_as_new: 445 # Remove the primary key from the form's data, we are only 446 # creating new instances 447 form.data[form.add_prefix(self._pk_field.name)] = None 448 return form 361 449 362 450 def get_queryset(self): django/trunk/django/utils/itercompat.py
r7914 r8805 73 73 out_value.sort() 74 74 return out_value 75 django/trunk/docs/topics/forms/modelforms.txt
r8616 r8805 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 ---------------- django/trunk/tests/modeltests/model_formsets/models.py
r8775 r8805 73 73 def __unicode__(self): 74 74 return self.name 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'),) 75 91 76 92 class MexicanRestaurant(Restaurant): … … 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 """} django/trunk/tests/modeltests/model_forms/models.py
r8772 r8805 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) 138 122 139 123 140 __test__ = {'API_TESTS': """ … … 1133 1150 u'1' 1134 1151 1152 # unique/unique_together validation 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 1135 1187 # Choices on CharField and IntegerField 1136 1137 1188 >>> class ArticleForm(ModelForm): 1138 1189 ... class Meta:
