From 126e0ec0c3096ae5c631b00b29ff9e9f01ca15c2 Mon Sep 17 00:00:00 2001 From: Adrian Holovaty Date: Mon, 27 Nov 2006 03:49:19 +0000 Subject: [PATCH] Fixed #3026 -- newforms: Form class now suppresses validation and errors if no data (or None) is passed in. Validation still happens if you pass in an empty dictionary. Also updated unit tests. Thanks, SmileyChris git-svn-id: http://code.djangoproject.com/svn/django/trunk@4117 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/newforms/forms.py | 23 ++-- tests/regressiontests/forms/tests.py | 152 +++++++++++++++++++++------ 2 files changed, 126 insertions(+), 49 deletions(-) diff --git a/django/newforms/forms.py b/django/newforms/forms.py index f58ce2c563..bf05e38fc2 100644 --- a/django/newforms/forms.py +++ b/django/newforms/forms.py @@ -36,6 +36,7 @@ class Form(object): __metaclass__ = DeclarativeFieldsMetaclass def __init__(self, data=None, auto_id=False): # TODO: prefix stuff + self.ignore_errors = data is None self.data = data or {} self.auto_id = auto_id self.clean_data = None # Stores the data after clean() has been called. @@ -65,22 +66,13 @@ class Form(object): def is_valid(self): """ - Returns True if the form has no errors. Otherwise, False. This exists - solely for convenience, so client code can use positive logic rather - than confusing negative logic ("if not form.errors"). + Returns True if the form has no errors. Otherwise, False. If errors are + being ignored, returns False. """ - return not bool(self.errors) + return not self.ignore_errors and not bool(self.errors) def as_table(self): "Returns this form rendered as HTML s -- excluding the
." - return u'\n'.join([u'%s:%s' % (pretty_name(name), BoundField(self, field, name)) for name, field in self.fields.items()]) - - def as_ul(self): - "Returns this form rendered as HTML
  • s -- excluding the ." - return u'\n'.join([u'
  • %s: %s
  • ' % (pretty_name(name), BoundField(self, field, name)) for name, field in self.fields.items()]) - - def as_table_with_errors(self): - "Returns this form rendered as HTML s, with errors." output = [] if self.errors.get(NON_FIELD_ERRORS): # Errors not corresponding to a particular field are displayed at the top. @@ -92,8 +84,8 @@ class Form(object): output.append(u'%s:%s' % (pretty_name(name), bf)) return u'\n'.join(output) - def as_ul_with_errors(self): - "Returns this form rendered as HTML
  • s, with errors." + def as_ul(self): + "Returns this form rendered as HTML
  • s -- excluding the ." output = [] if self.errors.get(NON_FIELD_ERRORS): # Errors not corresponding to a particular field are displayed at the top. @@ -113,6 +105,9 @@ class Form(object): """ self.clean_data = {} errors = ErrorDict() + if self.ignore_errors: # Stop further processing. + self.__errors = errors + return for name, field in self.fields.items(): value = self.data.get(name, None) try: diff --git a/tests/regressiontests/forms/tests.py b/tests/regressiontests/forms/tests.py index 3d7543badd..8de27dee51 100644 --- a/tests/regressiontests/forms/tests.py +++ b/tests/regressiontests/forms/tests.py @@ -1137,31 +1137,8 @@ u'' ... first_name = CharField() ... last_name = CharField() ... birthday = DateField() ->>> p = Person() ->>> print p -First name: -Last name: -Birthday: ->>> print p.as_table() -First name: -Last name: -Birthday: ->>> print p.as_ul() -
  • First name:
  • -
  • Last name:
  • -
  • Birthday:
  • ->>> print p.as_table_with_errors() - -First name: - -Last name: - -Birthday: ->>> print p.as_ul_with_errors() -
  • First name:
  • -
  • Last name:
  • -
  • Birthday:
  • +Pass a dictionary to a Form's __init__(). >>> p = Person({'first_name': u'John', 'last_name': u'Lennon', 'birthday': u'1940-10-9'}) >>> p.errors {} @@ -1189,12 +1166,58 @@ u'' Last name: Birthday: +Empty dictionaries are valid, too. +>>> p = Person({}) +>>> p.errors +{'first_name': [u'This field is required.'], 'last_name': [u'This field is required.'], 'birthday': [u'This field is required.']} +>>> p.is_valid() +False +>>> print p + +First name: + +Last name: + +Birthday: +>>> print p.as_table() + +First name: + +Last name: + +Birthday: +>>> print p.as_ul() +
  • First name:
  • +
  • Last name:
  • +
  • Birthday:
  • + +If you don't pass any values to the Form's __init__(), or if you pass None, +the Form won't do any validation. Form.errors will be an empty dictionary *but* +Form.is_valid() will return False. +>>> p = Person() +>>> p.errors +{} +>>> p.is_valid() +False +>>> print p +First name: +Last name: +Birthday: +>>> print p.as_table() +First name: +Last name: +Birthday: +>>> print p.as_ul() +
  • First name:
  • +
  • Last name:
  • +
  • Birthday:
  • + Unicode values are handled properly. ->>> p = Person({'first_name': u'John', 'last_name': u'\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111'}) +>>> p = Person({'first_name': u'John', 'last_name': u'\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111', 'birthday': '1940-10-9'}) >>> p.as_table() -u'First name:\nLast name:\nBirthday:' +u'First name:\nLast name:\nBirthday:' >>> p.as_ul() -u'
  • First name:
  • \n
  • Last name:
  • \n
  • Birthday:
  • ' +u'
  • First name:
  • \n
  • Last name:
  • \n
  • Birthday:
  • ' >>> p = Person({'last_name': u'Lennon'}) >>> p.errors @@ -1375,7 +1398,10 @@ MultipleChoiceField is a special case, as its data is required to be a list: There are a couple of ways to do multiple-field validation. If you want the validation message to be associated with a particular field, implement the clean_XXX() method on the Form, where XXX is the field name. As in -Field.clean(), the clean_XXX() method should return the cleaned value: +Field.clean(), the clean_XXX() method should return the cleaned value. In the +clean_XXX() method, you have access to self.clean_data, which is a dictionary +of all the data that has been cleaned *so far*, in order by the fields, +including the current field (e.g., the field XXX if you're in clean_XXX()). >>> class UserRegistration(Form): ... username = CharField(max_length=10) ... password1 = CharField(widget=PasswordInput) @@ -1386,6 +1412,9 @@ Field.clean(), the clean_XXX() method should return the cleaned value: ... return self.clean_data['password2'] >>> f = UserRegistration() >>> f.errors +{} +>>> f = UserRegistration({}) +>>> f.errors {'username': [u'This field is required.'], 'password1': [u'This field is required.'], 'password2': [u'This field is required.']} >>> f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'bar'}) >>> f.errors @@ -1399,8 +1428,10 @@ Field.clean(), the clean_XXX() method should return the cleaned value: 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__'. Note that -Form.clean() still needs to return a dictionary of all clean data: +special-case association with the field named '__all__'. +Note that in Form.clean(), you have access to self.clean_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. >>> class UserRegistration(Form): ... username = CharField(max_length=10) ... password1 = CharField(widget=PasswordInput) @@ -1410,9 +1441,15 @@ Form.clean() still needs to return a dictionary of all clean data: ... raise ValidationError(u'Please make sure your passwords match.') ... return self.clean_data >>> f = UserRegistration() +>>> f.errors +{} +>>> f = UserRegistration({}) >>> print f.as_table() + Username: + Password1: + Password2: >>> f.errors {'username': [u'This field is required.'], 'password1': [u'This field is required.'], 'password2': [u'This field is required.']} @@ -1420,15 +1457,11 @@ Form.clean() still needs to return a dictionary of all clean data: >>> f.errors {'__all__': [u'Please make sure your passwords match.']} >>> print f.as_table() -Username: -Password1: -Password2: ->>> print f.as_table_with_errors() Username: Password1: Password2: ->>> print f.as_ul_with_errors() +>>> print f.as_ul()
  • Username:
  • Password1:
  • @@ -1486,6 +1519,55 @@ A Form's fields are displayed in the same order in which they were defined. Field12: Field13: Field14: + +# Sample form processing (as if in a view) #################################### + +>>> from django.template import Template, Context +>>> class UserRegistration(Form): +... username = CharField(max_length=10) +... password1 = CharField(widget=PasswordInput) +... password2 = CharField(widget=PasswordInput) +... def clean(self): +... if self.clean_data.get('password1') and self.clean_data.get('password2') and self.clean_data['password1'] != self.clean_data['password2']: +... raise ValidationError(u'Please make sure your passwords match.') +... return self.clean_data +>>> def my_function(method, post_data): +... if method == 'POST': +... form = UserRegistration(post_data) +... else: +... form = UserRegistration() +... if form.is_valid(): +... return 'VALID' +... t = Template('
    \n\n{{ form }}\n
    \n\n
    ') +... return t.render(Context({'form': form})) + +Case 1: GET (an empty form, with no errors). +>>> print my_function('GET', {}) +
    + + + + +
    Username:
    Password1:
    Password2:
    + +
    + +Case 2: POST with erroneous data (a redisplayed form, with errors). +>>> print my_function('POST', {'username': 'this-is-a-long-username', 'password1': 'foo', 'password2': 'bar'}) +
    + + + + + + +
    • Please make sure your passwords match.
    • Ensure this value has at most 10 characters.
    Username:
    Password1:
    Password2:
    + +
    + +Case 3: POST with valid data (the success message). +>>> print my_function('POST', {'username': 'adrian', 'password1': 'secret', 'password2': 'secret'}) +VALID """ if __name__ == "__main__":