diff --git a/tests/regressiontests/forms/extra.py b/tests/regressiontests/forms/extra.py new file mode 100644 index 0000000000..7f6175f649 --- /dev/null +++ b/tests/regressiontests/forms/extra.py @@ -0,0 +1,398 @@ +# -*- coding: utf-8 -*- +tests = r""" +>>> from django.newforms import * +>>> import datetime +>>> import time +>>> import re +>>> try: +... from decimal import Decimal +... except ImportError: +... from django.utils._decimal import Decimal + +############### +# Extra stuff # +############### + +The newforms library comes with some extra, higher-level Field and Widget +classes that demonstrate some of the library's abilities. + +# SelectDateWidget ############################################################ + +>>> from django.newforms.extras import SelectDateWidget +>>> w = SelectDateWidget(years=('2007','2008','2009','2010','2011','2012','2013','2014','2015','2016')) +>>> print w.render('mydate', '') + + + +>>> w.render('mydate', None) == w.render('mydate', '') +True +>>> print w.render('mydate', '2010-04-15') + + + + +Using a SelectDateWidget in a form: + +>>> class GetDate(Form): +... mydate = DateField(widget=SelectDateWidget) +>>> a = GetDate({'mydate_month':'4', 'mydate_day':'1', 'mydate_year':'2008'}) +>>> print a.is_valid() +True +>>> print a.cleaned_data['mydate'] +2008-04-01 + +As with any widget that implements get_value_from_datadict, +we must be prepared to accept the input from the "as_hidden" +rendering as well. + +>>> print a['mydate'].as_hidden() + +>>> b=GetDate({'mydate':'2008-4-1'}) +>>> print b.is_valid() +True +>>> print b.cleaned_data['mydate'] +2008-04-01 + + +# MultiWidget and MultiValueField ############################################# +# MultiWidgets are widgets composed of other widgets. They are usually +# combined with MultiValueFields - a field that is composed of other fields. +# MulitWidgets can themselved be composed of other MultiWidgets. +# SplitDateTimeWidget is one example of a MultiWidget. + +>>> class ComplexMultiWidget(MultiWidget): +... def __init__(self, attrs=None): +... widgets = ( +... TextInput(), +... SelectMultiple(choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))), +... SplitDateTimeWidget(), +... ) +... super(ComplexMultiWidget, self).__init__(widgets, attrs) +... +... def decompress(self, value): +... if value: +... data = value.split(',') +... return [data[0], data[1], datetime.datetime(*time.strptime(data[2], "%Y-%m-%d %H:%M:%S")[0:6])] +... return [None, None, None] +... def format_output(self, rendered_widgets): +... return u'\n'.join(rendered_widgets) +>>> w = ComplexMultiWidget() +>>> print w.render('name', 'some text,JP,2007-04-25 06:24:00') + + + + +>>> class ComplexField(MultiValueField): +... def __init__(self, required=True, widget=None, label=None, initial=None): +... fields = ( +... CharField(), +... MultipleChoiceField(choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))), +... SplitDateTimeField() +... ) +... super(ComplexField, self).__init__(fields, required, widget, label, initial) +... +... def compress(self, data_list): +... if data_list: +... return '%s,%s,%s' % (data_list[0],''.join(data_list[1]),data_list[2]) +... return None + +>>> f = ComplexField(widget=w) +>>> f.clean(['some text', ['J','P'], ['2007-04-25','6:24:00']]) +u'some text,JP,2007-04-25 06:24:00' +>>> f.clean(['some text',['X'], ['2007-04-25','6:24:00']]) +Traceback (most recent call last): +... +ValidationError: [u'Select a valid choice. X is not one of the available choices.'] + +# If insufficient data is provided, None is substituted +>>> f.clean(['some text',['JP']]) +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] + +>>> class ComplexFieldForm(Form): +... field1 = ComplexField(widget=w) +>>> f = ComplexFieldForm() +>>> print f + + + + +>>> f = ComplexFieldForm({'field1_0':'some text','field1_1':['J','P'], 'field1_2_0':'2007-04-25', 'field1_2_1':'06:24:00'}) +>>> print f + + + + +>>> f.cleaned_data +{'field1': u'some text,JP,2007-04-25 06:24:00'} + + +# IPAddressField ################################################################## + +>>> f = IPAddressField() +>>> f.clean('') +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] +>>> f.clean(None) +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] +>>> f.clean('127.0.0.1') +u'127.0.0.1' +>>> f.clean('foo') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid IPv4 address.'] +>>> f.clean('127.0.0.') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid IPv4 address.'] +>>> f.clean('1.2.3.4.5') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid IPv4 address.'] +>>> f.clean('256.125.1.5') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid IPv4 address.'] + +>>> f = IPAddressField(required=False) +>>> f.clean('') +u'' +>>> f.clean(None) +u'' +>>> f.clean('127.0.0.1') +u'127.0.0.1' +>>> f.clean('foo') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid IPv4 address.'] +>>> f.clean('127.0.0.') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid IPv4 address.'] +>>> f.clean('1.2.3.4.5') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid IPv4 address.'] +>>> f.clean('256.125.1.5') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid IPv4 address.'] + +################################# +# Tests of underlying functions # +################################# + +# smart_unicode tests +>>> from django.utils.encoding import smart_unicode +>>> class Test: +... def __str__(self): +... return 'ŠĐĆŽćžšđ' +>>> class TestU: +... def __str__(self): +... return 'Foo' +... def __unicode__(self): +... return u'\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111' +>>> smart_unicode(Test()) +u'\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111' +>>> smart_unicode(TestU()) +u'\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111' +>>> smart_unicode(1) +u'1' +>>> smart_unicode('foo') +u'foo' + + +#################################### +# Test accessing errors in clean() # +#################################### + +>>> class UserForm(Form): +... username = CharField(max_length=10) +... password = CharField(widget=PasswordInput) +... def clean(self): +... data = self.cleaned_data +... if not self.errors: +... data['username'] = data['username'].lower() +... return data + +>>> f = UserForm({'username': 'SirRobin', 'password': 'blue'}) +>>> f.is_valid() +True +>>> f.cleaned_data['username'] +u'sirrobin' + +####################################### +# Test overriding ErrorList in a form # +####################################### + +>>> from django.newforms.util import ErrorList +>>> class DivErrorList(ErrorList): +... def __unicode__(self): +... return self.as_divs() +... def as_divs(self): +... if not self: return u'' +... return u'
%s
' % ''.join([u'
%s
' % e for e in self]) +>>> class CommentForm(Form): +... name = CharField(max_length=50, required=False) +... email = EmailField() +... comment = CharField() +>>> data = dict(email='invalid') +>>> f = CommentForm(data, auto_id=False, error_class=DivErrorList) +>>> print f.as_p() +

