Index: django/newforms/util.py =================================================================== --- django/newforms/util.py (revision 7321) +++ django/newforms/util.py (working copy) @@ -50,11 +50,12 @@ return repr([force_unicode(e) for e in self]) class ValidationError(Exception): - def __init__(self, message): + def __init__(self, message, field_name=None): """ ValidationError can be passed any object that can be printed (usually a string) or a list of objects. """ + self.field_name = field_name if isinstance(message, list): self.messages = ErrorList([smart_unicode(msg) for msg in message]) else: Index: django/newforms/forms.py =================================================================== --- django/newforms/forms.py (revision 7321) +++ django/newforms/forms.py (working copy) @@ -215,7 +215,11 @@ try: self.cleaned_data = self.clean() except ValidationError, e: - self._errors[NON_FIELD_ERRORS] = e.messages + name = e.field_name or NON_FIELD_ERRORS + if e.field_name and name in self._errors: + self._errors[name] = ErrorList(self._errors[name] + e.messages) + else: + self._errors[name] = e.messages if self._errors: delattr(self, 'cleaned_data') Index: tests/regressiontests/forms/forms.py =================================================================== --- tests/regressiontests/forms/forms.py (revision 7321) +++ tests/regressiontests/forms/forms.py (working copy) @@ -611,8 +611,11 @@ Another way of doing multiple-field validation is by implementing the Form's clean() method. If you do this, any ValidationError raised by that -method will not be associated with a particular field; it will have a -special-case association with the field named '__all__'. +method either will not be associated with a particular field +and will have a special-case association with the field named '__all__'; +or if you specify the invalid field explicitly by passing the 'field_name' +parameter to the error constructor, it will be associated with the field in +similar manner to clean_XXX(). Note that in Form.clean(), you have access to self.cleaned_data, a dictionary of all the fields/values that have *not* raised a ValidationError. Also note Form.clean() is required to return a dictionary of all clean data. @@ -653,6 +656,59 @@ >>> f.cleaned_data {'username': u'adrian', 'password1': u'foo', 'password2': u'foo'} +The following is a somewhat contrived example that tests specifying the +invalid field in clean(). Note that default values in self.cleaned_data.get() +are neccessary to test if errors are appended correctly to base field errors. + +>>> class EmailRegistration(Form): +... username = CharField(max_length=10) +... email1 = EmailField() +... email2 = EmailField() +... def clean(self): +... email1 = self.cleaned_data.get('email1', 'foo') +... email2 = self.cleaned_data.get('email2', 'bar') +... if email1 != email2: +... raise ValidationError(u'Please make sure the email addresses match.', field_name = 'email2') +... return self.cleaned_data +... +>>> f = EmailRegistration(auto_id=False) +>>> f.errors +{} +>>> f = EmailRegistration({}, auto_id=False) +>>> f.errors +{'username': [u'This field is required.'], 'email2': [u'This field is required.', u'Please make sure the email addresses match.'], 'email1': [u'This field is required.']} +>>> print f.as_ul() +