Ticket #10134: unique_for_stuff.4.diff
File unique_for_stuff.4.diff, 10.9 KB (added by , 16 years ago) |
---|
-
django/forms/models.py
diff --git a/django/forms/models.py b/django/forms/models.py index 7ca1ab5..6ea32ef 100644
a b class BaseModelForm(BaseForm): 225 225 object_data.update(initial) 226 226 super(BaseModelForm, self).__init__(data, files, auto_id, prefix, object_data, 227 227 error_class, label_suffix, empty_permitted) 228 def clean(self):229 self.validate_unique()230 return self.cleaned_data231 228 232 def validate_unique(self):229 def _get_unique_checks(self): 233 230 from django.db.models.fields import FieldDoesNotExist, Field as ModelField 234 231 235 232 # Gather a list of checks to perform. We only perform unique checks … … class BaseModelForm(BaseForm): 239 236 # not make sense to check data that didn't validate, and since NULL does not 240 237 # equal NULL in SQL we should not do any unique checking for NULL values. 241 238 unique_checks = [] 239 # these are checks for the unique_for_<date/year/month> 240 date_checks = [] 242 241 for check in self.instance._meta.unique_together[:]: 243 242 fields_on_form = [field for field in check if self.cleaned_data.get(field) is not None] 244 243 if len(fields_on_form) == len(check): 245 244 unique_checks.append(check) 246 245 247 form_errors = []248 249 246 # Gather a list of checks for fields declared as unique and add them to 250 247 # the list of checks. Again, skip empty fields and any that did not validate. 251 for name , field in self.fields.items():248 for name in self.fields: 252 249 try: 253 250 f = self.instance._meta.get_field_by_name(name)[0] 254 251 except FieldDoesNotExist: … … class BaseModelForm(BaseForm): 260 257 # get_field_by_name found it, but it is not a Field so do not proceed 261 258 # to use it as if it were. 262 259 continue 263 if f.unique and self.cleaned_data.get(name) is not None: 260 if self.cleaned_data.get(name) is None: 261 continue 262 if f.unique: 264 263 unique_checks.append((name,)) 264 if f.unique_for_date and self.cleaned_data.get(f.unique_for_date) is not None: 265 date_checks.append(('date', name, f.unique_for_date)) 266 if f.unique_for_year and self.cleaned_data.get(f.unique_for_year) is not None: 267 date_checks.append(('year', name, f.unique_for_year)) 268 if f.unique_for_month and self.cleaned_data.get(f.unique_for_month) is not None: 269 date_checks.append(('month', name, f.unique_for_month)) 270 return unique_checks, date_checks 271 272 273 def clean(self): 274 self.validate_unique() 275 return self.cleaned_data 276 277 def validate_unique(self): 278 unique_checks, date_checks = self._get_unique_checks() 279 280 form_errors = [] 281 bad_fields = set() 282 283 field_errors, global_errors = self._preform_unique_checks(unique_checks) 284 bad_fields.union(field_errors) 285 form_errors.extend(global_errors) 286 287 field_errors, global_errors = self._preform_date_checks(date_checks) 288 bad_fields.union(field_errors) 289 form_errors.extend(global_errors) 290 291 for field_name in bad_fields: 292 del self.cleaned_data[field_name] 293 if form_errors: 294 # Raise the unique together errors since they are considered 295 # form-wide. 296 raise ValidationError(form_errors) 265 297 298 def _preform_unique_checks(self, unique_checks): 266 299 bad_fields = set() 300 form_errors = [] 301 267 302 for unique_check in unique_checks: 268 303 # Try to look up an existing object with the same values as this 269 304 # object's values for all the unique field. … … class BaseModelForm(BaseForm): 288 323 # This cute trick with extra/values is the most efficient way to 289 324 # tell if a particular query returns any results. 290 325 if qs.extra(select={'a': 1}).values('a').order_by(): 291 model_name = capfirst(self.instance._meta.verbose_name)292 293 # A unique field294 326 if len(unique_check) == 1: 295 field_name = unique_check[0] 296 field_label = self.fields[field_name].label 297 # Insert the error into the error dict, very sneaky 298 self._errors[field_name] = ErrorList([ 299 _(u"%(model_name)s with this %(field_label)s already exists.") % \ 300 {'model_name': unicode(model_name), 301 'field_label': unicode(field_label)} 302 ]) 303 # unique_together 327 self._errors[unique_check[0]] = ErrorList([self.unique_error_message(unique_check)]) 304 328 else: 305 field_labels = [self.fields[field_name].label for field_name in unique_check] 306 field_labels = get_text_list(field_labels, _('and')) 307 form_errors.append( 308 _(u"%(model_name)s with this %(field_label)s already exists.") % \ 309 {'model_name': unicode(model_name), 310 'field_label': unicode(field_labels)} 311 ) 329 form_errors.append(self.unique_error_message(unique_check)) 312 330 313 331 # Mark these fields as needing to be removed from cleaned data 314 332 # later. 315 333 for field_name in unique_check: 316 334 bad_fields.add(field_name) 335 return bad_fields, form_errors 336 337 def _preform_date_checks(self, date_checks): 338 bad_fields = set() 339 for lookup_type, field, unique_for in date_checks: 340 lookup_kwargs = {} 341 # there's a ticket to add a date lookup, we can remove this special 342 # case if that makes it's way in 343 if lookup_type == 'date': 344 date = self.cleaned_data[unique_for] 345 lookup_kwargs['%s__day' % unique_for] = date.day 346 lookup_kwargs['%s__month' % unique_for] = date.month 347 lookup_kwargs['%s__year' % unique_for] = date.year 348 else: 349 lookup_kwargs['%s__%s' % (unique_for, lookup_type)] = getattr(self.cleaned_data[unique_for], lookup_type) 350 lookup_kwargs[field] = self.cleaned_data[field] 351 352 qs = self.instance.__class__._default_manager.filter(**lookup_kwargs) 353 # Exclude the current object from the query if we are editing an 354 # instance (as opposed to creating a new one) 355 if self.instance.pk is not None: 356 qs = qs.exclude(pk=self.instance.pk) 357 358 # This cute trick with extra/values is the most efficient way to 359 # tell if a particular query returns any results. 360 if qs.extra(select={'a': 1}).values('a').order_by(): 361 self._errors[field] = ErrorList([ 362 self.date_error_message(lookup_type, field, unique_for) 363 ]) 364 bad_fields.add(field) 365 return bad_fields, [] 366 367 def date_error_message(self, lookup_type, field, unique_for): 368 return _(u"%(field_name)s must be unique for %(date_field)s %(lookup)s.") % { 369 'field_name': unicode(self.fields[field].label), 370 'date_field': unicode(self.fields[unique_for].label), 371 'lookup': lookup_type, 372 } 373 374 def unique_error_message(self, unique_check): 375 model_name = capfirst(self.instance._meta.verbose_name) 376 377 # A unique field 378 if len(unique_check) == 1: 379 field_name = unique_check[0] 380 field_label = self.fields[field_name].label 381 # Insert the error into the error dict, very sneaky 382 return _(u"%(model_name)s with this %(field_label)s already exists.") % { 383 'model_name': unicode(model_name), 384 'field_label': unicode(field_label) 385 } 386 # unique_together 387 else: 388 field_labels = [self.fields[field_name].label for field_name in unique_check] 389 field_labels = get_text_list(field_labels, _('and')) 390 return _(u"%(model_name)s with this %(field_label)s already exists.") % { 391 'model_name': unicode(model_name), 392 'field_label': unicode(field_labels) 393 } 317 394 318 for field_name in bad_fields:319 del self.cleaned_data[field_name]320 if form_errors:321 # Raise the unique together errors since they are considered322 # form-wide.323 raise ValidationError(form_errors)324 395 325 396 def save(self, commit=True): 326 397 """ -
tests/modeltests/model_forms/models.py
diff --git a/tests/modeltests/model_forms/models.py b/tests/modeltests/model_forms/models.py index 992bb90..d5353ae 100644
a b class ExplicitPK(models.Model): 189 189 def __unicode__(self): 190 190 return self.key 191 191 192 class Post(models.Model): 193 title = models.CharField(max_length=50, unique_for_date='posted', blank=True) 194 slug = models.CharField(max_length=50, unique_for_year='posted', blank=True) 195 subtitle = models.CharField(max_length=50, unique_for_month='posted', blank=True) 196 posted = models.DateField() 197 198 def __unicode__(self): 199 return self.name 200 192 201 __test__ = {'API_TESTS': """ 193 202 >>> from django import forms 194 203 >>> from django.forms.models import ModelForm, model_to_dict … … ValidationError: [u'Select a valid choice. z is not one of the available choices 1472 1481 <tr><th><label for="id_description">Description:</label></th><td><input type="text" name="description" id="id_description" /></td></tr> 1473 1482 <tr><th><label for="id_url">The URL:</label></th><td><input id="id_url" type="text" name="url" maxlength="40" /></td></tr> 1474 1483 1484 ### validation on unique_for_date 1485 1486 >>> p = Post.objects.create(title="Django 1.0 is released", slug="Django 1.0", subtitle="Finally", posted=datetime.date(2008, 9, 3)) 1487 >>> class PostForm(ModelForm): 1488 ... class Meta: 1489 ... model = Post 1490 1491 >>> f = PostForm({'title': "Django 1.0 is released", 'posted': '2008-09-03'}) 1492 >>> f.is_valid() 1493 False 1494 >>> f.errors 1495 {'title': [u'Title must be unique for Posted date.']} 1496 >>> f = PostForm({'title': "Work on Django 1.1 begins", 'posted': '2008-09-03'}) 1497 >>> f.is_valid() 1498 True 1499 >>> f = PostForm({'title': "Django 1.0 is released", 'posted': '2008-09-04'}) 1500 >>> f.is_valid() 1501 True 1502 >>> f = PostForm({'slug': "Django 1.0", 'posted': '2008-01-01'}) 1503 >>> f.is_valid() 1504 False 1505 >>> f.errors 1506 {'slug': [u'Slug must be unique for Posted year.']} 1507 >>> f = PostForm({'subtitle': "Finally", 'posted': '2008-09-30'}) 1508 >>> f.is_valid() 1509 False 1510 >>> f.errors 1511 {'subtitle': [u'Subtitle must be unique for Posted month.']} 1512 >>> f = PostForm({'subtitle': "Finally", "title": "Django 1.0 is released", "slug": "Django 1.0", 'posted': '2008-09-03'}, instance=p) 1513 >>> f.is_valid() 1514 True 1515 1475 1516 # Clean up 1476 1517 >>> import shutil 1477 1518 >>> shutil.rmtree(temp_storage_dir)