Name:

+
Enter a valid e-mail address.
+

Email:

+
This field is required.
+

Comment:

+ +################################# +# Test multipart-encoded form # +################################# + +>>> class FormWithoutFile(Form): +... username = CharField() +>>> class FormWithFile(Form): +... username = CharField() +... file = FileField() +>>> class FormWithImage(Form): +... image = ImageField() + +>>> FormWithoutFile().is_multipart() +False +>>> FormWithFile().is_multipart() +True +>>> FormWithImage().is_multipart() +True + +""" diff --git a/tests/regressiontests/forms/fields.py b/tests/regressiontests/forms/fields.py new file mode 100644 index 0000000000..3b93d70338 --- /dev/null +++ b/tests/regressiontests/forms/fields.py @@ -0,0 +1,1162 @@ +# -*- coding: utf-8 -*- +tests = r""" +>>> from django.newforms import * +>>> from django.newforms.widgets import RadioFieldRenderer +>>> import datetime +>>> import time +>>> import re +>>> try: +... from decimal import Decimal +... except ImportError: +... from django.utils._decimal import Decimal + + +########## +# Fields # +########## + +Each Field class does some sort of validation. Each Field has a clean() method, +which either raises django.newforms.ValidationError or returns the "clean" +data -- usually a Unicode object, but, in some rare cases, a list. + +Each Field's __init__() takes at least these parameters: + required -- Boolean that specifies whether the field is required. + True by default. + widget -- A Widget class, or instance of a Widget class, that should be + used for this Field when displaying it. Each Field has a default + Widget that it'll use if you don't specify this. In most cases, + the default widget is TextInput. + label -- A verbose name for this field, for use in displaying this field in + a form. By default, Django will use a "pretty" version of the form + field name, if the Field is part of a Form. + initial -- A value to use in this Field's initial display. This value is + *not* used as a fallback if data isn't given. + +Other than that, the Field subclasses have class-specific options for +__init__(). For example, CharField has a max_length option. + +# CharField ################################################################### + +>>> f = CharField() +>>> f.clean(1) +u'1' +>>> f.clean('hello') +u'hello' +>>> f.clean(None) +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] +>>> f.clean('') +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] +>>> f.clean([1, 2, 3]) +u'[1, 2, 3]' + +>>> f = CharField(required=False) +>>> f.clean(1) +u'1' +>>> f.clean('hello') +u'hello' +>>> f.clean(None) +u'' +>>> f.clean('') +u'' +>>> f.clean([1, 2, 3]) +u'[1, 2, 3]' + +CharField accepts an optional max_length parameter: +>>> f = CharField(max_length=10, required=False) +>>> f.clean('12345') +u'12345' +>>> f.clean('1234567890') +u'1234567890' +>>> f.clean('1234567890a') +Traceback (most recent call last): +... +ValidationError: [u'Ensure this value has at most 10 characters (it has 11).'] + +CharField accepts an optional min_length parameter: +>>> f = CharField(min_length=10, required=False) +>>> f.clean('') +u'' +>>> f.clean('12345') +Traceback (most recent call last): +... +ValidationError: [u'Ensure this value has at least 10 characters (it has 5).'] +>>> f.clean('1234567890') +u'1234567890' +>>> f.clean('1234567890a') +u'1234567890a' + +>>> f = CharField(min_length=10, required=True) +>>> f.clean('') +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] +>>> f.clean('12345') +Traceback (most recent call last): +... +ValidationError: [u'Ensure this value has at least 10 characters (it has 5).'] +>>> f.clean('1234567890') +u'1234567890' +>>> f.clean('1234567890a') +u'1234567890a' + +# IntegerField ################################################################ + +>>> f = IntegerField() +>>> f.clean('') +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] +>>> f.clean(None) +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] +>>> f.clean('1') +1 +>>> isinstance(f.clean('1'), int) +True +>>> f.clean('23') +23 +>>> f.clean('a') +Traceback (most recent call last): +... +ValidationError: [u'Enter a whole number.'] +>>> f.clean(42) +42 +>>> f.clean(3.14) +Traceback (most recent call last): +... +ValidationError: [u'Enter a whole number.'] +>>> f.clean('1 ') +1 +>>> f.clean(' 1') +1 +>>> f.clean(' 1 ') +1 +>>> f.clean('1a') +Traceback (most recent call last): +... +ValidationError: [u'Enter a whole number.'] + +>>> f = IntegerField(required=False) +>>> f.clean('') +>>> repr(f.clean('')) +'None' +>>> f.clean(None) +>>> repr(f.clean(None)) +'None' +>>> f.clean('1') +1 +>>> isinstance(f.clean('1'), int) +True +>>> f.clean('23') +23 +>>> f.clean('a') +Traceback (most recent call last): +... +ValidationError: [u'Enter a whole number.'] +>>> f.clean('1 ') +1 +>>> f.clean(' 1') +1 +>>> f.clean(' 1 ') +1 +>>> f.clean('1a') +Traceback (most recent call last): +... +ValidationError: [u'Enter a whole number.'] + +IntegerField accepts an optional max_value parameter: +>>> f = IntegerField(max_value=10) +>>> f.clean(None) +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] +>>> f.clean(1) +1 +>>> f.clean(10) +10 +>>> f.clean(11) +Traceback (most recent call last): +... +ValidationError: [u'Ensure this value is less than or equal to 10.'] +>>> f.clean('10') +10 +>>> f.clean('11') +Traceback (most recent call last): +... +ValidationError: [u'Ensure this value is less than or equal to 10.'] + +IntegerField accepts an optional min_value parameter: +>>> f = IntegerField(min_value=10) +>>> f.clean(None) +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] +>>> f.clean(1) +Traceback (most recent call last): +... +ValidationError: [u'Ensure this value is greater than or equal to 10.'] +>>> f.clean(10) +10 +>>> f.clean(11) +11 +>>> f.clean('10') +10 +>>> f.clean('11') +11 + +min_value and max_value can be used together: +>>> f = IntegerField(min_value=10, max_value=20) +>>> f.clean(None) +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] +>>> f.clean(1) +Traceback (most recent call last): +... +ValidationError: [u'Ensure this value is greater than or equal to 10.'] +>>> f.clean(10) +10 +>>> f.clean(11) +11 +>>> f.clean('10') +10 +>>> f.clean('11') +11 +>>> f.clean(20) +20 +>>> f.clean(21) +Traceback (most recent call last): +... +ValidationError: [u'Ensure this value is less than or equal to 20.'] + +# FloatField ################################################################## + +>>> f = FloatField() +>>> f.clean('') +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] +>>> f.clean(None) +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] +>>> f.clean('1') +1.0 +>>> isinstance(f.clean('1'), float) +True +>>> f.clean('23') +23.0 +>>> f.clean('3.14') +3.1400000000000001 +>>> f.clean(3.14) +3.1400000000000001 +>>> f.clean(42) +42.0 +>>> f.clean('a') +Traceback (most recent call last): +... +ValidationError: [u'Enter a number.'] +>>> f.clean('1.0 ') +1.0 +>>> f.clean(' 1.0') +1.0 +>>> f.clean(' 1.0 ') +1.0 +>>> f.clean('1.0a') +Traceback (most recent call last): +... +ValidationError: [u'Enter a number.'] + +>>> f = FloatField(required=False) +>>> f.clean('') + +>>> f.clean(None) + +>>> f.clean('1') +1.0 + +FloatField accepts min_value and max_value just like IntegerField: +>>> f = FloatField(max_value=1.5, min_value=0.5) + +>>> f.clean('1.6') +Traceback (most recent call last): +... +ValidationError: [u'Ensure this value is less than or equal to 1.5.'] +>>> f.clean('0.4') +Traceback (most recent call last): +... +ValidationError: [u'Ensure this value is greater than or equal to 0.5.'] +>>> f.clean('1.5') +1.5 +>>> f.clean('0.5') +0.5 + +# DecimalField ################################################################ + +>>> f = DecimalField(max_digits=4, decimal_places=2) +>>> f.clean('') +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] +>>> f.clean(None) +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] +>>> f.clean('1') +Decimal("1") +>>> isinstance(f.clean('1'), Decimal) +True +>>> f.clean('23') +Decimal("23") +>>> f.clean('3.14') +Decimal("3.14") +>>> f.clean(3.14) +Decimal("3.14") +>>> f.clean(Decimal('3.14')) +Decimal("3.14") +>>> f.clean('a') +Traceback (most recent call last): +... +ValidationError: [u'Enter a number.'] +>>> f.clean('1.0 ') +Decimal("1.0") +>>> f.clean(' 1.0') +Decimal("1.0") +>>> f.clean(' 1.0 ') +Decimal("1.0") +>>> f.clean('1.0a') +Traceback (most recent call last): +... +ValidationError: [u'Enter a number.'] +>>> f.clean('123.45') +Traceback (most recent call last): +... +ValidationError: [u'Ensure that there are no more than 4 digits in total.'] +>>> f.clean('1.234') +Traceback (most recent call last): +... +ValidationError: [u'Ensure that there are no more than 2 decimal places.'] +>>> f.clean('123.4') +Traceback (most recent call last): +... +ValidationError: [u'Ensure that there are no more than 2 digits before the decimal point.'] +>>> f.clean('-12.34') +Decimal("-12.34") +>>> f.clean('-123.45') +Traceback (most recent call last): +... +ValidationError: [u'Ensure that there are no more than 4 digits in total.'] +>>> f.clean('-.12') +Decimal("-0.12") +>>> f.clean('-00.12') +Decimal("-0.12") +>>> f.clean('-000.12') +Decimal("-0.12") +>>> f.clean('-000.123') +Traceback (most recent call last): +... +ValidationError: [u'Ensure that there are no more than 2 decimal places.'] +>>> f.clean('-000.1234') +Traceback (most recent call last): +... +ValidationError: [u'Ensure that there are no more than 4 digits in total.'] +>>> f.clean('--0.12') +Traceback (most recent call last): +... +ValidationError: [u'Enter a number.'] + +>>> f = DecimalField(max_digits=4, decimal_places=2, required=False) +>>> f.clean('') + +>>> f.clean(None) + +>>> f.clean('1') +Decimal("1") + +DecimalField accepts min_value and max_value just like IntegerField: +>>> f = DecimalField(max_digits=4, decimal_places=2, max_value=Decimal('1.5'), min_value=Decimal('0.5')) + +>>> f.clean('1.6') +Traceback (most recent call last): +... +ValidationError: [u'Ensure this value is less than or equal to 1.5.'] +>>> f.clean('0.4') +Traceback (most recent call last): +... +ValidationError: [u'Ensure this value is greater than or equal to 0.5.'] +>>> f.clean('1.5') +Decimal("1.5") +>>> f.clean('0.5') +Decimal("0.5") +>>> f.clean('.5') +Decimal("0.5") +>>> f.clean('00.50') +Decimal("0.50") + +# DateField ################################################################### + +>>> import datetime +>>> f = DateField() +>>> f.clean(datetime.date(2006, 10, 25)) +datetime.date(2006, 10, 25) +>>> f.clean(datetime.datetime(2006, 10, 25, 14, 30)) +datetime.date(2006, 10, 25) +>>> f.clean(datetime.datetime(2006, 10, 25, 14, 30, 59)) +datetime.date(2006, 10, 25) +>>> f.clean(datetime.datetime(2006, 10, 25, 14, 30, 59, 200)) +datetime.date(2006, 10, 25) +>>> f.clean('2006-10-25') +datetime.date(2006, 10, 25) +>>> f.clean('10/25/2006') +datetime.date(2006, 10, 25) +>>> f.clean('10/25/06') +datetime.date(2006, 10, 25) +>>> f.clean('Oct 25 2006') +datetime.date(2006, 10, 25) +>>> f.clean('October 25 2006') +datetime.date(2006, 10, 25) +>>> f.clean('October 25, 2006') +datetime.date(2006, 10, 25) +>>> f.clean('25 October 2006') +datetime.date(2006, 10, 25) +>>> f.clean('25 October, 2006') +datetime.date(2006, 10, 25) +>>> f.clean('2006-4-31') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid date.'] +>>> f.clean('200a-10-25') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid date.'] +>>> f.clean('25/10/06') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid date.'] +>>> f.clean(None) +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] + +>>> f = DateField(required=False) +>>> f.clean(None) +>>> repr(f.clean(None)) +'None' +>>> f.clean('') +>>> repr(f.clean('')) +'None' + +DateField accepts an optional input_formats parameter: +>>> f = DateField(input_formats=['%Y %m %d']) +>>> f.clean(datetime.date(2006, 10, 25)) +datetime.date(2006, 10, 25) +>>> f.clean(datetime.datetime(2006, 10, 25, 14, 30)) +datetime.date(2006, 10, 25) +>>> f.clean('2006 10 25') +datetime.date(2006, 10, 25) + +The input_formats parameter overrides all default input formats, +so the default formats won't work unless you specify them: +>>> f.clean('2006-10-25') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid date.'] +>>> f.clean('10/25/2006') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid date.'] +>>> f.clean('10/25/06') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid date.'] + +# TimeField ################################################################### + +>>> import datetime +>>> f = TimeField() +>>> f.clean(datetime.time(14, 25)) +datetime.time(14, 25) +>>> f.clean(datetime.time(14, 25, 59)) +datetime.time(14, 25, 59) +>>> f.clean('14:25') +datetime.time(14, 25) +>>> f.clean('14:25:59') +datetime.time(14, 25, 59) +>>> f.clean('hello') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid time.'] +>>> f.clean('1:24 p.m.') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid time.'] + +TimeField accepts an optional input_formats parameter: +>>> f = TimeField(input_formats=['%I:%M %p']) +>>> f.clean(datetime.time(14, 25)) +datetime.time(14, 25) +>>> f.clean(datetime.time(14, 25, 59)) +datetime.time(14, 25, 59) +>>> f.clean('4:25 AM') +datetime.time(4, 25) +>>> f.clean('4:25 PM') +datetime.time(16, 25) + +The input_formats parameter overrides all default input formats, +so the default formats won't work unless you specify them: +>>> f.clean('14:30:45') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid time.'] + +# DateTimeField ############################################################### + +>>> import datetime +>>> f = DateTimeField() +>>> f.clean(datetime.date(2006, 10, 25)) +datetime.datetime(2006, 10, 25, 0, 0) +>>> f.clean(datetime.datetime(2006, 10, 25, 14, 30)) +datetime.datetime(2006, 10, 25, 14, 30) +>>> f.clean(datetime.datetime(2006, 10, 25, 14, 30, 59)) +datetime.datetime(2006, 10, 25, 14, 30, 59) +>>> f.clean(datetime.datetime(2006, 10, 25, 14, 30, 59, 200)) +datetime.datetime(2006, 10, 25, 14, 30, 59, 200) +>>> f.clean('2006-10-25 14:30:45') +datetime.datetime(2006, 10, 25, 14, 30, 45) +>>> f.clean('2006-10-25 14:30:00') +datetime.datetime(2006, 10, 25, 14, 30) +>>> f.clean('2006-10-25 14:30') +datetime.datetime(2006, 10, 25, 14, 30) +>>> f.clean('2006-10-25') +datetime.datetime(2006, 10, 25, 0, 0) +>>> f.clean('10/25/2006 14:30:45') +datetime.datetime(2006, 10, 25, 14, 30, 45) +>>> f.clean('10/25/2006 14:30:00') +datetime.datetime(2006, 10, 25, 14, 30) +>>> f.clean('10/25/2006 14:30') +datetime.datetime(2006, 10, 25, 14, 30) +>>> f.clean('10/25/2006') +datetime.datetime(2006, 10, 25, 0, 0) +>>> f.clean('10/25/06 14:30:45') +datetime.datetime(2006, 10, 25, 14, 30, 45) +>>> f.clean('10/25/06 14:30:00') +datetime.datetime(2006, 10, 25, 14, 30) +>>> f.clean('10/25/06 14:30') +datetime.datetime(2006, 10, 25, 14, 30) +>>> f.clean('10/25/06') +datetime.datetime(2006, 10, 25, 0, 0) +>>> f.clean('hello') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid date/time.'] +>>> f.clean('2006-10-25 4:30 p.m.') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid date/time.'] + +DateField accepts an optional input_formats parameter: +>>> f = DateTimeField(input_formats=['%Y %m %d %I:%M %p']) +>>> f.clean(datetime.date(2006, 10, 25)) +datetime.datetime(2006, 10, 25, 0, 0) +>>> f.clean(datetime.datetime(2006, 10, 25, 14, 30)) +datetime.datetime(2006, 10, 25, 14, 30) +>>> f.clean(datetime.datetime(2006, 10, 25, 14, 30, 59)) +datetime.datetime(2006, 10, 25, 14, 30, 59) +>>> f.clean(datetime.datetime(2006, 10, 25, 14, 30, 59, 200)) +datetime.datetime(2006, 10, 25, 14, 30, 59, 200) +>>> f.clean('2006 10 25 2:30 PM') +datetime.datetime(2006, 10, 25, 14, 30) + +The input_formats parameter overrides all default input formats, +so the default formats won't work unless you specify them: +>>> f.clean('2006-10-25 14:30:45') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid date/time.'] + +>>> f = DateTimeField(required=False) +>>> f.clean(None) +>>> repr(f.clean(None)) +'None' +>>> f.clean('') +>>> repr(f.clean('')) +'None' + +# RegexField ################################################################## + +>>> f = RegexField('^\d[A-F]\d$') +>>> f.clean('2A2') +u'2A2' +>>> f.clean('3F3') +u'3F3' +>>> f.clean('3G3') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid value.'] +>>> f.clean(' 2A2') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid value.'] +>>> f.clean('2A2 ') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid value.'] +>>> f.clean('') +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] + +>>> f = RegexField('^\d[A-F]\d$', required=False) +>>> f.clean('2A2') +u'2A2' +>>> f.clean('3F3') +u'3F3' +>>> f.clean('3G3') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid value.'] +>>> f.clean('') +u'' + +Alternatively, RegexField can take a compiled regular expression: +>>> f = RegexField(re.compile('^\d[A-F]\d$')) +>>> f.clean('2A2') +u'2A2' +>>> f.clean('3F3') +u'3F3' +>>> f.clean('3G3') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid value.'] +>>> f.clean(' 2A2') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid value.'] +>>> f.clean('2A2 ') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid value.'] + +RegexField takes an optional error_message argument: +>>> f = RegexField('^\d\d\d\d$', error_message='Enter a four-digit number.') +>>> f.clean('1234') +u'1234' +>>> f.clean('123') +Traceback (most recent call last): +... +ValidationError: [u'Enter a four-digit number.'] +>>> f.clean('abcd') +Traceback (most recent call last): +... +ValidationError: [u'Enter a four-digit number.'] + +RegexField also access min_length and max_length parameters, for convenience. +>>> f = RegexField('^\d+$', min_length=5, max_length=10) +>>> f.clean('123') +Traceback (most recent call last): +... +ValidationError: [u'Ensure this value has at least 5 characters (it has 3).'] +>>> f.clean('abc') +Traceback (most recent call last): +... +ValidationError: [u'Ensure this value has at least 5 characters (it has 3).'] +>>> f.clean('12345') +u'12345' +>>> f.clean('1234567890') +u'1234567890' +>>> f.clean('12345678901') +Traceback (most recent call last): +... +ValidationError: [u'Ensure this value has at most 10 characters (it has 11).'] +>>> f.clean('12345a') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid value.'] + +# EmailField ################################################################## + +>>> f = EmailField() +>>> f.clean('') +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] +>>> f.clean(None) +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] +>>> f.clean('person@example.com') +u'person@example.com' +>>> f.clean('foo') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid e-mail address.'] +>>> f.clean('foo@') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid e-mail address.'] +>>> f.clean('foo@bar') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid e-mail address.'] + +>>> f = EmailField(required=False) +>>> f.clean('') +u'' +>>> f.clean(None) +u'' +>>> f.clean('person@example.com') +u'person@example.com' +>>> f.clean('foo') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid e-mail address.'] +>>> f.clean('foo@') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid e-mail address.'] +>>> f.clean('foo@bar') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid e-mail address.'] + +EmailField also access min_length and max_length parameters, for convenience. +>>> f = EmailField(min_length=10, max_length=15) +>>> f.clean('a@foo.com') +Traceback (most recent call last): +... +ValidationError: [u'Ensure this value has at least 10 characters (it has 9).'] +>>> f.clean('alf@foo.com') +u'alf@foo.com' +>>> f.clean('alf123456788@foo.com') +Traceback (most recent call last): +... +ValidationError: [u'Ensure this value has at most 15 characters (it has 20).'] + +# FileField ################################################################## + +>>> f = FileField() +>>> f.clean('') +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] + +>>> f.clean(None) +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] + +>>> f.clean({}) +Traceback (most recent call last): +... +ValidationError: [u'No file was submitted.'] + +>>> f.clean('some content that is not a file') +Traceback (most recent call last): +... +ValidationError: [u'No file was submitted. Check the encoding type on the form.'] + +>>> f.clean({'filename': 'name', 'content':None}) +Traceback (most recent call last): +... +ValidationError: [u'The submitted file is empty.'] + +>>> f.clean({'filename': 'name', 'content':''}) +Traceback (most recent call last): +... +ValidationError: [u'The submitted file is empty.'] + +>>> type(f.clean({'filename': 'name', 'content':'Some File Content'})) + + +# URLField ################################################################## + +>>> f = URLField() +>>> f.clean('') +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] +>>> f.clean(None) +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] +>>> f.clean('http://localhost') +u'http://localhost' +>>> f.clean('http://example.com') +u'http://example.com' +>>> f.clean('http://www.example.com') +u'http://www.example.com' +>>> f.clean('http://www.example.com:8000/test') +u'http://www.example.com:8000/test' +>>> f.clean('http://200.8.9.10') +u'http://200.8.9.10' +>>> f.clean('http://200.8.9.10:8000/test') +u'http://200.8.9.10:8000/test' +>>> f.clean('foo') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid URL.'] +>>> f.clean('http://') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid URL.'] +>>> f.clean('http://example') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid URL.'] +>>> f.clean('http://example.') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid URL.'] +>>> f.clean('http://.com') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid URL.'] + +>>> f = URLField(required=False) +>>> f.clean('') +u'' +>>> f.clean(None) +u'' +>>> f.clean('http://example.com') +u'http://example.com' +>>> f.clean('http://www.example.com') +u'http://www.example.com' +>>> f.clean('foo') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid URL.'] +>>> f.clean('http://') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid URL.'] +>>> f.clean('http://example') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid URL.'] +>>> f.clean('http://example.') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid URL.'] +>>> f.clean('http://.com') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid URL.'] + +URLField takes an optional verify_exists parameter, which is False by default. +This verifies that the URL is live on the Internet and doesn't return a 404 or 500: +>>> f = URLField(verify_exists=True) +>>> f.clean('http://www.google.com') # This will fail if there's no Internet connection +u'http://www.google.com' +>>> f.clean('http://example') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid URL.'] +>>> f.clean('http://www.jfoiwjfoi23jfoijoaijfoiwjofiwjefewl.com') # bad domain +Traceback (most recent call last): +... +ValidationError: [u'This URL appears to be a broken link.'] +>>> f.clean('http://google.com/we-love-microsoft.html') # good domain, bad page +Traceback (most recent call last): +... +ValidationError: [u'This URL appears to be a broken link.'] +>>> f = URLField(verify_exists=True, required=False) +>>> f.clean('') +u'' +>>> f.clean('http://www.google.com') # This will fail if there's no Internet connection +u'http://www.google.com' + +URLField also access min_length and max_length parameters, for convenience. +>>> f = URLField(min_length=15, max_length=20) +>>> f.clean('http://f.com') +Traceback (most recent call last): +... +ValidationError: [u'Ensure this value has at least 15 characters (it has 12).'] +>>> f.clean('http://example.com') +u'http://example.com' +>>> f.clean('http://abcdefghijklmnopqrstuvwxyz.com') +Traceback (most recent call last): +... +ValidationError: [u'Ensure this value has at most 20 characters (it has 37).'] + +URLField should prepend 'http://' if no scheme was given +>>> f = URLField(required=False) +>>> f.clean('example.com') +u'http://example.com' +>>> f.clean('') +u'' +>>> f.clean('https://example.com') +u'https://example.com' + +# BooleanField ################################################################ + +>>> f = BooleanField() +>>> f.clean('') +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] +>>> f.clean(None) +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] +>>> f.clean(True) +True +>>> f.clean(False) +False +>>> f.clean(1) +True +>>> f.clean(0) +False +>>> f.clean('Django rocks') +True + +>>> f = BooleanField(required=False) +>>> f.clean('') +False +>>> f.clean(None) +False +>>> f.clean(True) +True +>>> f.clean(False) +False +>>> f.clean(1) +True +>>> f.clean(0) +False +>>> f.clean('Django rocks') +True + +# ChoiceField ################################################################# + +>>> f = ChoiceField(choices=[('1', '1'), ('2', '2')]) +>>> f.clean('') +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] +>>> f.clean(None) +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] +>>> f.clean(1) +u'1' +>>> f.clean('1') +u'1' +>>> f.clean('3') +Traceback (most recent call last): +... +ValidationError: [u'Select a valid choice. That choice is not one of the available choices.'] + +>>> f = ChoiceField(choices=[('1', '1'), ('2', '2')], required=False) +>>> f.clean('') +u'' +>>> f.clean(None) +u'' +>>> f.clean(1) +u'1' +>>> f.clean('1') +u'1' +>>> f.clean('3') +Traceback (most recent call last): +... +ValidationError: [u'Select a valid choice. That choice is not one of the available choices.'] + +>>> f = ChoiceField(choices=[('J', 'John'), ('P', 'Paul')]) +>>> f.clean('J') +u'J' +>>> f.clean('John') +Traceback (most recent call last): +... +ValidationError: [u'Select a valid choice. That choice is not one of the available choices.'] + +# NullBooleanField ############################################################ + +>>> f = NullBooleanField() +>>> f.clean('') +>>> f.clean(True) +True +>>> f.clean(False) +False +>>> f.clean(None) +>>> f.clean('1') +>>> f.clean('2') +>>> f.clean('3') +>>> f.clean('hello') + +# MultipleChoiceField ######################################################### + +>>> f = MultipleChoiceField(choices=[('1', '1'), ('2', '2')]) +>>> f.clean('') +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] +>>> f.clean(None) +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] +>>> f.clean([1]) +[u'1'] +>>> f.clean(['1']) +[u'1'] +>>> f.clean(['1', '2']) +[u'1', u'2'] +>>> f.clean([1, '2']) +[u'1', u'2'] +>>> f.clean((1, '2')) +[u'1', u'2'] +>>> f.clean('hello') +Traceback (most recent call last): +... +ValidationError: [u'Enter a list of values.'] +>>> f.clean([]) +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] +>>> f.clean(()) +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] +>>> f.clean(['3']) +Traceback (most recent call last): +... +ValidationError: [u'Select a valid choice. 3 is not one of the available choices.'] + +>>> f = MultipleChoiceField(choices=[('1', '1'), ('2', '2')], required=False) +>>> f.clean('') +[] +>>> f.clean(None) +[] +>>> f.clean([1]) +[u'1'] +>>> f.clean(['1']) +[u'1'] +>>> f.clean(['1', '2']) +[u'1', u'2'] +>>> f.clean([1, '2']) +[u'1', u'2'] +>>> f.clean((1, '2')) +[u'1', u'2'] +>>> f.clean('hello') +Traceback (most recent call last): +... +ValidationError: [u'Enter a list of values.'] +>>> f.clean([]) +[] +>>> f.clean(()) +[] +>>> f.clean(['3']) +Traceback (most recent call last): +... +ValidationError: [u'Select a valid choice. 3 is not one of the available choices.'] + +# ComboField ################################################################## + +ComboField takes a list of fields that should be used to validate a value, +in that order. +>>> f = ComboField(fields=[CharField(max_length=20), EmailField()]) +>>> f.clean('test@example.com') +u'test@example.com' +>>> f.clean('longemailaddress@example.com') +Traceback (most recent call last): +... +ValidationError: [u'Ensure this value has at most 20 characters (it has 28).'] +>>> f.clean('not an e-mail') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid e-mail address.'] +>>> f.clean('') +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] +>>> f.clean(None) +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] + +>>> f = ComboField(fields=[CharField(max_length=20), EmailField()], required=False) +>>> f.clean('test@example.com') +u'test@example.com' +>>> f.clean('longemailaddress@example.com') +Traceback (most recent call last): +... +ValidationError: [u'Ensure this value has at most 20 characters (it has 28).'] +>>> f.clean('not an e-mail') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid e-mail address.'] +>>> f.clean('') +u'' +>>> f.clean(None) +u'' + +# SplitDateTimeField ########################################################## + +>>> f = SplitDateTimeField() +>>> f.clean([datetime.date(2006, 1, 10), datetime.time(7, 30)]) +datetime.datetime(2006, 1, 10, 7, 30) +>>> f.clean(None) +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] +>>> f.clean('') +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] +>>> f.clean('hello') +Traceback (most recent call last): +... +ValidationError: [u'Enter a list of values.'] +>>> f.clean(['hello', 'there']) +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid date.', u'Enter a valid time.'] +>>> f.clean(['2006-01-10', 'there']) +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid time.'] +>>> f.clean(['hello', '07:30']) +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid date.'] + +>>> f = SplitDateTimeField(required=False) +>>> f.clean([datetime.date(2006, 1, 10), datetime.time(7, 30)]) +datetime.datetime(2006, 1, 10, 7, 30) +>>> f.clean(['2006-01-10', '07:30']) +datetime.datetime(2006, 1, 10, 7, 30) +>>> f.clean(None) +>>> f.clean('') +>>> f.clean(['']) +>>> f.clean(['', '']) +>>> f.clean('hello') +Traceback (most recent call last): +... +ValidationError: [u'Enter a list of values.'] +>>> f.clean(['hello', 'there']) +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid date.', u'Enter a valid time.'] +>>> f.clean(['2006-01-10', 'there']) +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid time.'] +>>> f.clean(['hello', '07:30']) +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid date.'] +>>> f.clean(['2006-01-10', '']) +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid time.'] +>>> f.clean(['2006-01-10']) +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid time.'] +>>> f.clean(['', '07:30']) +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid date.'] +""" diff --git a/tests/regressiontests/forms/forms.py b/tests/regressiontests/forms/forms.py new file mode 100644 index 0000000000..ed88e3a6bb --- /dev/null +++ b/tests/regressiontests/forms/forms.py @@ -0,0 +1,1606 @@ +# -*- coding: utf-8 -*- +tests = r""" +>>> from django.newforms import * +>>> import datetime +>>> import time +>>> import re +>>> try: +... from decimal import Decimal +... except ImportError: +... from django.utils._decimal import Decimal + +######### +# Forms # +######### + +A Form is a collection of Fields. It knows how to validate a set of data and it +knows how to render itself in a couple of default ways (e.g., an HTML table). +You can pass it data in __init__(), as a dictionary. + +# Form ######################################################################## + +>>> class Person(Form): +... first_name = CharField() +... last_name = CharField() +... birthday = DateField() + +Pass a dictionary to a Form's __init__(). +>>> p = Person({'first_name': u'John', 'last_name': u'Lennon', 'birthday': u'1940-10-9'}) +>>> p.is_bound +True +>>> p.errors +{} +>>> p.is_valid() +True +>>> p.errors.as_ul() +u'' +>>> p.errors.as_text() +u'' +>>> p.cleaned_data +{'first_name': u'John', 'last_name': u'Lennon', 'birthday': datetime.date(1940, 10, 9)} +>>> print p['first_name'] + +>>> print p['last_name'] + +>>> print p['birthday'] + +>>> print p['nonexistentfield'] +Traceback (most recent call last): +... +KeyError: "Key 'nonexistentfield' not found in Form" + +>>> for boundfield in p: +... print boundfield + + + +>>> for boundfield in p: +... print boundfield.label, boundfield.data +First name John +Last name Lennon +Birthday 1940-10-9 +>>> print p + + + + +Empty dictionaries are valid, too. +>>> p = Person({}) +>>> p.is_bound +True +>>> 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 +>>> p.cleaned_data +Traceback (most recent call last): +... +AttributeError: 'Person' object has no attribute 'cleaned_data' +>>> print p + + + +>>> print p.as_table() + + + +>>> print p.as_ul() +
  • +
  • +
  • +>>> print p.as_p() + +

    + +

    + +

    + +If you don't pass any values to the Form's __init__(), or if you pass None, +the Form will be considered unbound and won't do any validation. Form.errors +will be an empty dictionary *but* Form.is_valid() will return False. +>>> p = Person() +>>> p.is_bound +False +>>> p.errors +{} +>>> p.is_valid() +False +>>> p.cleaned_data +Traceback (most recent call last): +... +AttributeError: 'Person' object has no attribute 'cleaned_data' +>>> print p + + + +>>> print p.as_table() + + + +>>> print p.as_ul() +
  • +
  • +
  • +>>> print p.as_p() +

    +

    +

    + +Unicode values are handled properly. +>>> 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'\n\n' +>>> p.as_ul() +u'
  • \n
  • \n
  • ' +>>> p.as_p() +u'

    \n

    \n

    ' + +>>> p = Person({'last_name': u'Lennon'}) +>>> p.errors +{'first_name': [u'This field is required.'], 'birthday': [u'This field is required.']} +>>> p.is_valid() +False +>>> p.errors.as_ul() +u'' +>>> print p.errors.as_text() +* first_name + * This field is required. +* birthday + * This field is required. +>>> p.cleaned_data +Traceback (most recent call last): +... +AttributeError: 'Person' object has no attribute 'cleaned_data' +>>> p['first_name'].errors +[u'This field is required.'] +>>> p['first_name'].errors.as_ul() +u'' +>>> p['first_name'].errors.as_text() +u'* This field is required.' + +>>> p = Person() +>>> print p['first_name'] + +>>> print p['last_name'] + +>>> print p['birthday'] + + +cleaned_data will always *only* contain a key for fields defined in the +Form, even if you pass extra data when you define the Form. In this +example, we pass a bunch of extra fields to the form constructor, +but cleaned_data contains only the form's fields. +>>> data = {'first_name': u'John', 'last_name': u'Lennon', 'birthday': u'1940-10-9', 'extra1': 'hello', 'extra2': 'hello'} +>>> p = Person(data) +>>> p.is_valid() +True +>>> p.cleaned_data +{'first_name': u'John', 'last_name': u'Lennon', 'birthday': datetime.date(1940, 10, 9)} + +cleaned_data will include a key and value for *all* fields defined in the Form, +even if the Form's data didn't include a value for fields that are not +required. In this example, the data dictionary doesn't include a value for the +"nick_name" field, but cleaned_data includes it. For CharFields, it's set to the +empty string. +>>> class OptionalPersonForm(Form): +... first_name = CharField() +... last_name = CharField() +... nick_name = CharField(required=False) +>>> data = {'first_name': u'John', 'last_name': u'Lennon'} +>>> f = OptionalPersonForm(data) +>>> f.is_valid() +True +>>> f.cleaned_data +{'nick_name': u'', 'first_name': u'John', 'last_name': u'Lennon'} + +For DateFields, it's set to None. +>>> class OptionalPersonForm(Form): +... first_name = CharField() +... last_name = CharField() +... birth_date = DateField(required=False) +>>> data = {'first_name': u'John', 'last_name': u'Lennon'} +>>> f = OptionalPersonForm(data) +>>> f.is_valid() +True +>>> f.cleaned_data +{'birth_date': None, 'first_name': u'John', 'last_name': u'Lennon'} + +"auto_id" tells the Form to add an "id" attribute to each form element. +If it's a string that contains '%s', Django will use that as a format string +into which the field's name will be inserted. It will also put a