From 7f79fae7c686cbaf4b0f6d22feb29445af206677 Mon Sep 17 00:00:00 2001
From: Sebastian Noack <s.noack@placement24.com>
Date: Thu, 6 Sep 2012 10:47:52 +0200
Subject: [PATCH] Made ModelForm correctly handle unique checks for parent
models.
See http://code.djangoproject.com/ticket/18912
---
django/forms/models.py | 39 ++++++++++++++++++++++-----------------
1 file changed, 22 insertions(+), 17 deletions(-)
diff --git a/django/forms/models.py b/django/forms/models.py
index cc43612..392e062 100644
a
|
b
|
class BaseModelForm(BaseForm):
|
227 | 227 | error_class, label_suffix, empty_permitted) |
228 | 228 | |
229 | 229 | def clean(self): |
230 | | self.validate_unique() |
| 230 | self.validate_unique(self.instance.__class__) |
231 | 231 | return self.cleaned_data |
232 | 232 | |
233 | | def validate_unique(self): |
234 | | unique_checks, date_checks = self._get_unique_checks() |
| 233 | def validate_unique(self, model): |
| 234 | unique_checks, date_checks = self._get_unique_checks(model) |
235 | 235 | form_errors = [] |
236 | 236 | bad_fields = set() |
237 | 237 | |
238 | | field_errors, global_errors = self._perform_unique_checks(unique_checks) |
| 238 | field_errors, global_errors = self._perform_unique_checks(model, unique_checks) |
239 | 239 | bad_fields.union(field_errors) |
240 | 240 | form_errors.extend(global_errors) |
241 | 241 | |
242 | | field_errors, global_errors = self._perform_date_checks(date_checks) |
| 242 | field_errors, global_errors = self._perform_date_checks(model, date_checks) |
243 | 243 | bad_fields.union(field_errors) |
244 | 244 | form_errors.extend(global_errors) |
245 | 245 | |
… |
… |
class BaseModelForm(BaseForm):
|
250 | 250 | # form-wide. |
251 | 251 | raise ValidationError(form_errors) |
252 | 252 | |
253 | | def _get_unique_checks(self): |
| 253 | for parent_model in model._meta.parents: |
| 254 | self.validate_unique(parent_model) |
| 255 | |
| 256 | def _get_unique_checks(self, model): |
254 | 257 | from django.db.models.fields import FieldDoesNotExist, Field as ModelField |
255 | 258 | |
256 | 259 | # Gather a list of checks to perform. We only perform unique checks |
… |
… |
class BaseModelForm(BaseForm):
|
262 | 265 | unique_checks = [] |
263 | 266 | # these are checks for the unique_for_<date/year/month> |
264 | 267 | date_checks = [] |
265 | | for check in self.instance._meta.unique_together[:]: |
| 268 | for check in model._meta.unique_together[:]: |
266 | 269 | fields_on_form = [field for field in check if self.cleaned_data.get(field) is not None] |
267 | 270 | if len(fields_on_form) == len(check): |
268 | 271 | unique_checks.append(check) |
… |
… |
class BaseModelForm(BaseForm):
|
271 | 274 | # the list of checks. Again, skip empty fields and any that did not validate. |
272 | 275 | for name in self.fields: |
273 | 276 | try: |
274 | | f = self.instance._meta.get_field_by_name(name)[0] |
| 277 | f, parent_model, _, _ = model._meta.get_field_by_name(name) |
275 | 278 | except FieldDoesNotExist: |
276 | 279 | # This is an extra field that's not on the ModelForm, ignore it |
277 | 280 | continue |
… |
… |
class BaseModelForm(BaseForm):
|
281 | 284 | # get_field_by_name found it, but it is not a Field so do not proceed |
282 | 285 | # to use it as if it were. |
283 | 286 | continue |
| 287 | if parent_model: |
| 288 | continue |
284 | 289 | if self.cleaned_data.get(name) is None: |
285 | 290 | continue |
286 | 291 | if f.unique: |
… |
… |
class BaseModelForm(BaseForm):
|
294 | 299 | return unique_checks, date_checks |
295 | 300 | |
296 | 301 | |
297 | | def _perform_unique_checks(self, unique_checks): |
| 302 | def _perform_unique_checks(self, model, unique_checks): |
298 | 303 | bad_fields = set() |
299 | 304 | form_errors = [] |
300 | 305 | |
… |
… |
class BaseModelForm(BaseForm):
|
312 | 317 | lookup_value = lookup_value.pk |
313 | 318 | lookup_kwargs[str(field_name)] = lookup_value |
314 | 319 | |
315 | | qs = self.instance.__class__._default_manager.filter(**lookup_kwargs) |
| 320 | qs = model._default_manager.filter(**lookup_kwargs) |
316 | 321 | |
317 | 322 | # Exclude the current object from the query if we are editing an |
318 | 323 | # instance (as opposed to creating a new one) |
… |
… |
class BaseModelForm(BaseForm):
|
323 | 328 | # tell if a particular query returns any results. |
324 | 329 | if qs.extra(select={'a': 1}).values('a').order_by(): |
325 | 330 | if len(unique_check) == 1: |
326 | | self._errors[unique_check[0]] = ErrorList([self.unique_error_message(unique_check)]) |
| 331 | self._errors[unique_check[0]] = ErrorList([self.unique_error_message(model, unique_check)]) |
327 | 332 | else: |
328 | | form_errors.append(self.unique_error_message(unique_check)) |
| 333 | form_errors.append(self.unique_error_message(model, unique_check)) |
329 | 334 | |
330 | 335 | # Mark these fields as needing to be removed from cleaned data |
331 | 336 | # later. |
… |
… |
class BaseModelForm(BaseForm):
|
333 | 338 | bad_fields.add(field_name) |
334 | 339 | return bad_fields, form_errors |
335 | 340 | |
336 | | def _perform_date_checks(self, date_checks): |
| 341 | def _perform_date_checks(self, model, date_checks): |
337 | 342 | bad_fields = set() |
338 | 343 | for lookup_type, field, unique_for in date_checks: |
339 | 344 | lookup_kwargs = {} |
… |
… |
class BaseModelForm(BaseForm):
|
348 | 353 | lookup_kwargs['%s__%s' % (unique_for, lookup_type)] = getattr(self.cleaned_data[unique_for], lookup_type) |
349 | 354 | lookup_kwargs[field] = self.cleaned_data[field] |
350 | 355 | |
351 | | qs = self.instance.__class__._default_manager.filter(**lookup_kwargs) |
| 356 | qs = model._default_manager.filter(**lookup_kwargs) |
352 | 357 | # Exclude the current object from the query if we are editing an |
353 | 358 | # instance (as opposed to creating a new one) |
354 | 359 | if self.instance.pk is not None: |
… |
… |
class BaseModelForm(BaseForm):
|
370 | 375 | 'lookup': lookup_type, |
371 | 376 | } |
372 | 377 | |
373 | | def unique_error_message(self, unique_check): |
374 | | model_name = capfirst(self.instance._meta.verbose_name) |
| 378 | def unique_error_message(self, model, unique_check): |
| 379 | model_name = capfirst(model._meta.verbose_name) |
375 | 380 | |
376 | 381 | # A unique field |
377 | 382 | if len(unique_check) == 1: |
… |
… |
class BaseModelFormSet(BaseFormSet):
|
532 | 537 | break |
533 | 538 | else: |
534 | 539 | return |
535 | | unique_checks, date_checks = form._get_unique_checks() |
| 540 | unique_checks, date_checks = form._get_unique_checks(self.instance.__class__) |
536 | 541 | errors = [] |
537 | 542 | # Do each of the unique checks (unique and unique_together) |
538 | 543 | for unique_check in unique_checks: |