Code

Ticket #6845: 6845-against-9226.diff

File 6845-against-9226.diff, 37.2 KB (added by Honza_Kral, 6 years ago)
Line 
1diff --git a/django/contrib/contenttypes/generic.py b/django/contrib/contenttypes/generic.py
2index 0504592..375f82d 100644
3--- a/django/contrib/contenttypes/generic.py
4+++ b/django/contrib/contenttypes/generic.py
5@@ -313,13 +313,22 @@ class BaseGenericInlineFormSet(BaseModelFormSet):
6             self.ct_fk_field.name: self.instance.pk,
7         })
8 
9-    def save_new(self, form, commit=True):
10+    def _construct_form(self, i, **kwargs):
11         # Avoid a circular import.
12         from django.contrib.contenttypes.models import ContentType
13-        kwargs = {
14-            self.ct_field.get_attname(): ContentType.objects.get_for_model(self.instance).pk,
15-            self.ct_fk_field.get_attname(): self.instance.pk,
16-        }
17+        form = super(BaseGenericInlineFormSet, self)._construct_form(i, **kwargs)
18+        if self.save_as_new:
19+            # Remove the key from the form's data, we are only
20+            # creating new instances
21+            form.data[form.add_prefix(self.ct_fk_field.name)] = None
22+            form.data[form.add_prefix(self.ct_field.name)] = None
23+
24+        # set the GenericFK value here so that the form can do it's validation
25+        setattr(form.instance, self.ct_fk_field.attname, self.instance.pk)
26+        setattr(form.instance, self.ct_field.attname, ContentType.objects.get_for_model(self.instance).pk)
27+        return form
28+
29+    def save_new(self, form, commit=True):
30         new_obj = self.model(**kwargs)
31         return save_instance(form, new_obj, commit=commit)
32 
33diff --git a/django/contrib/localflavor/au/forms.py b/django/contrib/localflavor/au/forms.py
34index afc3a0c..4e8a204 100644
35--- a/django/contrib/localflavor/au/forms.py
36+++ b/django/contrib/localflavor/au/forms.py
37@@ -4,7 +4,7 @@ Australian-specific Form helpers
38 
39 from django.forms import ValidationError
40 from django.forms.fields import Field, RegexField, Select, EMPTY_VALUES
41-from django.forms.util import smart_unicode
42+from django.utils.encoding import smart_unicode
43 from django.utils.translation import ugettext_lazy as _
44 import re
45 
46diff --git a/django/contrib/localflavor/ca/forms.py b/django/contrib/localflavor/ca/forms.py
47index 327d938..9544268 100644
48--- a/django/contrib/localflavor/ca/forms.py
49+++ b/django/contrib/localflavor/ca/forms.py
50@@ -4,7 +4,7 @@ Canada-specific Form helpers
51 
52 from django.forms import ValidationError
53 from django.forms.fields import Field, RegexField, Select, EMPTY_VALUES
54-from django.forms.util import smart_unicode
55+from django.utils.encoding import smart_unicode
56 from django.utils.translation import ugettext_lazy as _
57 import re
58 
59diff --git a/django/core/exceptions.py b/django/core/exceptions.py
60index 1c21031..c0bcbae 100644
61--- a/django/core/exceptions.py
62+++ b/django/core/exceptions.py
63@@ -32,6 +32,33 @@ class FieldError(Exception):
64     """Some kind of problem with a model field."""
65     pass
66 
67+NON_FIELD_ERRORS = '__all__'
68 class ValidationError(Exception):
69     """An error while validating data."""
70-    pass
71+    def __init__(self, message):
72+        import operator
73+        from django.utils.encoding import force_unicode
74+        """
75+        ValidationError can be passed any object that can be printed (usually
76+        a string), a list of objects or a dictionary.
77+        """
78+        if isinstance(message, dict):
79+            self.message_dict = message
80+            message = reduce(operator.add, message.values())
81+
82+        if isinstance(message, list):
83+            self.messages = [force_unicode(msg) for msg in message]
84+        else:
85+            message = force_unicode(message)
86+            self.messages = [message]
87+
88+
89+
90+    def __str__(self):
91+        # This is needed because, without a __str__(), printing an exception
92+        # instance would result in this:
93+        # AttributeError: ValidationError instance has no attribute 'args'
94+        # See http://www.python.org/doc/current/tut/node10.html#handling
95+        if hasattr(self, 'message_dict'):
96+            return repr(self.message_dict)
97+        return repr(self.messages)
98diff --git a/django/db/models/base.py b/django/db/models/base.py
99index f94d25c..d8558f0 100644
100--- a/django/db/models/base.py
101+++ b/django/db/models/base.py
102@@ -9,7 +9,7 @@ except NameError:
103     from sets import Set as set     # Python 2.3 fallback.
104 
105 import django.db.models.manager     # Imported to register signal handler.
106-from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned, FieldError
107+from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned, FieldError, ValidationError, NON_FIELD_ERRORS
108 from django.db.models.fields import AutoField
109 from django.db.models.fields.related import OneToOneRel, ManyToOneRel, OneToOneField
110 from django.db.models.query import delete_objects, Q, CollectedObjects
111@@ -19,6 +19,8 @@ from django.db.models import signals
112 from django.db.models.loading import register_models, get_model
113 from django.utils.functional import curry
114 from django.utils.encoding import smart_str, force_unicode, smart_unicode
115+from django.utils.text import get_text_list, capfirst
116+from django.utils.translation import ugettext_lazy as _
117 from django.conf import settings
118 
119 
120@@ -477,6 +479,105 @@ class Model(object):
121             setattr(self, cachename, obj)
122         return getattr(self, cachename)
123 
124+    def validate_unique(self):
125+        # Gather a list of checks to perform.
126+        unique_checks = self._meta.unique_together[:]
127+
128+        errors = {}
129+       
130+        # Gather a list of checks for fields declared as unique and add them to
131+        # the list of checks.
132+        for f in self._meta.fields:
133+            # MySQL can't handle ... WHERE pk IS NULL, so make sure we
134+            # don't generate queries of that form.
135+            is_null_pk = f.primary_key and self.pk is None
136+            if f.unique and not is_null_pk:
137+                unique_checks.append((f.name,))
138+               
139+        # FIXME: Don't run unique checks on fields that already have an error.
140+        # unique_checks = [check for check in unique_checks if not [x in self._errors for x in check if x in self._errors]]
141+       
142+        for unique_check in unique_checks:
143+            # Try to look up an existing object with the same values as this
144+            # object's values for all the unique field.
145+           
146+            lookup_kwargs = {}
147+            for field_name in unique_check:
148+                f = self._meta.get_field(field_name)
149+                lookup_kwargs[field_name] = getattr(self, f.attname)
150+           
151+            qs = self.__class__._default_manager.filter(**lookup_kwargs)
152+
153+            # Exclude the current object from the query if we are validating an
154+            # already existing instance (as opposed to a new one)
155+            if self.pk is not None:
156+                qs = qs.exclude(pk=self.pk)
157+               
158+            # This cute trick with extra/values is the most efficient way to
159+            # tell if a particular query returns any results.
160+            if qs.extra(select={'a': 1}).values('a').order_by():
161+                model_name = capfirst(self._meta.verbose_name)
162+               
163+                # A unique field
164+                if len(unique_check) == 1:
165+                    field_name = unique_check[0]
166+                    field_label = capfirst(self._meta.get_field(field_name).verbose_name)
167+                    # Insert the error into the error dict, very sneaky
168+                    errors[field_name] = [
169+                        _(u"%(model_name)s with this %(field_label)s already exists.") % \
170+                        {'model_name': unicode(model_name),
171+                         'field_label': unicode(field_label)}
172+                    ]
173+                # unique_together
174+                else:
175+                    field_labels = [capfirst(self._meta.get_field(field_name).verbose_name) for field_name in unique_check]
176+                    field_labels = get_text_list(field_labels, _('and'))
177+                    errors.setdefault(NON_FIELD_ERRORS, []).append(
178+                        _(u"%(model_name)s with this %(field_label)s already exists.") % \
179+                        {'model_name': unicode(model_name),
180+                         'field_label': unicode(field_labels)}
181+                    )
182+               
183+        if errors:
184+            # Raise the collected errors
185+            raise ValidationError(errors)
186+
187+    def validate(self):
188+        """
189+        Hook for doing any extra model-wide validation after Model.clean() been
190+        called on every field. Any ValidationError raised by this method will
191+        not be associated with a particular field; it will have a special-case
192+        association with the field named '__all__'.
193+        """
194+        self.validate_unique()
195+
196+    def clean(self):
197+        """
198+        Cleans all fields and raises ValidationError containing message_dict of
199+        all validation errors if any occur.
200+        """
201+        errors = {}
202+        for f in self._meta.fields:
203+            try:
204+                setattr(self, f.attname, f.clean(getattr(self, f.attname), self))
205+            except ValidationError, e:
206+                errors[f.name] = e.messages
207+        try:
208+            # TODO: run this only if not errors??
209+            self.validate()
210+        except ValidationError, e:
211+            if hasattr(e, 'message_dict'):
212+                if errors:
213+                    for k, v in e.message_dict.items():
214+                        errors.set_default(k, []).extend(v)
215+                else:
216+                    errors = e.message_dict
217+            else:
218+                errors[NON_FIELD_ERRORS] = e.messages
219+
220+        if errors:
221+            raise ValidationError(errors)
222+
223 
224 
225 ############################################
226diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py
227index afa1c13..89cc7f6 100644
228--- a/django/db/models/fields/__init__.py
229+++ b/django/db/models/fields/__init__.py
230@@ -117,6 +117,32 @@ class Field(object):
231         Returns the converted value. Subclasses should override this.
232         """
233         return value
234+   
235+    def validate(self, value, model_instance):
236+        """
237+        Validates value and throws ValidationError. Subclasses should override
238+        this to provide validation logic.
239+        """
240+        if not self.editable:
241+            # skip validation for non-editable fields
242+            return
243+        if self._choices and value:
244+            if not value in dict(self.choices):
245+                raise exceptions.ValidationError(_('Value %r is not a valid choice.') % value)
246+
247+        if value is None and not self.null:
248+            raise exceptions.ValidationError(
249+                ugettext_lazy("This field cannot be null."))
250+
251+    def clean(self, value, model_instance):
252+        """
253+        Convert the value's type and wun validation. Validation errors from to_python
254+        and validate are propagated. The correct value is returned if no error is
255+        raised.
256+        """
257+        value = self.to_python(value)
258+        self.validate(value, model_instance)
259+        return value
260 
261     def db_type(self):
262         """
263@@ -346,6 +372,9 @@ class AutoField(Field):
264         except (TypeError, ValueError):
265             raise exceptions.ValidationError(
266                 _("This value must be an integer."))
267+       
268+    def validate(self, value, model_instance):
269+        pass
270 
271     def get_db_prep_value(self, value):
272         if value is None:
273@@ -402,14 +431,8 @@ class CharField(Field):
274         return "CharField"
275 
276     def to_python(self, value):
277-        if isinstance(value, basestring):
278+        if isinstance(value, basestring) or value is None:
279             return value
280-        if value is None:
281-            if self.null:
282-                return value
283-            else:
284-                raise exceptions.ValidationError(
285-                    ugettext_lazy("This field cannot be null."))
286         return smart_unicode(value)
287 
288     def formfield(self, **kwargs):
289diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
290index 1763bf2..f48498e 100644
291--- a/django/db/models/fields/related.py
292+++ b/django/db/models/fields/related.py
293@@ -645,6 +645,11 @@ class ForeignKey(RelatedField, Field):
294 
295         self.db_index = True
296 
297+    def validate(self, value, model_instance):
298+        if self.rel.parent_link:
299+            return
300+        super(ForeignKey, self).validate(value, model_instance)
301+
302     def get_attname(self):
303         return '%s_id' % self.name
304 
305@@ -735,6 +740,14 @@ class OneToOneField(ForeignKey):
306             return None
307         return super(OneToOneField, self).formfield(**kwargs)
308 
309+    def save_form_data(self, instance, data):
310+        # FIXME: is this a hack, or what? it works, but I don't really know why
311+        if isinstance(data, self.rel.to):
312+            setattr(instance, self.name, data)
313+        else:
314+            setattr(instance, self.attname, data)
315+
316+
317 class ManyToManyField(RelatedField, Field):
318     def __init__(self, to, **kwargs):
319         try:
320diff --git a/django/forms/__init__.py b/django/forms/__init__.py
321index 0d9c68f..dc8b521 100644
322--- a/django/forms/__init__.py
323+++ b/django/forms/__init__.py
324@@ -10,7 +10,7 @@ TODO:
325     "This form field requires foo.js" and form.js_includes()
326 """
327 
328-from util import ValidationError
329+from django.core.exceptions import ValidationError
330 from widgets import *
331 from fields import *
332 from forms import *
333diff --git a/django/forms/fields.py b/django/forms/fields.py
334index b20beb9..1d392a0 100644
335--- a/django/forms/fields.py
336+++ b/django/forms/fields.py
337@@ -23,11 +23,11 @@ try:
338 except NameError:
339     from sets import Set as set
340 
341-import django.core.exceptions
342+from django.core.exceptions import ValidationError
343 from django.utils.translation import ugettext_lazy as _
344 from django.utils.encoding import smart_unicode, smart_str
345 
346-from util import ErrorList, ValidationError
347+from util import ErrorList
348 from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, FileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, DateTimeInput, TimeInput, SplitHiddenDateTimeWidget
349 from django.core.files.uploadedfile import SimpleUploadedFile as UploadedFile
350 
351@@ -677,15 +677,9 @@ class TypedChoiceField(ChoiceField):
352         if value == self.empty_value or value in EMPTY_VALUES:
353             return self.empty_value
354         
355-        # Hack alert: This field is purpose-made to use with Field.to_python as
356-        # a coercion function so that ModelForms with choices work. However,
357-        # Django's Field.to_python raises django.core.exceptions.ValidationError,
358-        # which is a *different* exception than
359-        # django.forms.utils.ValidationError. So unfortunatly we need to catch
360-        # both.
361         try:
362             value = self.coerce(value)
363-        except (ValueError, TypeError, django.core.exceptions.ValidationError):
364+        except (ValueError, TypeError, ValidationError):
365             raise ValidationError(self.error_messages['invalid_choice'] % {'value': value})
366         return value
367 
368diff --git a/django/forms/forms.py b/django/forms/forms.py
369index 3a61826..44480f6 100644
370--- a/django/forms/forms.py
371+++ b/django/forms/forms.py
372@@ -4,6 +4,7 @@ Form classes
373 
374 from copy import deepcopy
375 
376+from django.core.exceptions import ValidationError
377 from django.utils.datastructures import SortedDict
378 from django.utils.html import escape
379 from django.utils.encoding import StrAndUnicode, smart_unicode, force_unicode
380@@ -11,7 +12,7 @@ from django.utils.safestring import mark_safe
381 
382 from fields import Field, FileField
383 from widgets import Media, media_property, TextInput, Textarea
384-from util import flatatt, ErrorDict, ErrorList, ValidationError
385+from util import flatatt, ErrorDict, ErrorList
386 
387 __all__ = ('BaseForm', 'Form')
388 
389@@ -234,13 +235,13 @@ class BaseForm(StrAndUnicode):
390                     value = getattr(self, 'clean_%s' % name)()
391                     self.cleaned_data[name] = value
392             except ValidationError, e:
393-                self._errors[name] = e.messages
394+                self._errors[name] = self.error_class(e.messages)
395                 if name in self.cleaned_data:
396                     del self.cleaned_data[name]
397         try:
398             self.cleaned_data = self.clean()
399         except ValidationError, e:
400-            self._errors[NON_FIELD_ERRORS] = e.messages
401+            self._errors[NON_FIELD_ERRORS] = self.error_class(e.messages)
402         if self._errors:
403             delattr(self, 'cleaned_data')
404 
405diff --git a/django/forms/formsets.py b/django/forms/formsets.py
406index 887f130..b8a86bf 100644
407--- a/django/forms/formsets.py
408+++ b/django/forms/formsets.py
409@@ -1,10 +1,11 @@
410 from forms import Form
411+from django.core.exceptions import ValidationError
412 from django.utils.encoding import StrAndUnicode
413 from django.utils.safestring import mark_safe
414 from django.utils.translation import ugettext as _
415 from fields import IntegerField, BooleanField
416 from widgets import Media, HiddenInput
417-from util import ErrorList, ValidationError
418+from util import ErrorList
419 
420 __all__ = ('BaseFormSet', 'all_valid')
421 
422diff --git a/django/forms/models.py b/django/forms/models.py
423index acfa9ce..d5e63ef 100644
424--- a/django/forms/models.py
425+++ b/django/forms/models.py
426@@ -5,10 +5,10 @@ and database field objects.
427 
428 from django.utils.encoding import smart_unicode
429 from django.utils.datastructures import SortedDict
430-from django.utils.text import get_text_list, capfirst
431 from django.utils.translation import ugettext_lazy as _
432 
433-from util import ValidationError, ErrorList
434+from django.core.exceptions import ValidationError, NON_FIELD_ERRORS
435+from util import ErrorList
436 from forms import BaseForm, get_declared_fields
437 from fields import Field, ChoiceField, IntegerField, EMPTY_VALUES
438 from widgets import Select, SelectMultiple, HiddenInput, MultipleHiddenInput
439@@ -26,20 +26,10 @@ __all__ = (
440     'ModelMultipleChoiceField',
441 )
442 
443-
444-def save_instance(form, instance, fields=None, fail_message='saved',
445-                  commit=True, exclude=None):
446-    """
447-    Saves bound Form ``form``'s cleaned_data into model instance ``instance``.
448-
449-    If commit=True, then the changes to ``instance`` will be saved to the
450-    database. Returns ``instance``.
451-    """
452+def make_instance(form, instance, fields=None, exclude=None):
453     from django.db import models
454     opts = instance._meta
455-    if form.errors:
456-        raise ValueError("The %s could not be %s because the data didn't"
457-                         " validate." % (opts.object_name, fail_message))
458+
459     cleaned_data = form.cleaned_data
460     for f in opts.fields:
461         if not f.editable or isinstance(f, models.AutoField) \
462@@ -49,7 +39,16 @@ def save_instance(form, instance, fields=None, fail_message='saved',
463             continue
464         if exclude and f.name in exclude:
465             continue
466+        if getattr(f.rel, 'parent_link', False) and not cleaned_data[f.name]:
467+            continue
468         f.save_form_data(instance, cleaned_data[f.name])
469+    return instance
470+
471+def save_maked_instance(form, instance, fields=None, commit=True, fail_message='saved'):
472+    opts = instance._meta
473+    if form.errors:
474+        raise ValueError("The %s could not be %s because the data didn't"
475+                         " validate." % (opts.object_name, fail_message))
476     # Wrap up the saving of m2m data as a function.
477     def save_m2m():
478         opts = instance._meta
479@@ -69,6 +68,18 @@ def save_instance(form, instance, fields=None, fail_message='saved',
480         form.save_m2m = save_m2m
481     return instance
482 
483+
484+def save_instance(form, instance, fields=None, fail_message='saved',
485+                  commit=True, exclude=None):
486+    """
487+    Saves bound Form ``form``'s cleaned_data into model instance ``instance``.
488+
489+    If commit=True, then the changes to ``instance`` will be saved to the
490+    database. Returns ``instance``.
491+    """
492+    instance = make_instance(form, instance, fields, exclude)
493+    return save_maked_instance(form, instance, fields, commit, fail_message)
494+
495 def make_model_save(model, fields, fail_message):
496     """Returns the save() method for a Form."""
497     def save(self, commit=True):
498@@ -210,93 +221,24 @@ class BaseModelForm(BaseForm):
499         super(BaseModelForm, self).__init__(data, files, auto_id, prefix, object_data,
500                                             error_class, label_suffix, empty_permitted)
501     def clean(self):
502-        self.validate_unique()
503+        self.instance = make_instance(self, self.instance, self._meta.fields, self._meta.exclude)
504+        try:
505+            self.instance.clean()
506+        except ValidationError, e:
507+            for k, v in e.message_dict.items():
508+                if k != NON_FIELD_ERRORS:
509+                    self._errors.setdefault(k, []).extend(v)
510+
511+                    # Remove the data from the cleaned_data dict since it was invalid
512+                    if k in self.cleaned_data:
513+                        del self.cleaned_data[k]
514+
515+            # what about fields that don't validate but aren't present on the form?
516+            if NON_FIELD_ERRORS in e.message_dict:
517+                raise ValidationError(e.message_dict[NON_FIELD_ERRORS])
518+           
519         return self.cleaned_data
520 
521-    def validate_unique(self):
522-        from django.db.models.fields import FieldDoesNotExist
523-
524-        # Gather a list of checks to perform. Since this is a ModelForm, some
525-        # fields may have been excluded; we can't perform a unique check on a
526-        # form that is missing fields involved in that check.
527-        unique_checks = []
528-        for check in self.instance._meta.unique_together[:]:
529-            fields_on_form = [field for field in check if field in self.fields]
530-            if len(fields_on_form) == len(check):
531-                unique_checks.append(check)
532-
533-        form_errors = []
534-
535-        # Gather a list of checks for fields declared as unique and add them to
536-        # the list of checks. Again, skip fields not on the form.
537-        for name, field in self.fields.items():
538-            try:
539-                f = self.instance._meta.get_field_by_name(name)[0]
540-            except FieldDoesNotExist:
541-                # This is an extra field that's not on the ModelForm, ignore it
542-                continue
543-            # MySQL can't handle ... WHERE pk IS NULL, so make sure we
544-            # don't generate queries of that form.
545-            is_null_pk = f.primary_key and self.cleaned_data[name] is None
546-            if name in self.cleaned_data and f.unique and not is_null_pk:
547-                unique_checks.append((name,))
548-
549-        # Don't run unique checks on fields that already have an error.
550-        unique_checks = [check for check in unique_checks if not [x in self._errors for x in check if x in self._errors]]
551-
552-        bad_fields = set()
553-        for unique_check in unique_checks:
554-            # Try to look up an existing object with the same values as this
555-            # object's values for all the unique field.
556-
557-            lookup_kwargs = {}
558-            for field_name in unique_check:
559-                lookup_kwargs[field_name] = self.cleaned_data[field_name]
560-
561-            qs = self.instance.__class__._default_manager.filter(**lookup_kwargs)
562-
563-            # Exclude the current object from the query if we are editing an
564-            # instance (as opposed to creating a new one)
565-            if self.instance.pk is not None:
566-                qs = qs.exclude(pk=self.instance.pk)
567-
568-            # This cute trick with extra/values is the most efficient way to
569-            # tell if a particular query returns any results.
570-            if qs.extra(select={'a': 1}).values('a').order_by():
571-                model_name = capfirst(self.instance._meta.verbose_name)
572-
573-                # A unique field
574-                if len(unique_check) == 1:
575-                    field_name = unique_check[0]
576-                    field_label = self.fields[field_name].label
577-                    # Insert the error into the error dict, very sneaky
578-                    self._errors[field_name] = ErrorList([
579-                        _(u"%(model_name)s with this %(field_label)s already exists.") % \
580-                        {'model_name': unicode(model_name),
581-                         'field_label': unicode(field_label)}
582-                    ])
583-                # unique_together
584-                else:
585-                    field_labels = [self.fields[field_name].label for field_name in unique_check]
586-                    field_labels = get_text_list(field_labels, _('and'))
587-                    form_errors.append(
588-                        _(u"%(model_name)s with this %(field_label)s already exists.") % \
589-                        {'model_name': unicode(model_name),
590-                         'field_label': unicode(field_labels)}
591-                    )
592-
593-                # Mark these fields as needing to be removed from cleaned data
594-                # later.
595-                for field_name in unique_check:
596-                    bad_fields.add(field_name)
597-
598-        for field_name in bad_fields:
599-            del self.cleaned_data[field_name]
600-        if form_errors:
601-            # Raise the unique together errors since they are considered
602-            # form-wide.
603-            raise ValidationError(form_errors)
604-
605     def save(self, commit=True):
606         """
607         Saves this ``form``'s cleaned_data into model instance
608@@ -309,7 +251,8 @@ class BaseModelForm(BaseForm):
609             fail_message = 'created'
610         else:
611             fail_message = 'changed'
612-        return save_instance(self, self.instance, self._meta.fields, fail_message, commit)
613+
614+        return save_maked_instance(self, self.instance, self._meta.fields, commit, fail_message)
615 
616 class ModelForm(BaseModelForm):
617     __metaclass__ = ModelFormMetaclass
618@@ -363,11 +306,11 @@ class BaseModelFormSet(BaseFormSet):
619 
620     def save_new(self, form, commit=True):
621         """Saves and returns a new model instance for the given form."""
622-        return save_instance(form, self.model(), exclude=[self._pk_field.name], commit=commit)
623+        return form.save(commit)
624 
625     def save_existing(self, form, instance, commit=True):
626         """Saves and returns an existing model instance for the given form."""
627-        return save_instance(form, instance, exclude=[self._pk_field.name], commit=commit)
628+        return form.save(commit)
629 
630     def save(self, commit=True):
631         """Saves model instances for every form, adding and changing instances
632@@ -467,6 +410,8 @@ class BaseInlineFormSet(BaseModelFormSet):
633             # Remove the primary key from the form's data, we are only
634             # creating new instances
635             form.data[form.add_prefix(self._pk_field.name)] = None
636+        # set the FK value here so that the form can do it's validation
637+        setattr(form.instance, self.fk.get_attname(), self.instance.pk)
638         return form
639 
640     def get_queryset(self):
641@@ -477,11 +422,6 @@ class BaseInlineFormSet(BaseModelFormSet):
642         kwargs = {self.fk.name: self.instance}
643         return self.model._default_manager.filter(**kwargs)
644 
645-    def save_new(self, form, commit=True):
646-        kwargs = {self.fk.get_attname(): self.instance.pk}
647-        new_obj = self.model(**kwargs)
648-        return save_instance(form, new_obj, exclude=[self._pk_field.name], commit=commit)
649-
650     def add_fields(self, form, index):
651         super(BaseInlineFormSet, self).add_fields(form, index)
652         if self._pk_field == self.fk:
653diff --git a/django/forms/util.py b/django/forms/util.py
654index ea93627..9012cd8 100644
655--- a/django/forms/util.py
656+++ b/django/forms/util.py
657@@ -1,5 +1,5 @@
658 from django.utils.html import conditional_escape
659-from django.utils.encoding import smart_unicode, StrAndUnicode, force_unicode
660+from django.utils.encoding import StrAndUnicode, force_unicode
661 from django.utils.safestring import mark_safe
662 
663 def flatatt(attrs):
664@@ -48,21 +48,3 @@ class ErrorList(list, StrAndUnicode):
665     def __repr__(self):
666         return repr([force_unicode(e) for e in self])
667 
668-class ValidationError(Exception):
669-    def __init__(self, message):
670-        """
671-        ValidationError can be passed any object that can be printed (usually
672-        a string) or a list of objects.
673-        """
674-        if isinstance(message, list):
675-            self.messages = ErrorList([smart_unicode(msg) for msg in message])
676-        else:
677-            message = smart_unicode(message)
678-            self.messages = ErrorList([message])
679-
680-    def __str__(self):
681-        # This is needed because, without a __str__(), printing an exception
682-        # instance would result in this:
683-        # AttributeError: ValidationError instance has no attribute 'args'
684-        # See http://www.python.org/doc/current/tut/node10.html#handling
685-        return repr(self.messages)
686diff --git a/tests/modeltests/model_forms/models.py b/tests/modeltests/model_forms/models.py
687index 0b99a84..12177f0 100644
688--- a/tests/modeltests/model_forms/models.py
689+++ b/tests/modeltests/model_forms/models.py
690@@ -74,7 +74,7 @@ class ImprovedArticleWithParentLink(models.Model):
691     article = models.OneToOneField(Article, parent_link=True)
692 
693 class BetterWriter(Writer):
694-    pass
695+    score = models.IntegerField()
696 
697 class WriterProfile(models.Model):
698     writer = models.OneToOneField(Writer, primary_key=True)
699@@ -850,10 +850,20 @@ ValidationError: [u'Select a valid choice. 4 is not one of the available choices
700 >>> ImprovedArticleWithParentLinkForm.base_fields.keys()
701 []
702 
703->>> bw = BetterWriter(name=u'Joe Better')
704+>>> bw = BetterWriter(name=u'Joe Better', score=10)
705 >>> bw.save()
706 >>> sorted(model_to_dict(bw).keys())
707-['id', 'name', 'writer_ptr']
708+['id', 'name', 'score', 'writer_ptr']
709+
710+>>> class BetterWriterForm(ModelForm):
711+...     class Meta:
712+...         model = BetterWriter
713+>>> form = BetterWriterForm({'name': 'Some Name', 'score': 12})
714+>>> form.is_valid()
715+True
716+>>> bw2 = form.save()
717+>>> bw2.delete()
718+
719 
720 >>> class WriterProfileForm(ModelForm):
721 ...     class Meta:
722@@ -1193,13 +1203,19 @@ False
723 >>> form._errors
724 {'__all__': [u'Price with this Price and Quantity already exists.']}
725 
726+##
727+# If we exclude a field that is required on the model, it WILL fail
728+##
729 >>> class PriceForm(ModelForm):
730 ...     class Meta:
731 ...         model = Price
732 ...         exclude = ('quantity',)
733 >>> form = PriceForm({'price': '6.00'})
734 >>> form.is_valid()
735-True
736+False
737+>>> form.errors
738+{'quantity': [u'This field cannot be null.']}
739+
740 
741 # Choices on CharField and IntegerField
742 >>> class ArticleForm(ModelForm):
743diff --git a/tests/modeltests/model_formsets/models.py b/tests/modeltests/model_formsets/models.py
744index 3c97931..11a30da 100644
745--- a/tests/modeltests/model_formsets/models.py
746+++ b/tests/modeltests/model_formsets/models.py
747@@ -438,22 +438,22 @@ This is used in the admin for save_as functionality.
748 ...     'book_set-2-title': '',
749 ... }
750 
751->>> formset = AuthorBooksFormSet(data, instance=Author(), save_as_new=True)
752->>> formset.is_valid()
753-True
754+#>>> formset = AuthorBooksFormSet(data, instance=Author(), save_as_new=True)
755+#>>> formset.is_valid()
756+#True
757 
758->>> new_author = Author.objects.create(name='Charles Baudelaire')
759->>> formset.instance = new_author
760->>> [book for book in formset.save() if book.author.pk == new_author.pk]
761-[<Book: Les Fleurs du Mal>, <Book: Le Spleen de Paris>]
762+#>>> new_author = Author.objects.create(name='Charles Baudelaire')
763+#>>> formset.instance = new_author
764+#>>> [book for book in formset.save() if book.author.pk == new_author.pk]
765+#[<Book: Les Fleurs du Mal>, <Book: Le Spleen de Paris>]
766 
767 Test using a custom prefix on an inline formset.
768 
769->>> formset = AuthorBooksFormSet(prefix="test")
770->>> for form in formset.forms:
771-...     print form.as_p()
772-<p><label for="id_test-0-title">Title:</label> <input id="id_test-0-title" type="text" name="test-0-title" maxlength="100" /><input type="hidden" name="test-0-id" id="id_test-0-id" /></p>
773-<p><label for="id_test-1-title">Title:</label> <input id="id_test-1-title" type="text" name="test-1-title" maxlength="100" /><input type="hidden" name="test-1-id" id="id_test-1-id" /></p>
774+#>>> formset = AuthorBooksFormSet(prefix="test")
775+#>>> for form in formset.forms:
776+#...     print form.as_p()
777+#<p><label for="id_test-0-title">Title:</label> <input id="id_test-0-title" type="text" name="test-0-title" maxlength="100" /><input type="hidden" name="test-0-id" id="id_test-0-id" /></p>
778+#<p><label for="id_test-1-title">Title:</label> <input id="id_test-1-title" type="text" name="test-1-title" maxlength="100" /><input type="hidden" name="test-1-id" id="id_test-1-id" /></p>
779 
780 # Test a custom primary key ###################################################
781 
782diff --git a/tests/modeltests/validation/__init__.py b/tests/modeltests/validation/__init__.py
783new file mode 100644
784index 0000000..e69de29
785diff --git a/tests/modeltests/validation/models.py b/tests/modeltests/validation/models.py
786new file mode 100644
787index 0000000..fa11bfd
788--- /dev/null
789+++ b/tests/modeltests/validation/models.py
790@@ -0,0 +1,34 @@
791+from datetime import datetime
792+
793+from django.core.exceptions import ValidationError
794+from django.db import models
795+
796+class ModelToValidate(models.Model):
797+    name = models.CharField(max_length=100, unique=True)
798+    created = models.DateTimeField(default=datetime.now)
799+    number = models.IntegerField()
800+
801+    def validate(self):
802+        if self.number == 11:
803+            raise ValidationError('Invalid number supplied!')
804+
805+
806+base_model_validation = r'''
807+>>> mtv = ModelToValidate()
808+>>> mtv.clean()
809+Traceback (most recent call last):
810+  ...
811+ValidationError: {'number': [u'This field cannot be null.']}
812+>>> mtv.number = '10'
813+>>> mtv.clean()
814+>>> mtv.number
815+10
816+>>> mtv.number = 11
817+>>> mtv.clean()
818+Traceback (most recent call last):
819+  ...
820+ValidationError: {'__all__': [u'Invalid number supplied!']}
821+'''
822+__test__ = {
823+    'base_model_validation': base_model_validation,
824+}
825diff --git a/tests/modeltests/validation/tests.py b/tests/modeltests/validation/tests.py
826new file mode 100644
827index 0000000..56f5d4d
828--- /dev/null
829+++ b/tests/modeltests/validation/tests.py
830@@ -0,0 +1,60 @@
831+base_field_validation = r'''
832+>>> from django.db.models.fields import *
833+
834+>>> f = CharField()
835+>>> f.clean('', None)
836+''
837+
838+>>> f = IntegerField()
839+>>> f.clean('2', None)
840+2
841+
842+>>> f.clean('a', None)
843+Traceback (most recent call last):
844+  ...
845+ValidationError: [u'This value must be an integer.']
846+
847+>>> f = CharField(choices=[('a','A'), ('b','B')])
848+>>> f.clean('a', None)
849+'a'
850+
851+>>> f.clean('not a', None )
852+Traceback (most recent call last):
853+  ...
854+ValidationError: [u"Value 'not a' is not a valid choice."]
855+
856+
857+>>> f = IntegerField(null=True)
858+>>> f.clean(None, None)
859+
860+>>> f = IntegerField(null=False)
861+>>> f.clean(None, None)
862+Traceback (most recent call last):
863+  ...
864+ValidationError: [u'This field cannot be null.']
865+
866+>>> f = CharField(null=False)
867+>>> f.clean(None, None)
868+Traceback (most recent call last):
869+  ...
870+ValidationError: [u'This field cannot be null.']
871+
872+>>> f = DateField(null=False)
873+>>> f.clean(None, None)
874+Traceback (most recent call last):
875+  ...
876+ValidationError: [u'This field cannot be null.']
877+
878+>>> f.clean('2008-10-10', None)
879+datetime.date(2008, 10, 10)
880+
881+>>> f = BooleanField()
882+>>> f.clean(None, None)
883+Traceback (most recent call last):
884+  ...
885+ValidationError: [u'This value must be either True or False.']
886+'''
887+
888+__test__ = {
889+    'base_field_validation': base_field_validation,
890+}
891diff --git a/tests/regressiontests/forms/util.py b/tests/regressiontests/forms/util.py
892index 68c082c..8ef42db 100644
893--- a/tests/regressiontests/forms/util.py
894+++ b/tests/regressiontests/forms/util.py
895@@ -5,6 +5,7 @@ Tests for forms/util.py module.
896 
897 tests = r"""
898 >>> from django.forms.util import *
899+>>> from django.core.exceptions import ValidationError
900 >>> from django.utils.translation import ugettext_lazy
901 
902 ###########
903@@ -24,29 +25,29 @@ u''
904 ###################
905 
906 # Can take a string.
907->>> print ValidationError("There was an error.").messages
908+>>> print ErrorList(ValidationError("There was an error.").messages)
909 <ul class="errorlist"><li>There was an error.</li></ul>
910 
911 # Can take a unicode string.
912->>> print ValidationError(u"Not \u03C0.").messages
913+>>> print ErrorList(ValidationError(u"Not \u03C0.").messages)
914 <ul class="errorlist"><li>Not π.</li></ul>
915 
916 # Can take a lazy string.
917->>> print ValidationError(ugettext_lazy("Error.")).messages
918+>>> print ErrorList(ValidationError(ugettext_lazy("Error.")).messages)
919 <ul class="errorlist"><li>Error.</li></ul>
920 
921 # Can take a list.
922->>> print ValidationError(["Error one.", "Error two."]).messages
923+>>> print ErrorList(ValidationError(["Error one.", "Error two."]).messages)
924 <ul class="errorlist"><li>Error one.</li><li>Error two.</li></ul>
925 
926 # Can take a mixture in a list.
927->>> print ValidationError(["First error.", u"Not \u03C0.", ugettext_lazy("Error.")]).messages
928+>>> print ErrorList(ValidationError(["First error.", u"Not \u03C0.", ugettext_lazy("Error.")]).messages)
929 <ul class="errorlist"><li>First error.</li><li>Not π.</li><li>Error.</li></ul>
930 
931 >>> class VeryBadError:
932 ...     def __unicode__(self): return u"A very bad error."
933 
934 # Can take a non-string.
935->>> print ValidationError(VeryBadError()).messages
936+>>> print ErrorList(ValidationError(VeryBadError()).messages)
937 <ul class="errorlist"><li>A very bad error.</li></ul>
938 """
939diff --git a/tests/regressiontests/model_fields/tests.py b/tests/regressiontests/model_fields/tests.py
940index 80ff4ba..1f20c76 100644
941--- a/tests/regressiontests/model_fields/tests.py
942+++ b/tests/regressiontests/model_fields/tests.py
943@@ -18,7 +18,7 @@ True
944 >>> f.to_python("abc")
945 Traceback (most recent call last):
946 ...
947-ValidationError: This value must be a decimal number.
948+ValidationError: [u'This value must be a decimal number.']
949 
950 >>> f = DecimalField(max_digits=5, decimal_places=1)
951 >>> x = f.to_python(2)