diff options
Diffstat (limited to 'tests/regressiontests/forms')
| -rw-r--r-- | tests/regressiontests/forms/localflavor.py | 1359 | ||||
| -rw-r--r-- | tests/regressiontests/forms/regressions.py | 51 | ||||
| -rw-r--r-- | tests/regressiontests/forms/tests.py | 1230 |
3 files changed, 2584 insertions, 56 deletions
diff --git a/tests/regressiontests/forms/localflavor.py b/tests/regressiontests/forms/localflavor.py new file mode 100644 index 0000000000..ede89de2a0 --- /dev/null +++ b/tests/regressiontests/forms/localflavor.py @@ -0,0 +1,1359 @@ +# -*- coding: utf-8 -*- +# Tests for the different contrib/localflavor/ form fields. + +localflavor_tests = r""" +# USZipCodeField ############################################################## + +USZipCodeField validates that the data is either a five-digit U.S. zip code or +a zip+4. +>>> from django.contrib.localflavor.us.forms import USZipCodeField +>>> f = USZipCodeField() +>>> f.clean('60606') +u'60606' +>>> f.clean(60606) +u'60606' +>>> f.clean('04000') +u'04000' +>>> f.clean('4000') +Traceback (most recent call last): +... +ValidationError: [u'Enter a zip code in the format XXXXX or XXXXX-XXXX.'] +>>> f.clean('60606-1234') +u'60606-1234' +>>> f.clean('6060-1234') +Traceback (most recent call last): +... +ValidationError: [u'Enter a zip code in the format XXXXX or XXXXX-XXXX.'] +>>> f.clean('60606-') +Traceback (most recent call last): +... +ValidationError: [u'Enter a zip code in the format XXXXX or XXXXX-XXXX.'] +>>> 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 = USZipCodeField(required=False) +>>> f.clean('60606') +u'60606' +>>> f.clean(60606) +u'60606' +>>> f.clean('04000') +u'04000' +>>> f.clean('4000') +Traceback (most recent call last): +... +ValidationError: [u'Enter a zip code in the format XXXXX or XXXXX-XXXX.'] +>>> f.clean('60606-1234') +u'60606-1234' +>>> f.clean('6060-1234') +Traceback (most recent call last): +... +ValidationError: [u'Enter a zip code in the format XXXXX or XXXXX-XXXX.'] +>>> f.clean('60606-') +Traceback (most recent call last): +... +ValidationError: [u'Enter a zip code in the format XXXXX or XXXXX-XXXX.'] +>>> f.clean(None) +u'' +>>> f.clean('') +u'' + +# USPhoneNumberField ########################################################## + +USPhoneNumberField validates that the data is a valid U.S. phone number, +including the area code. It's normalized to XXX-XXX-XXXX format. +>>> from django.contrib.localflavor.us.forms import USPhoneNumberField +>>> f = USPhoneNumberField() +>>> f.clean('312-555-1212') +u'312-555-1212' +>>> f.clean('3125551212') +u'312-555-1212' +>>> f.clean('312 555-1212') +u'312-555-1212' +>>> f.clean('(312) 555-1212') +u'312-555-1212' +>>> f.clean('312 555 1212') +u'312-555-1212' +>>> f.clean('312.555.1212') +u'312-555-1212' +>>> f.clean('312.555-1212') +u'312-555-1212' +>>> f.clean(' (312) 555.1212 ') +u'312-555-1212' +>>> f.clean('555-1212') +Traceback (most recent call last): +... +ValidationError: [u'Phone numbers must be in XXX-XXX-XXXX format.'] +>>> f.clean('312-55-1212') +Traceback (most recent call last): +... +ValidationError: [u'Phone numbers must be in XXX-XXX-XXXX format.'] +>>> 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 = USPhoneNumberField(required=False) +>>> f.clean('312-555-1212') +u'312-555-1212' +>>> f.clean('3125551212') +u'312-555-1212' +>>> f.clean('312 555-1212') +u'312-555-1212' +>>> f.clean('(312) 555-1212') +u'312-555-1212' +>>> f.clean('312 555 1212') +u'312-555-1212' +>>> f.clean('312.555.1212') +u'312-555-1212' +>>> f.clean('312.555-1212') +u'312-555-1212' +>>> f.clean(' (312) 555.1212 ') +u'312-555-1212' +>>> f.clean('555-1212') +Traceback (most recent call last): +... +ValidationError: [u'Phone numbers must be in XXX-XXX-XXXX format.'] +>>> f.clean('312-55-1212') +Traceback (most recent call last): +... +ValidationError: [u'Phone numbers must be in XXX-XXX-XXXX format.'] +>>> f.clean(None) +u'' +>>> f.clean('') +u'' + +# USStateField ################################################################ + +USStateField validates that the data is either an abbreviation or name of a +U.S. state. +>>> from django.contrib.localflavor.us.forms import USStateField +>>> f = USStateField() +>>> f.clean('il') +u'IL' +>>> f.clean('IL') +u'IL' +>>> f.clean('illinois') +u'IL' +>>> f.clean(' illinois ') +u'IL' +>>> f.clean(60606) +Traceback (most recent call last): +... +ValidationError: [u'Enter a U.S. state or territory.'] +>>> 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 = USStateField(required=False) +>>> f.clean('il') +u'IL' +>>> f.clean('IL') +u'IL' +>>> f.clean('illinois') +u'IL' +>>> f.clean(' illinois ') +u'IL' +>>> f.clean(60606) +Traceback (most recent call last): +... +ValidationError: [u'Enter a U.S. state or territory.'] +>>> f.clean(None) +u'' +>>> f.clean('') +u'' + +# USStateSelect ############################################################### + +USStateSelect is a Select widget that uses a list of U.S. states/territories +as its choices. +>>> from django.contrib.localflavor.us.forms import USStateSelect +>>> w = USStateSelect() +>>> print w.render('state', 'IL') +<select name="state"> +<option value="AL">Alabama</option> +<option value="AK">Alaska</option> +<option value="AS">American Samoa</option> +<option value="AZ">Arizona</option> +<option value="AR">Arkansas</option> +<option value="CA">California</option> +<option value="CO">Colorado</option> +<option value="CT">Connecticut</option> +<option value="DE">Delaware</option> +<option value="DC">District of Columbia</option> +<option value="FM">Federated States of Micronesia</option> +<option value="FL">Florida</option> +<option value="GA">Georgia</option> +<option value="GU">Guam</option> +<option value="HI">Hawaii</option> +<option value="ID">Idaho</option> +<option value="IL" selected="selected">Illinois</option> +<option value="IN">Indiana</option> +<option value="IA">Iowa</option> +<option value="KS">Kansas</option> +<option value="KY">Kentucky</option> +<option value="LA">Louisiana</option> +<option value="ME">Maine</option> +<option value="MH">Marshall Islands</option> +<option value="MD">Maryland</option> +<option value="MA">Massachusetts</option> +<option value="MI">Michigan</option> +<option value="MN">Minnesota</option> +<option value="MS">Mississippi</option> +<option value="MO">Missouri</option> +<option value="MT">Montana</option> +<option value="NE">Nebraska</option> +<option value="NV">Nevada</option> +<option value="NH">New Hampshire</option> +<option value="NJ">New Jersey</option> +<option value="NM">New Mexico</option> +<option value="NY">New York</option> +<option value="NC">North Carolina</option> +<option value="ND">North Dakota</option> +<option value="MP">Northern Mariana Islands</option> +<option value="OH">Ohio</option> +<option value="OK">Oklahoma</option> +<option value="OR">Oregon</option> +<option value="PW">Palau</option> +<option value="PA">Pennsylvania</option> +<option value="PR">Puerto Rico</option> +<option value="RI">Rhode Island</option> +<option value="SC">South Carolina</option> +<option value="SD">South Dakota</option> +<option value="TN">Tennessee</option> +<option value="TX">Texas</option> +<option value="UT">Utah</option> +<option value="VT">Vermont</option> +<option value="VI">Virgin Islands</option> +<option value="VA">Virginia</option> +<option value="WA">Washington</option> +<option value="WV">West Virginia</option> +<option value="WI">Wisconsin</option> +<option value="WY">Wyoming</option> +</select> + +# USSocialSecurityNumberField ################################################# +>>> from django.contrib.localflavor.us.forms import USSocialSecurityNumberField +>>> f = USSocialSecurityNumberField() +>>> f.clean('987-65-4330') +u'987-65-4330' +>>> f.clean('987654330') +u'987-65-4330' +>>> f.clean('078-05-1120') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid U.S. Social Security number in XXX-XX-XXXX format.'] + +# UKPostcodeField ############################################################# + +UKPostcodeField validates that the data is a valid UK postcode. +>>> from django.contrib.localflavor.uk.forms import UKPostcodeField +>>> f = UKPostcodeField() +>>> f.clean('BT32 4PX') +u'BT32 4PX' +>>> f.clean('GIR 0AA') +u'GIR 0AA' +>>> f.clean('BT324PX') +Traceback (most recent call last): +... +ValidationError: [u'Enter a postcode. A space is required between the two postcode parts.'] +>>> f.clean('1NV 4L1D') +Traceback (most recent call last): +... +ValidationError: [u'Enter a postcode. A space is required between the two postcode parts.'] +>>> 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 = UKPostcodeField(required=False) +>>> f.clean('BT32 4PX') +u'BT32 4PX' +>>> f.clean('GIR 0AA') +u'GIR 0AA' +>>> f.clean('1NV 4L1D') +Traceback (most recent call last): +... +ValidationError: [u'Enter a postcode. A space is required between the two postcode parts.'] +>>> f.clean('BT324PX') +Traceback (most recent call last): +... +ValidationError: [u'Enter a postcode. A space is required between the two postcode parts.'] +>>> f.clean(None) +u'' +>>> f.clean('') +u'' + +# FRZipCodeField ############################################################# + +FRZipCodeField validates that the data is a valid FR zipcode. +>>> from django.contrib.localflavor.fr.forms import FRZipCodeField +>>> f = FRZipCodeField() +>>> f.clean('75001') +u'75001' +>>> f.clean('93200') +u'93200' +>>> f.clean('2A200') +Traceback (most recent call last): +... +ValidationError: [u'Enter a zip code in the format XXXXX.'] +>>> f.clean('980001') +Traceback (most recent call last): +... +ValidationError: [u'Enter a zip code in the format XXXXX.'] +>>> 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 = FRZipCodeField(required=False) +>>> f.clean('75001') +u'75001' +>>> f.clean('93200') +u'93200' +>>> f.clean('2A200') +Traceback (most recent call last): +... +ValidationError: [u'Enter a zip code in the format XXXXX.'] +>>> f.clean('980001') +Traceback (most recent call last): +... +ValidationError: [u'Enter a zip code in the format XXXXX.'] +>>> f.clean(None) +u'' +>>> f.clean('') +u'' + + +# FRPhoneNumberField ########################################################## + +FRPhoneNumberField validates that the data is a valid french phone number. +It's normalized to 0X XX XX XX XX format. Dots are valid too. +>>> from django.contrib.localflavor.fr.forms import FRPhoneNumberField +>>> f = FRPhoneNumberField() +>>> f.clean('01 55 44 58 64') +u'01 55 44 58 64' +>>> f.clean('0155445864') +u'01 55 44 58 64' +>>> f.clean('01 5544 5864') +u'01 55 44 58 64' +>>> f.clean('01 55.44.58.64') +u'01 55 44 58 64' +>>> f.clean('01.55.44.58.64') +u'01 55 44 58 64' +>>> f.clean('01,55,44,58,64') +Traceback (most recent call last): +... +ValidationError: [u'Phone numbers must be in 0X XX XX XX XX format.'] +>>> f.clean('555 015 544') +Traceback (most recent call last): +... +ValidationError: [u'Phone numbers must be in 0X XX XX XX XX format.'] +>>> 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 = FRPhoneNumberField(required=False) +>>> f.clean('01 55 44 58 64') +u'01 55 44 58 64' +>>> f.clean('0155445864') +u'01 55 44 58 64' +>>> f.clean('01 5544 5864') +u'01 55 44 58 64' +>>> f.clean('01 55.44.58.64') +u'01 55 44 58 64' +>>> f.clean('01.55.44.58.64') +u'01 55 44 58 64' +>>> f.clean('01,55,44,58,64') +Traceback (most recent call last): +... +ValidationError: [u'Phone numbers must be in 0X XX XX XX XX format.'] +>>> f.clean('555 015 544') +Traceback (most recent call last): +... +ValidationError: [u'Phone numbers must be in 0X XX XX XX XX format.'] +>>> f.clean(None) +u'' +>>> f.clean('') +u'' + +# FRDepartmentSelect ############################################################### + +FRDepartmentSelect is a Select widget that uses a list of french departments +including DOM TOM +>>> from django.contrib.localflavor.fr.forms import FRDepartmentSelect +>>> w = FRDepartmentSelect() +>>> print w.render('dep', 'Paris') +<select name="dep"> +<option value="01">01 - Ain</option> +<option value="02">02 - Aisne</option> +<option value="03">03 - Allier</option> +<option value="04">04 - Alpes-de-Haute-Provence</option> +<option value="05">05 - Hautes-Alpes</option> +<option value="06">06 - Alpes-Maritimes</option> +<option value="07">07 - Ardeche</option> +<option value="08">08 - Ardennes</option> +<option value="09">09 - Ariege</option> +<option value="10">10 - Aube</option> +<option value="11">11 - Aude</option> +<option value="12">12 - Aveyron</option> +<option value="13">13 - Bouches-du-Rhone</option> +<option value="14">14 - Calvados</option> +<option value="15">15 - Cantal</option> +<option value="16">16 - Charente</option> +<option value="17">17 - Charente-Maritime</option> +<option value="18">18 - Cher</option> +<option value="19">19 - Correze</option> +<option value="21">21 - Cote-d'Or</option> +<option value="22">22 - Cotes-d'Armor</option> +<option value="23">23 - Creuse</option> +<option value="24">24 - Dordogne</option> +<option value="25">25 - Doubs</option> +<option value="26">26 - Drome</option> +<option value="27">27 - Eure</option> +<option value="28">28 - Eure-et-Loire</option> +<option value="29">29 - Finistere</option> +<option value="2A">2A - Corse-du-Sud</option> +<option value="2B">2B - Haute-Corse</option> +<option value="30">30 - Gard</option> +<option value="31">31 - Haute-Garonne</option> +<option value="32">32 - Gers</option> +<option value="33">33 - Gironde</option> +<option value="34">34 - Herault</option> +<option value="35">35 - Ille-et-Vilaine</option> +<option value="36">36 - Indre</option> +<option value="37">37 - Indre-et-Loire</option> +<option value="38">38 - Isere</option> +<option value="39">39 - Jura</option> +<option value="40">40 - Landes</option> +<option value="41">41 - Loir-et-Cher</option> +<option value="42">42 - Loire</option> +<option value="43">43 - Haute-Loire</option> +<option value="44">44 - Loire-Atlantique</option> +<option value="45">45 - Loiret</option> +<option value="46">46 - Lot</option> +<option value="47">47 - Lot-et-Garonne</option> +<option value="48">48 - Lozere</option> +<option value="49">49 - Maine-et-Loire</option> +<option value="50">50 - Manche</option> +<option value="51">51 - Marne</option> +<option value="52">52 - Haute-Marne</option> +<option value="53">53 - Mayenne</option> +<option value="54">54 - Meurthe-et-Moselle</option> +<option value="55">55 - Meuse</option> +<option value="56">56 - Morbihan</option> +<option value="57">57 - Moselle</option> +<option value="58">58 - Nievre</option> +<option value="59">59 - Nord</option> +<option value="60">60 - Oise</option> +<option value="61">61 - Orne</option> +<option value="62">62 - Pas-de-Calais</option> +<option value="63">63 - Puy-de-Dome</option> +<option value="64">64 - Pyrenees-Atlantiques</option> +<option value="65">65 - Hautes-Pyrenees</option> +<option value="66">66 - Pyrenees-Orientales</option> +<option value="67">67 - Bas-Rhin</option> +<option value="68">68 - Haut-Rhin</option> +<option value="69">69 - Rhone</option> +<option value="70">70 - Haute-Saone</option> +<option value="71">71 - Saone-et-Loire</option> +<option value="72">72 - Sarthe</option> +<option value="73">73 - Savoie</option> +<option value="74">74 - Haute-Savoie</option> +<option value="75">75 - Paris</option> +<option value="76">76 - Seine-Maritime</option> +<option value="77">77 - Seine-et-Marne</option> +<option value="78">78 - Yvelines</option> +<option value="79">79 - Deux-Sevres</option> +<option value="80">80 - Somme</option> +<option value="81">81 - Tarn</option> +<option value="82">82 - Tarn-et-Garonne</option> +<option value="83">83 - Var</option> +<option value="84">84 - Vaucluse</option> +<option value="85">85 - Vendee</option> +<option value="86">86 - Vienne</option> +<option value="87">87 - Haute-Vienne</option> +<option value="88">88 - Vosges</option> +<option value="89">89 - Yonne</option> +<option value="90">90 - Territoire de Belfort</option> +<option value="91">91 - Essonne</option> +<option value="92">92 - Hauts-de-Seine</option> +<option value="93">93 - Seine-Saint-Denis</option> +<option value="94">94 - Val-de-Marne</option> +<option value="95">95 - Val-d'Oise</option> +<option value="2A">2A - Corse du sud</option> +<option value="2B">2B - Haute Corse</option> +<option value="971">971 - Guadeloupe</option> +<option value="972">972 - Martinique</option> +<option value="973">973 - Guyane</option> +<option value="974">974 - La Reunion</option> +<option value="975">975 - Saint-Pierre-et-Miquelon</option> +<option value="976">976 - Mayotte</option> +<option value="984">984 - Terres Australes et Antarctiques</option> +<option value="986">986 - Wallis et Futuna</option> +<option value="987">987 - Polynesie Francaise</option> +<option value="988">988 - Nouvelle-Caledonie</option> +</select> + +# JPPostalCodeField ############################################################### + +A form field that validates its input is a Japanese postcode. + +Accepts 7 digits(with/out hyphen). +>>> from django.contrib.localflavor.jp.forms import JPPostalCodeField +>>> f = JPPostalCodeField() +>>> f.clean('251-0032') +u'2510032' +>>> f.clean('2510032') +u'2510032' +>>> f.clean('2510-032') +Traceback (most recent call last): +... +ValidationError: [u'Enter a postal code in the format XXXXXXX or XXX-XXXX.'] +>>> f.clean('251a0032') +Traceback (most recent call last): +... +ValidationError: [u'Enter a postal code in the format XXXXXXX or XXX-XXXX.'] +>>> f.clean('a51-0032') +Traceback (most recent call last): +... +ValidationError: [u'Enter a postal code in the format XXXXXXX or XXX-XXXX.'] +>>> f.clean('25100321') +Traceback (most recent call last): +... +ValidationError: [u'Enter a postal code in the format XXXXXXX or XXX-XXXX.'] +>>> f.clean('') +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] + +>>> f = JPPostalCodeField(required=False) +>>> f.clean('251-0032') +u'2510032' +>>> f.clean('2510032') +u'2510032' +>>> f.clean('2510-032') +Traceback (most recent call last): +... +ValidationError: [u'Enter a postal code in the format XXXXXXX or XXX-XXXX.'] +>>> f.clean('') +u'' +>>> f.clean(None) +u'' + +# JPPrefectureSelect ############################################################### + +A Select widget that uses a list of Japanese prefectures as its choices. +>>> from django.contrib.localflavor.jp.forms import JPPrefectureSelect +>>> w = JPPrefectureSelect() +>>> print w.render('prefecture', 'kanagawa') +<select name="prefecture"> +<option value="hokkaido">Hokkaido</option> +<option value="aomori">Aomori</option> +<option value="iwate">Iwate</option> +<option value="miyagi">Miyagi</option> +<option value="akita">Akita</option> +<option value="yamagata">Yamagata</option> +<option value="fukushima">Fukushima</option> +<option value="ibaraki">Ibaraki</option> +<option value="tochigi">Tochigi</option> +<option value="gunma">Gunma</option> +<option value="saitama">Saitama</option> +<option value="chiba">Chiba</option> +<option value="tokyo">Tokyo</option> +<option value="kanagawa" selected="selected">Kanagawa</option> +<option value="yamanashi">Yamanashi</option> +<option value="nagano">Nagano</option> +<option value="niigata">Niigata</option> +<option value="toyama">Toyama</option> +<option value="ishikawa">Ishikawa</option> +<option value="fukui">Fukui</option> +<option value="gifu">Gifu</option> +<option value="shizuoka">Shizuoka</option> +<option value="aichi">Aichi</option> +<option value="mie">Mie</option> +<option value="shiga">Shiga</option> +<option value="kyoto">Kyoto</option> +<option value="osaka">Osaka</option> +<option value="hyogo">Hyogo</option> +<option value="nara">Nara</option> +<option value="wakayama">Wakayama</option> +<option value="tottori">Tottori</option> +<option value="shimane">Shimane</option> +<option value="okayama">Okayama</option> +<option value="hiroshima">Hiroshima</option> +<option value="yamaguchi">Yamaguchi</option> +<option value="tokushima">Tokushima</option> +<option value="kagawa">Kagawa</option> +<option value="ehime">Ehime</option> +<option value="kochi">Kochi</option> +<option value="fukuoka">Fukuoka</option> +<option value="saga">Saga</option> +<option value="nagasaki">Nagasaki</option> +<option value="kumamoto">Kumamoto</option> +<option value="oita">Oita</option> +<option value="miyazaki">Miyazaki</option> +<option value="kagoshima">Kagoshima</option> +<option value="okinawa">Okinawa</option> +</select> + +# ITZipCodeField ############################################################# + +>>> from django.contrib.localflavor.it.forms import ITZipCodeField +>>> f = ITZipCodeField() +>>> f.clean('00100') +u'00100' +>>> f.clean(' 00100') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid zip code.'] + +# ITRegionSelect ############################################################# + +>>> from django.contrib.localflavor.it.forms import ITRegionSelect +>>> w = ITRegionSelect() +>>> w.render('regions', 'PMN') +u'<select name="regions">\n<option value="ABR">Abruzzo</option>\n<option value="BAS">Basilicata</option>\n<option value="CAL">Calabria</option>\n<option value="CAM">Campania</option>\n<option value="EMR">Emilia-Romagna</option>\n<option value="FVG">Friuli-Venezia Giulia</option>\n<option value="LAZ">Lazio</option>\n<option value="LIG">Liguria</option>\n<option value="LOM">Lombardia</option>\n<option value="MAR">Marche</option>\n<option value="MOL">Molise</option>\n<option value="PMN" selected="selected">Piemonte</option>\n<option value="PUG">Puglia</option>\n<option value="SAR">Sardegna</option>\n<option value="SIC">Sicilia</option>\n<option value="TOS">Toscana</option>\n<option value="TAA">Trentino-Alto Adige</option>\n<option value="UMB">Umbria</option>\n<option value="VAO">Valle d\u2019Aosta</option>\n<option value="VEN">Veneto</option>\n</select>' + +# ITSocialSecurityNumberField ################################################# + +>>> from django.contrib.localflavor.it.forms import ITSocialSecurityNumberField +>>> f = ITSocialSecurityNumberField() +>>> f.clean('LVSGDU99T71H501L') +u'LVSGDU99T71H501L' +>>> f.clean('LBRRME11A01L736W') +u'LBRRME11A01L736W' +>>> f.clean('lbrrme11a01l736w') +u'LBRRME11A01L736W' +>>> f.clean('LBR RME 11A01 L736W') +u'LBRRME11A01L736W' +>>> f.clean('LBRRME11A01L736A') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid Social Security number.'] +>>> f.clean('%BRRME11A01L736W') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid Social Security number.'] + +# ITVatNumberField ########################################################### + +>>> from django.contrib.localflavor.it.forms import ITVatNumberField +>>> f = ITVatNumberField() +>>> f.clean('07973780013') +u'07973780013' +>>> f.clean('7973780013') +u'07973780013' +>>> f.clean(7973780013) +u'07973780013' +>>> f.clean('07973780014') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid VAT number.'] +>>> f.clean('A7973780013') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid VAT number.'] + +# FIZipCodeField ############################################################# + +FIZipCodeField validates that the data is a valid FI zipcode. +>>> from django.contrib.localflavor.fi.forms import FIZipCodeField +>>> f = FIZipCodeField() +>>> f.clean('20540') +u'20540' +>>> f.clean('20101') +u'20101' +>>> f.clean('20s40') +Traceback (most recent call last): +... +ValidationError: [u'Enter a zip code in the format XXXXX.'] +>>> f.clean('205401') +Traceback (most recent call last): +... +ValidationError: [u'Enter a zip code in the format XXXXX.'] +>>> 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 = FIZipCodeField(required=False) +>>> f.clean('20540') +u'20540' +>>> f.clean('20101') +u'20101' +>>> f.clean('20s40') +Traceback (most recent call last): +... +ValidationError: [u'Enter a zip code in the format XXXXX.'] +>>> f.clean('205401') +Traceback (most recent call last): +... +ValidationError: [u'Enter a zip code in the format XXXXX.'] +>>> f.clean(None) +u'' +>>> f.clean('') +u'' + +# FIMunicipalitySelect ############################################################### + +A Select widget that uses a list of Finnish municipalities as its choices. +>>> from django.contrib.localflavor.fi.forms import FIMunicipalitySelect +>>> w = FIMunicipalitySelect() +>>> unicode(w.render('municipalities', 'turku')) +u'<select name="municipalities">\n<option value="akaa">Akaa</option>\n<option value="alaharma">Alah\xe4rm\xe4</option>\n<option value="alajarvi">Alaj\xe4rvi</option>\n<option value="alastaro">Alastaro</option>\n<option value="alavieska">Alavieska</option>\n<option value="alavus">Alavus</option>\n<option value="anjalankoski">Anjalankoski</option>\n<option value="artjarvi">Artj\xe4rvi</option>\n<option value="asikkala">Asikkala</option>\n<option value="askainen">Askainen</option>\n<option value="askola">Askola</option>\n<option value="aura">Aura</option>\n<option value="brando">Br\xe4nd\xf6</option>\n<option value="dragsfjard">Dragsfj\xe4rd</option>\n<option value="eckero">Ecker\xf6</option>\n<option value="elimaki">Elim\xe4ki</option>\n<option value="eno">Eno</option>\n<option value="enonkoski">Enonkoski</option>\n<option value="enontekio">Enonteki\xf6</option>\n<option value="espoo">Espoo</option>\n<option value="eura">Eura</option>\n<option value="eurajoki">Eurajoki</option>\n<option value="evijarvi">Evij\xe4rvi</option>\n<option value="finstrom">Finstr\xf6m</option>\n<option value="forssa">Forssa</option>\n<option value="foglo">F\xf6gl\xf6</option>\n<option value="geta">Geta</option>\n<option value="haapajarvi">Haapaj\xe4rvi</option>\n<option value="haapavesi">Haapavesi</option>\n<option value="hailuoto">Hailuoto</option>\n<option value="halikko">Halikko</option>\n<option value="halsua">Halsua</option>\n<option value="hamina">Hamina</option>\n<option value="hammarland">Hammarland</option>\n<option value="hankasalmi">Hankasalmi</option>\n<option value="hanko">Hanko</option>\n<option value="harjavalta">Harjavalta</option>\n<option value="hartola">Hartola</option>\n<option value="hattula">Hattula</option>\n<option value="hauho">Hauho</option>\n<option value="haukipudas">Haukipudas</option>\n<option value="hausjarvi">Hausj\xe4rvi</option>\n<option value="heinola">Heinola</option>\n<option value="heinavesi">Hein\xe4vesi</option>\n<option value="helsinki">Helsinki</option>\n<option value="himanka">Himanka</option>\n<option value="hirvensalmi">Hirvensalmi</option>\n<option value="hollola">Hollola</option>\n<option value="honkajoki">Honkajoki</option>\n<option value="houtskari">Houtskari</option>\n<option value="huittinen">Huittinen</option>\n<option value="humppila">Humppila</option>\n<option value="hyrynsalmi">Hyrynsalmi</option>\n<option value="hyvinkaa">Hyvink\xe4\xe4</option>\n<option value="hameenkoski">H\xe4meenkoski</option>\n<option value="hameenkyro">H\xe4meenkyr\xf6</option>\n<option value="hameenlinna">H\xe4meenlinna</option>\n<option value="ii">Ii</option>\n<option value="iisalmi">Iisalmi</option>\n<option value="iitti">Iitti</option>\n<option value="ikaalinen">Ikaalinen</option>\n<option value="ilmajoki">Ilmajoki</option>\n<option value="ilomantsi">Ilomantsi</option>\n<option value="imatra">Imatra</option>\n<option value="inari">Inari</option>\n<option value="inio">Ini\xf6</option>\n<option value="inkoo">Inkoo</option>\n<option value="isojoki">Isojoki</option>\n<option value="isokyro">Isokyr\xf6</option>\n<option value="jaala">Jaala</option>\n<option value="jalasjarvi">Jalasj\xe4rvi</option>\n<option value="janakkala">Janakkala</option>\n<option value="joensuu">Joensuu</option>\n<option value="jokioinen">Jokioinen</option>\n<option value="jomala">Jomala</option>\n<option value="joroinen">Joroinen</option>\n<option value="joutsa">Joutsa</option>\n<option value="joutseno">Joutseno</option>\n<option value="juankoski">Juankoski</option>\n<option value="jurva">Jurva</option>\n<option value="juuka">Juuka</option>\n<option value="juupajoki">Juupajoki</option>\n<option value="juva">Juva</option>\n<option value="jyvaskyla">Jyv\xe4skyl\xe4</option>\n<option value="jyvaskylan_mlk">Jyv\xe4skyl\xe4n maalaiskunta</option>\n<option value="jamijarvi">J\xe4mij\xe4rvi</option>\n<option value="jamsa">J\xe4ms\xe4</option>\n<option value="jamsankoski">J\xe4ms\xe4nkoski</option>\n<option value="jarvenpaa">J\xe4rvenp\xe4\xe4</option>\n<option value="kaarina">Kaarina</option>\n<option value="kaavi">Kaavi</option>\n<option value="kajaani">Kajaani</option>\n<option value="kalajoki">Kalajoki</option>\n<option value="kalvola">Kalvola</option>\n<option value="kangasala">Kangasala</option>\n<option value="kangasniemi">Kangasniemi</option>\n<option value="kankaanpaa">Kankaanp\xe4\xe4</option>\n<option value="kannonkoski">Kannonkoski</option>\n<option value="kannus">Kannus</option>\n<option value="karijoki">Karijoki</option>\n<option value="karjaa">Karjaa</option>\n<option value="karjalohja">Karjalohja</option>\n<option value="karkkila">Karkkila</option>\n<option value="karstula">Karstula</option>\n<option value="karttula">Karttula</option>\n<option value="karvia">Karvia</option>\n<option value="kaskinen">Kaskinen</option>\n<option value="kauhajoki">Kauhajoki</option>\n<option value="kauhava">Kauhava</option>\n<option value="kauniainen">Kauniainen</option>\n<option value="kaustinen">Kaustinen</option>\n<option value="keitele">Keitele</option>\n<option value="kemi">Kemi</option>\n<option value="kemijarvi">Kemij\xe4rvi</option>\n<option value="keminmaa">Keminmaa</option>\n<option value="kemio">Kemi\xf6</option>\n<option value="kempele">Kempele</option>\n<option value="kerava">Kerava</option>\n<option value="kerimaki">Kerim\xe4ki</option>\n<option value="kestila">Kestil\xe4</option>\n<option value="kesalahti">Kes\xe4lahti</option>\n<option value="keuruu">Keuruu</option>\n<option value="kihnio">Kihni\xf6</option>\n<option value="kiikala">Kiikala</option>\n<option value="kiikoinen">Kiikoinen</option>\n<option value="kiiminki">Kiiminki</option>\n<option value="kinnula">Kinnula</option>\n<option value="kirkkonummi">Kirkkonummi</option>\n<option value="kisko">Kisko</option>\n<option value="kitee">Kitee</option>\n<option value="kittila">Kittil\xe4</option>\n<option value="kiukainen">Kiukainen</option>\n<option value="kiuruvesi">Kiuruvesi</option>\n<option value="kivijarvi">Kivij\xe4rvi</option>\n<option value="kokemaki">Kokem\xe4ki</option>\n<option value="kokkola">Kokkola</option>\n<option value="kolari">Kolari</option>\n<option value="konnevesi">Konnevesi</option>\n<option value="kontiolahti">Kontiolahti</option>\n<option value="korpilahti">Korpilahti</option>\n<option value="korppoo">Korppoo</option>\n<option value="korsnas">Korsn\xe4s</option>\n<option value="kortesjarvi">Kortesj\xe4rvi</option>\n<option value="koskitl">KoskiTl</option>\n<option value="kotka">Kotka</option>\n<option value="kouvola">Kouvola</option>\n<option value="kristiinankaupunki">Kristiinankaupunki</option>\n<option value="kruunupyy">Kruunupyy</option>\n<option value="kuhmalahti">Kuhmalahti</option>\n<option value="kuhmo">Kuhmo</option>\n<option value="kuhmoinen">Kuhmoinen</option>\n<option value="kumlinge">Kumlinge</option>\n<option value="kuopio">Kuopio</option>\n<option value="kuortane">Kuortane</option>\n<option value="kurikka">Kurikka</option>\n<option value="kuru">Kuru</option>\n<option value="kustavi">Kustavi</option>\n<option value="kuusamo">Kuusamo</option>\n<option value="kuusankoski">Kuusankoski</option>\n<option value="kuusjoki">Kuusjoki</option>\n<option value="kylmakoski">Kylm\xe4koski</option>\n<option value="kyyjarvi">Kyyj\xe4rvi</option>\n<option value="kalvia">K\xe4lvi\xe4</option>\n<option value="karkola">K\xe4rk\xf6l\xe4</option>\n<option value="karsamaki">K\xe4rs\xe4m\xe4ki</option>\n<option value="kokar">K\xf6kar</option>\n<option value="koylio">K\xf6yli\xf6</option>\n<option value="lahti">Lahti</option>\n<option value="laihia">Laihia</option>\n<option value="laitila">Laitila</option>\n<option value="lammi">Lammi</option>\n<option value="lapinjarvi">Lapinj\xe4rvi</option>\n<option value="lapinlahti">Lapinlahti</option>\n<option value="lappajarvi">Lappaj\xe4rvi</option>\n<option value="lappeenranta">Lappeenranta</option>\n<option value="lappi">Lappi</option>\n<option value="lapua">Lapua</option>\n<option value="laukaa">Laukaa</option>\n<option value="lavia">Lavia</option>\n<option value="lehtimaki">Lehtim\xe4ki</option>\n<option value="leivonmaki">Leivonm\xe4ki</option>\n<option value="lemi">Lemi</option>\n<option value="lemland">Lemland</option>\n<option value="lempaala">Lemp\xe4\xe4l\xe4</option>\n<option value="lemu">Lemu</option>\n<option value="leppavirta">Lepp\xe4virta</option>\n<option value="lestijarvi">Lestij\xe4rvi</option>\n<option value="lieksa">Lieksa</option>\n<option value="lieto">Lieto</option>\n<option value="liljendal">Liljendal</option>\n<option value="liminka">Liminka</option>\n<option value="liperi">Liperi</option>\n<option value="lohja">Lohja</option>\n<option value="lohtaja">Lohtaja</option>\n<option value="loimaa">Loimaa</option>\n<option value="loppi">Loppi</option>\n<option value="loviisa">Loviisa</option>\n<option value="luhanka">Luhanka</option>\n<option value="lumijoki">Lumijoki</option>\n<option value="lumparland">Lumparland</option>\n<option value="luoto">Luoto</option>\n<option value="luumaki">Luum\xe4ki</option>\n<option value="luvia">Luvia</option>\n<option value="maalahti">Maalahti</option>\n<option value="maaninka">Maaninka</option>\n<option value="maarianhamina">Maarianhamina</option>\n<option value="marttila">Marttila</option>\n<option value="masku">Masku</option>\n<option value="mellila">Mellil\xe4</option>\n<option value="merijarvi">Merij\xe4rvi</option>\n<option value="merikarvia">Merikarvia</option>\n<option value="merimasku">Merimasku</option>\n<option value="miehikkala">Miehikk\xe4l\xe4</option>\n<option value="mikkeli">Mikkeli</option>\n<option value="mouhijarvi">Mouhij\xe4rvi</option>\n<option value="muhos">Muhos</option>\n<option value="multia">Multia</option>\n<option value="muonio">Muonio</option>\n<option value="mustasaari">Mustasaari</option>\n<option value="muurame">Muurame</option>\n<option value="muurla">Muurla</option>\n<option value="mynamaki">Myn\xe4m\xe4ki</option>\n<option value="myrskyla">Myrskyl\xe4</option>\n<option value="mantsala">M\xe4nts\xe4l\xe4</option>\n<option value="mantta">M\xe4ntt\xe4</option>\n<option value="mantyharju">M\xe4ntyharju</option>\n<option value="naantali">Naantali</option>\n<option value="nakkila">Nakkila</option>\n<option value="nastola">Nastola</option>\n<option value="nauvo">Nauvo</option>\n<option value="nilsia">Nilsi\xe4</option>\n<option value="nivala">Nivala</option>\n<option value="nokia">Nokia</option>\n<option value="noormarkku">Noormarkku</option>\n<option value="nousiainen">Nousiainen</option>\n<option value="nummi-pusula">Nummi-Pusula</option>\n<option value="nurmes">Nurmes</option>\n<option value="nurmijarvi">Nurmij\xe4rvi</option>\n<option value="nurmo">Nurmo</option>\n<option value="narpio">N\xe4rpi\xf6</option>\n<option value="oravainen">Oravainen</option>\n<option value="orimattila">Orimattila</option>\n<option value="oripaa">Orip\xe4\xe4</option>\n<option value="orivesi">Orivesi</option>\n<option value="oulainen">Oulainen</option>\n<option value="oulu">Oulu</option>\n<option value="oulunsalo">Oulunsalo</option>\n<option value="outokumpu">Outokumpu</option>\n<option value="padasjoki">Padasjoki</option>\n<option value="paimio">Paimio</option>\n<option value="paltamo">Paltamo</option>\n<option value="parainen">Parainen</option>\n<option value="parikkala">Parikkala</option>\n<option value="parkano">Parkano</option>\n<option value="pedersore">Peders\xf6re</option>\n<option value="pelkosenniemi">Pelkosenniemi</option>\n<option value="pello">Pello</option>\n<option value="perho">Perho</option>\n<option value="pernaja">Pernaja</option>\n<option value="pernio">Perni\xf6</option>\n<option value="pertteli">Pertteli</option>\n<option value="pertunmaa">Pertunmaa</option>\n<option value="petajavesi">Pet\xe4j\xe4vesi</option>\n<option value="pieksamaki">Pieks\xe4m\xe4ki</option>\n<option value="pielavesi">Pielavesi</option>\n<option value="pietarsaari">Pietarsaari</option>\n<option value="pihtipudas">Pihtipudas</option>\n<option value="piikkio">Piikki\xf6</option>\n<option value="piippola">Piippola</option>\n<option value="pirkkala">Pirkkala</option>\n<option value="pohja">Pohja</option>\n<option value="polvijarvi">Polvij\xe4rvi</option>\n<option value="pomarkku">Pomarkku</option>\n<option value="pori">Pori</option>\n<option value="pornainen">Pornainen</option>\n<option value="porvoo">Porvoo</option>\n<option value="posio">Posio</option>\n<option value="pudasjarvi">Pudasj\xe4rvi</option>\n<option value="pukkila">Pukkila</option>\n<option value="pulkkila">Pulkkila</option>\n<option value="punkaharju">Punkaharju</option>\n<option value="punkalaidun">Punkalaidun</option>\n<option value="puolanka">Puolanka</option>\n<option value="puumala">Puumala</option>\n<option value="pyhtaa">Pyht\xe4\xe4</option>\n<option value="pyhajoki">Pyh\xe4joki</option>\n<option value="pyhajarvi">Pyh\xe4j\xe4rvi</option>\n<option value="pyhanta">Pyh\xe4nt\xe4</option>\n<option value="pyharanta">Pyh\xe4ranta</option>\n<option value="pyhaselka">Pyh\xe4selk\xe4</option>\n<option value="pylkonmaki">Pylk\xf6nm\xe4ki</option>\n<option value="palkane">P\xe4lk\xe4ne</option>\n<option value="poytya">P\xf6yty\xe4</option>\n<option value="raahe">Raahe</option>\n<option value="raisio">Raisio</option>\n<option value="rantasalmi">Rantasalmi</option>\n<option value="rantsila">Rantsila</option>\n<option value="ranua">Ranua</option>\n<option value="rauma">Rauma</option>\n<option value="rautalampi">Rautalampi</option>\n<option value="rautavaara">Rautavaara</option>\n<option value="rautjarvi">Rautj\xe4rvi</option>\n<option value="reisjarvi">Reisj\xe4rvi</option>\n<option value="renko">Renko</option>\n<option value="riihimaki">Riihim\xe4ki</option>\n<option value="ristiina">Ristiina</option>\n<option value="ristijarvi">Ristij\xe4rvi</option>\n<option value="rovaniemi">Rovaniemi</option>\n<option value="ruokolahti">Ruokolahti</option>\n<option value="ruotsinpyhtaa">Ruotsinpyht\xe4\xe4</option>\n<option value="ruovesi">Ruovesi</option>\n<option value="rusko">Rusko</option>\n<option value="rymattyla">Rym\xe4ttyl\xe4</option>\n<option value="raakkyla">R\xe4\xe4kkyl\xe4</option>\n<option value="saarijarvi">Saarij\xe4rvi</option>\n<option value="salla">Salla</option>\n<option value="salo">Salo</option>\n<option value="saltvik">Saltvik</option>\n<option value="sammatti">Sammatti</option>\n<option value="sauvo">Sauvo</option>\n<option value="savitaipale">Savitaipale</option>\n<option value="savonlinna">Savonlinna</option>\n<option value="savonranta">Savonranta</option>\n<option value="savukoski">Savukoski</option>\n<option value="seinajoki">Sein\xe4joki</option>\n<option value="sievi">Sievi</option>\n<option value="siikainen">Siikainen</option>\n<option value="siikajoki">Siikajoki</option>\n<option value="siilinjarvi">Siilinj\xe4rvi</option>\n<option value="simo">Simo</option>\n<option value="sipoo">Sipoo</option>\n<option value="siuntio">Siuntio</option>\n<option value="sodankyla">Sodankyl\xe4</option>\n<option value="soini">Soini</option>\n<option value="somero">Somero</option>\n<option value="sonkajarvi">Sonkaj\xe4rvi</option>\n<option value="sotkamo">Sotkamo</option>\n<option value="sottunga">Sottunga</option>\n<option value="sulkava">Sulkava</option>\n<option value="sund">Sund</option>\n<option value="suomenniemi">Suomenniemi</option>\n<option value="suomusjarvi">Suomusj\xe4rvi</option>\n<option value="suomussalmi">Suomussalmi</option>\n<option value="suonenjoki">Suonenjoki</option>\n<option value="sysma">Sysm\xe4</option>\n<option value="sakyla">S\xe4kyl\xe4</option>\n<option value="sarkisalo">S\xe4rkisalo</option>\n<option value="taipalsaari">Taipalsaari</option>\n<option value="taivalkoski">Taivalkoski</option>\n<option value="taivassalo">Taivassalo</option>\n<option value="tammela">Tammela</option>\n<option value="tammisaari">Tammisaari</option>\n<option value="tampere">Tampere</option>\n<option value="tarvasjoki">Tarvasjoki</option>\n<option value="tervo">Tervo</option>\n<option value="tervola">Tervola</option>\n<option value="teuva">Teuva</option>\n<option value="tohmajarvi">Tohmaj\xe4rvi</option>\n<option value="toholampi">Toholampi</option>\n<option value="toivakka">Toivakka</option>\n<option value="tornio">Tornio</option>\n<option value="turku" selected="selected">Turku</option>\n<option value="tuulos">Tuulos</option>\n<option value="tuusniemi">Tuusniemi</option>\n<option value="tuusula">Tuusula</option>\n<option value="tyrnava">Tyrn\xe4v\xe4</option>\n<option value="toysa">T\xf6ys\xe4</option>\n<option value="ullava">Ullava</option>\n<option value="ulvila">Ulvila</option>\n<option value="urjala">Urjala</option>\n<option value="utajarvi">Utaj\xe4rvi</option>\n<option value="utsjoki">Utsjoki</option>\n<option value="uurainen">Uurainen</option>\n<option value="uusikaarlepyy">Uusikaarlepyy</option>\n<option value="uusikaupunki">Uusikaupunki</option>\n<option value="vaala">Vaala</option>\n<option value="vaasa">Vaasa</option>\n<option value="vahto">Vahto</option>\n<option value="valkeakoski">Valkeakoski</option>\n<option value="valkeala">Valkeala</option>\n<option value="valtimo">Valtimo</option>\n<option value="vammala">Vammala</option>\n<option value="vampula">Vampula</option>\n<option value="vantaa">Vantaa</option>\n<option value="varkaus">Varkaus</option>\n<option value="varpaisjarvi">Varpaisj\xe4rvi</option>\n<option value="vehmaa">Vehmaa</option>\n<option value="velkua">Velkua</option>\n<option value="vesanto">Vesanto</option>\n<option value="vesilahti">Vesilahti</option>\n<option value="veteli">Veteli</option>\n<option value="vierema">Vierem\xe4</option>\n<option value="vihanti">Vihanti</option>\n<option value="vihti">Vihti</option>\n<option value="viitasaari">Viitasaari</option>\n<option value="vilppula">Vilppula</option>\n<option value="vimpeli">Vimpeli</option>\n<option value="virolahti">Virolahti</option>\n<option value="virrat">Virrat</option>\n<option value="vardo">V\xe5rd\xf6</option>\n<option value="vahakyro">V\xe4h\xe4kyr\xf6</option>\n<option value="vastanfjard">V\xe4stanfj\xe4rd</option>\n<option value="voyri-maksamaa">V\xf6yri-Maksamaa</option>\n<option value="yliharma">Ylih\xe4rm\xe4</option>\n<option value="yli-ii">Yli-Ii</option>\n<option value="ylikiiminki">Ylikiiminki</option>\n<option value="ylistaro">Ylistaro</option>\n<option value="ylitornio">Ylitornio</option>\n<option value="ylivieska">Ylivieska</option>\n<option value="ylamaa">Yl\xe4maa</option>\n<option value="ylane">Yl\xe4ne</option>\n<option value="ylojarvi">Yl\xf6j\xe4rvi</option>\n<option value="ypaja">Yp\xe4j\xe4</option>\n<option value="aetsa">\xc4ets\xe4</option>\n<option value="ahtari">\xc4ht\xe4ri</option>\n<option value="aanekoski">\xc4\xe4nekoski</option>\n</select>' + +# FISocialSecurityNumber +############################################################## + +>>> from django.contrib.localflavor.fi.forms import FISocialSecurityNumber +>>> f = FISocialSecurityNumber() +>>> f.clean('010101-0101') +u'010101-0101' +>>> f.clean('010101+0101') +u'010101+0101' +>>> f.clean('010101A0101') +u'010101A0101' +>>> f.clean('101010-0102') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid Finnish social security number.'] +>>> f.clean('10a010-0101') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid Finnish social security number.'] +>>> f.clean('101010-0\xe401') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid Finnish social security number.'] +>>> f.clean('101010b0101') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid Finnish social security number.'] +>>> 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 = FISocialSecurityNumber(required=False) +>>> f.clean('010101-0101') +u'010101-0101' +>>> f.clean(None) +u'' +>>> f.clean('') +u'' + +# BRZipCodeField ############################################################ +>>> from django.contrib.localflavor.br.forms import BRZipCodeField +>>> f = BRZipCodeField() +>>> f.clean('12345-123') +u'12345-123' +>>> f.clean('12345_123') +Traceback (most recent call last): +... +ValidationError: [u'Enter a zip code in the format XXXXX-XXX.'] +>>> f.clean('1234-123') +Traceback (most recent call last): +... +ValidationError: [u'Enter a zip code in the format XXXXX-XXX.'] +>>> f.clean('abcde-abc') +Traceback (most recent call last): +... +ValidationError: [u'Enter a zip code in the format XXXXX-XXX.'] +>>> f.clean('12345-') +Traceback (most recent call last): +... +ValidationError: [u'Enter a zip code in the format XXXXX-XXX.'] +>>> f.clean('-123') +Traceback (most recent call last): +... +ValidationError: [u'Enter a zip code in the format XXXXX-XXX.'] +>>> 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 = BRZipCodeField(required=False) +>>> f.clean(None) +u'' +>>> f.clean('') +u'' +>>> f.clean('-123') +Traceback (most recent call last): +... +ValidationError: [u'Enter a zip code in the format XXXXX-XXX.'] +>>> f.clean('12345-') +Traceback (most recent call last): +... +ValidationError: [u'Enter a zip code in the format XXXXX-XXX.'] +>>> f.clean('abcde-abc') +Traceback (most recent call last): +... +ValidationError: [u'Enter a zip code in the format XXXXX-XXX.'] +>>> f.clean('1234-123') +Traceback (most recent call last): +... +ValidationError: [u'Enter a zip code in the format XXXXX-XXX.'] +>>> f.clean('12345_123') +Traceback (most recent call last): +... +ValidationError: [u'Enter a zip code in the format XXXXX-XXX.'] +>>> f.clean('12345-123') +u'12345-123' + +# BRCNPJField ############################################################ + +>>> from django.contrib.localflavor.br.forms import BRCNPJField +>>> f = BRCNPJField(required=True) +>>> f.clean('') +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] +>>> f.clean('12-345-678/9012-10') +Traceback (most recent call last): +... +ValidationError: [u'Invalid CNPJ number.'] +>>> f.clean('12.345.678/9012-10') +Traceback (most recent call last): +... +ValidationError: [u'Invalid CNPJ number.'] +>>> f.clean('12345678/9012-10') +Traceback (most recent call last): +... +ValidationError: [u'Invalid CNPJ number.'] +>>> f.clean('64.132.916/0001-88') +'64.132.916/0001-88' +>>> f.clean('64-132-916/0001-88') +'64-132-916/0001-88' +>>> f.clean('64132916/0001-88') +'64132916/0001-88' +>>> f.clean('64.132.916/0001-XX') +Traceback (most recent call last): +... +ValidationError: [u'This field requires only numbers.'] +>>> f = BRCNPJField(required=False) +>>> f.clean('') +u'' + +# BRCPFField ################################################################# + +>>> from django.contrib.localflavor.br.forms import BRCPFField +>>> f = BRCPFField() +>>> 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('489.294.654-54') +Traceback (most recent call last): +... +ValidationError: [u'Invalid CPF number.'] +>>> f.clean('295.669.575-98') +Traceback (most recent call last): +... +ValidationError: [u'Invalid CPF number.'] +>>> f.clean('539.315.127-22') +Traceback (most recent call last): +... +ValidationError: [u'Invalid CPF number.'] +>>> f.clean('663.256.017-26') +u'663.256.017-26' +>>> f.clean('66325601726') +u'66325601726' +>>> f.clean('375.788.573-20') +u'375.788.573-20' +>>> f.clean('84828509895') +u'84828509895' +>>> f.clean('375.788.573-XX') +Traceback (most recent call last): +... +ValidationError: [u'This field requires only numbers.'] +>>> f.clean('375.788.573-000') +Traceback (most recent call last): +... +ValidationError: [u'Ensure this value has at most 14 characters.'] +>>> f.clean('123.456.78') +Traceback (most recent call last): +... +ValidationError: [u'Ensure this value has at least 11 characters.'] +>>> f.clean('123456789555') +Traceback (most recent call last): +... +ValidationError: [u'This field requires at most 11 digits or 14 characters.'] +>>> f = BRCPFField(required=False) +>>> f.clean('') +u'' +>>> f.clean(None) +u'' + +# BRPhoneNumberField ######################################################### + +>>> from django.contrib.localflavor.br.forms import BRPhoneNumberField +>>> f = BRPhoneNumberField() +>>> f.clean('41-3562-3464') +u'41-3562-3464' +>>> f.clean('4135623464') +u'41-3562-3464' +>>> f.clean('41 3562-3464') +u'41-3562-3464' +>>> f.clean('41 3562 3464') +u'41-3562-3464' +>>> f.clean('(41) 3562 3464') +u'41-3562-3464' +>>> f.clean('41.3562.3464') +u'41-3562-3464' +>>> f.clean('41.3562-3464') +u'41-3562-3464' +>>> f.clean(' (41) 3562.3464') +u'41-3562-3464' +>>> 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 = BRPhoneNumberField(required=False) +>>> f.clean('') +u'' +>>> f.clean(None) +u'' +>>> f.clean(' (41) 3562.3464') +u'41-3562-3464' +>>> f.clean('41.3562-3464') +u'41-3562-3464' +>>> f.clean('(41) 3562 3464') +u'41-3562-3464' +>>> f.clean('4135623464') +u'41-3562-3464' +>>> f.clean('41 3562-3464') +u'41-3562-3464' + +# BRStateSelect ############################################################## + +>>> from django.contrib.localflavor.br.forms import BRStateSelect +>>> w = BRStateSelect() +>>> w.render('states', 'PR') +u'<select name="states">\n<option value="AC">Acre</option>\n<option value="AL">Alagoas</option>\n<option value="AP">Amap\xe1</option>\n<option value="AM">Amazonas</option>\n<option value="BA">Bahia</option>\n<option value="CE">Cear\xe1</option>\n<option value="DF">Distrito Federal</option>\n<option value="ES">Esp\xedrito Santo</option>\n<option value="GO">Goi\xe1s</option>\n<option value="MA">Maranh\xe3o</option>\n<option value="MT">Mato Grosso</option>\n<option value="MS">Mato Grosso do Sul</option>\n<option value="MG">Minas Gerais</option>\n<option value="PA">Par\xe1</option>\n<option value="PB">Para\xedba</option>\n<option value="PR" selected="selected">Paran\xe1</option>\n<option value="PE">Pernambuco</option>\n<option value="PI">Piau\xed</option>\n<option value="RJ">Rio de Janeiro</option>\n<option value="RN">Rio Grande do Norte</option>\n<option value="RS">Rio Grande do Sul</option>\n<option value="RO">Rond\xf4nia</option>\n<option value="RR">Roraima</option>\n<option value="SC">Santa Catarina</option>\n<option value="SP">S\xe3o Paulo</option>\n<option value="SE">Sergipe</option>\n<option value="TO">Tocantins</option>\n</select>' + +# DEZipCodeField ############################################################## + +>>> from django.contrib.localflavor.de.forms import DEZipCodeField +>>> f = DEZipCodeField() +>>> f.clean('99423') +u'99423' +>>> f.clean(' 99423') +Traceback (most recent call last): +... +ValidationError: [u'Enter a zip code in the format XXXXX.'] + +# DEStateSelect ############################################################# + +>>> from django.contrib.localflavor.de.forms import DEStateSelect +>>> w = DEStateSelect() +>>> w.render('states', 'TH') +u'<select name="states">\n<option value="BW">Baden-Wuerttemberg</option>\n<option value="BY">Bavaria</option>\n<option value="BE">Berlin</option>\n<option value="BB">Brandenburg</option>\n<option value="HB">Bremen</option>\n<option value="HH">Hamburg</option>\n<option value="HE">Hessen</option>\n<option value="MV">Mecklenburg-Western Pomerania</option>\n<option value="NI">Lower Saxony</option>\n<option value="NW">North Rhine-Westphalia</option>\n<option value="RP">Rhineland-Palatinate</option>\n<option value="SL">Saarland</option>\n<option value="SN">Saxony</option>\n<option value="ST">Saxony-Anhalt</option>\n<option value="SH">Schleswig-Holstein</option>\n<option value="TH" selected="selected">Thuringia</option>\n</select>' + +# DEIdentityCardNumberField ################################################# + +>>> from django.contrib.localflavor.de.forms import DEIdentityCardNumberField +>>> f = DEIdentityCardNumberField() +>>> f.clean('7549313035D-6004103-0903042-0') +u'7549313035D-6004103-0903042-0' +>>> f.clean('9786324830D 6104243 0910271 2') +u'9786324830D-6104243-0910271-2' +>>> f.clean('0434657485D-6407276-0508137-9') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid German identity card number in XXXXXXXXXXX-XXXXXXX-XXXXXXX-X format.'] + +# CHZipCodeField ############################################################ + +>>> from django.contrib.localflavor.ch.forms import CHZipCodeField +>>> f = CHZipCodeField() +>>> f.clean('800x') +Traceback (most recent call last): +... +ValidationError: [u'Enter a zip code in the format XXXX.'] +>>> f.clean('80 00') +Traceback (most recent call last): +... +ValidationError: [u'Enter a zip code in the format XXXX.'] +>>> f.clean('8000') +u'8000' + +# CHPhoneNumberField ######################################################## + +>>> from django.contrib.localflavor.ch.forms import CHPhoneNumberField +>>> f = CHPhoneNumberField() +>>> f.clean('01234567890') +Traceback (most recent call last): +... +ValidationError: [u'Phone numbers must be in 0XX XXX XX XX format.'] +>>> f.clean('1234567890') +Traceback (most recent call last): +... +ValidationError: [u'Phone numbers must be in 0XX XXX XX XX format.'] +>>> f.clean('0123456789') +u'012 345 67 89' + +# CHIdentityCardNumberField ################################################# + +>>> from django.contrib.localflavor.ch.forms import CHIdentityCardNumberField +>>> f = CHIdentityCardNumberField() +>>> f.clean('C1234567<0') +u'C1234567<0' +>>> f.clean('C1234567<1') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid Swiss identity or passport card number in X1234567<0 or 1234567890 format.'] +>>> f.clean('2123456700') +u'2123456700' +>>> f.clean('2123456701') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid Swiss identity or passport card number in X1234567<0 or 1234567890 format.'] + +# CHStateSelect ############################################################# + +>>> from django.contrib.localflavor.ch.forms import CHStateSelect +>>> w = CHStateSelect() +>>> w.render('state', 'AG') +u'<select name="state">\n<option value="AG" selected="selected">Aargau</option>\n<option value="AI">Appenzell Innerrhoden</option>\n<option value="AR">Appenzell Ausserrhoden</option>\n<option value="BS">Basel-Stadt</option>\n<option value="BL">Basel-Land</option>\n<option value="BE">Berne</option>\n<option value="FR">Fribourg</option>\n<option value="GE">Geneva</option>\n<option value="GL">Glarus</option>\n<option value="GR">Graubuenden</option>\n<option value="JU">Jura</option>\n<option value="LU">Lucerne</option>\n<option value="NE">Neuchatel</option>\n<option value="NW">Nidwalden</option>\n<option value="OW">Obwalden</option>\n<option value="SH">Schaffhausen</option>\n<option value="SZ">Schwyz</option>\n<option value="SO">Solothurn</option>\n<option value="SG">St. Gallen</option>\n<option value="TG">Thurgau</option>\n<option value="TI">Ticino</option>\n<option value="UR">Uri</option>\n<option value="VS">Valais</option>\n<option value="VD">Vaud</option>\n<option value="ZG">Zug</option>\n<option value="ZH">Zurich</option>\n</select>' + +## AUPostCodeField ########################################################## + +A field that accepts a four digit Australian post code. + +>>> from django.contrib.localflavor.au.forms import AUPostCodeField +>>> f = AUPostCodeField() +>>> f.clean('1234') +u'1234' +>>> f.clean('2000') +u'2000' +>>> f.clean('abcd') +Traceback (most recent call last): +... +ValidationError: [u'Enter a 4 digit post code.'] +>>> f.clean('20001') +Traceback (most recent call last): +... +ValidationError: [u'Enter a 4 digit post code.'] +>>> 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 = AUPostCodeField(required=False) +>>> f.clean('1234') +u'1234' +>>> f.clean('2000') +u'2000' +>>> f.clean('abcd') +Traceback (most recent call last): +... +ValidationError: [u'Enter a 4 digit post code.'] +>>> f.clean('20001') +Traceback (most recent call last): +... +ValidationError: [u'Enter a 4 digit post code.'] +>>> f.clean(None) +u'' +>>> f.clean('') +u'' + +## AUPhoneNumberField ######################################################## + +A field that accepts a 10 digit Australian phone number. +llows spaces and parentheses around area code. + +>>> from django.contrib.localflavor.au.forms import AUPhoneNumberField +>>> f = AUPhoneNumberField() +>>> f.clean('1234567890') +u'1234567890' +>>> f.clean('0213456789') +u'0213456789' +>>> f.clean('02 13 45 67 89') +u'0213456789' +>>> f.clean('(02) 1345 6789') +u'0213456789' +>>> f.clean('(02) 1345-6789') +u'0213456789' +>>> f.clean('(02)1345-6789') +u'0213456789' +>>> f.clean('0408 123 456') +u'0408123456' +>>> f.clean('123') +Traceback (most recent call last): +... +ValidationError: [u'Phone numbers must contain 10 digits.'] +>>> f.clean('1800DJANGO') +Traceback (most recent call last): +... +ValidationError: [u'Phone numbers must contain 10 digits.'] +>>> 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 = AUPhoneNumberField(required=False) +>>> f.clean('1234567890') +u'1234567890' +>>> f.clean('0213456789') +u'0213456789' +>>> f.clean('02 13 45 67 89') +u'0213456789' +>>> f.clean('(02) 1345 6789') +u'0213456789' +>>> f.clean('(02) 1345-6789') +u'0213456789' +>>> f.clean('(02)1345-6789') +u'0213456789' +>>> f.clean('0408 123 456') +u'0408123456' +>>> f.clean('123') +Traceback (most recent call last): +... +ValidationError: [u'Phone numbers must contain 10 digits.'] +>>> f.clean('1800DJANGO') +Traceback (most recent call last): +... +ValidationError: [u'Phone numbers must contain 10 digits.'] +>>> f.clean(None) +u'' +>>> f.clean('') +u'' + +## AUStateSelect ############################################################# + +AUStateSelect is a Select widget that uses a list of Australian +states/territories as its choices. + +>>> from django.contrib.localflavor.au.forms import AUStateSelect +>>> f = AUStateSelect() +>>> print f.render('state', 'NSW') +<select name="state"> +<option value="ACT">Australian Capital Territory</option> +<option value="NSW" selected="selected">New South Wales</option> +<option value="NT">Northern Territory</option> +<option value="QLD">Queensland</option> +<option value="SA">South Australia</option> +<option value="TAS">Tasmania</option> +<option value="VIC">Victoria</option> +<option value="WA">Western Australia</option> +</select> + +## ISIdNumberField ############################################################# + +>>> from django.contrib.localflavor.is_.forms import * +>>> f = ISIdNumberField() +>>> f.clean('2308803449') +u'230880-3449' +>>> f.clean('230880-3449') +u'230880-3449' +>>> f.clean('230880 3449') +u'230880-3449' +>>> f.clean('230880343') +Traceback (most recent call last): +... +ValidationError: [u'Ensure this value has at least 10 characters.'] +>>> f.clean('230880343234') +Traceback (most recent call last): +... +ValidationError: [u'Ensure this value has at most 11 characters.'] +>>> f.clean('abcdefghijk') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid Icelandic identification number. The format is XXXXXX-XXXX.'] +>>> 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('2308803439') +Traceback (most recent call last): +... +ValidationError: [u'The Icelandic identification number is not valid.'] +>>> f.clean('2308803440') +u'230880-3440' +>>> f = ISIdNumberField(required=False) +>>> f.clean(None) +u'' +>>> f.clean('') +u'' + +## ISPhoneNumberField ############################################################# + +>>> from django.contrib.localflavor.is_.forms import * +>>> f = ISPhoneNumberField() +>>> f.clean('1234567') +u'1234567' +>>> f.clean('123 4567') +u'1234567' +>>> f.clean('123-4567') +u'1234567' +>>> f.clean('123-456') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid value.'] +>>> f.clean('123456') +Traceback (most recent call last): +... +ValidationError: [u'Ensure this value has at least 7 characters.'] +>>> f.clean('123456555') +Traceback (most recent call last): +... +ValidationError: [u'Ensure this value has at most 8 characters.'] +>>> f.clean('abcdefg') +Traceback (most recent call last): +ValidationError: [u'Enter a valid value.'] +>>> f.clean(' 1234567 ') +Traceback (most recent call last): +... +ValidationError: [u'Ensure this value has at most 8 characters.'] +>>> f.clean(' 12367 ') +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.clean(None) +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] +>>> f = ISPhoneNumberField(required=False) +>>> f.clean(None) +u'' +>>> f.clean('') +u'' + +## ISPostalCodeSelect ############################################################# + +>>> from django.contrib.localflavor.is_.forms import * +>>> f = ISPostalCodeSelect() + +>>> f.render('foo', 'bar') +u'<select name="foo">\n<option value="101">101 Reykjav\xedk</option>\n<option value="103">103 Reykjav\xedk</option>\n<option value="104">104 Reykjav\xedk</option>\n<option value="105">105 Reykjav\xedk</option>\n<option value="107">107 Reykjav\xedk</option>\n<option value="108">108 Reykjav\xedk</option>\n<option value="109">109 Reykjav\xedk</option>\n<option value="110">110 Reykjav\xedk</option>\n<option value="111">111 Reykjav\xedk</option>\n<option value="112">112 Reykjav\xedk</option>\n<option value="113">113 Reykjav\xedk</option>\n<option value="116">116 Kjalarnes</option>\n<option value="121">121 Reykjav\xedk</option>\n<option value="123">123 Reykjav\xedk</option>\n<option value="124">124 Reykjav\xedk</option>\n<option value="125">125 Reykjav\xedk</option>\n<option value="127">127 Reykjav\xedk</option>\n<option value="128">128 Reykjav\xedk</option>\n<option value="129">129 Reykjav\xedk</option>\n<option value="130">130 Reykjav\xedk</option>\n<option value="132">132 Reykjav\xedk</option>\n<option value="150">150 Reykjav\xedk</option>\n<option value="155">155 Reykjav\xedk</option>\n<option value="170">170 Seltjarnarnes</option>\n<option value="172">172 Seltjarnarnes</option>\n<option value="190">190 Vogar</option>\n<option value="200">200 K\xf3pavogur</option>\n<option value="201">201 K\xf3pavogur</option>\n<option value="202">202 K\xf3pavogur</option>\n<option value="203">203 K\xf3pavogur</option>\n<option value="210">210 Gar\xf0ab\xe6r</option>\n<option value="212">212 Gar\xf0ab\xe6r</option>\n<option value="220">220 Hafnarfj\xf6r\xf0ur</option>\n<option value="221">221 Hafnarfj\xf6r\xf0ur</option>\n<option value="222">222 Hafnarfj\xf6r\xf0ur</option>\n<option value="225">225 \xc1lftanes</option>\n<option value="230">230 Reykjanesb\xe6r</option>\n<option value="232">232 Reykjanesb\xe6r</option>\n<option value="233">233 Reykjanesb\xe6r</option>\n<option value="235">235 Keflav\xedkurflugv\xf6llur</option>\n<option value="240">240 Grindav\xedk</option>\n<option value="245">245 Sandger\xf0i</option>\n<option value="250">250 Gar\xf0ur</option>\n<option value="260">260 Reykjanesb\xe6r</option>\n<option value="270">270 Mosfellsb\xe6r</option>\n<option value="300">300 Akranes</option>\n<option value="301">301 Akranes</option>\n<option value="302">302 Akranes</option>\n<option value="310">310 Borgarnes</option>\n<option value="311">311 Borgarnes</option>\n<option value="320">320 Reykholt \xed Borgarfir\xf0i</option>\n<option value="340">340 Stykkish\xf3lmur</option>\n<option value="345">345 Flatey \xe1 Brei\xf0afir\xf0i</option>\n<option value="350">350 Grundarfj\xf6r\xf0ur</option>\n<option value="355">355 \xd3lafsv\xedk</option>\n<option value="356">356 Sn\xe6fellsb\xe6r</option>\n<option value="360">360 Hellissandur</option>\n<option value="370">370 B\xfa\xf0ardalur</option>\n<option value="371">371 B\xfa\xf0ardalur</option>\n<option value="380">380 Reykh\xf3lahreppur</option>\n<option value="400">400 \xcdsafj\xf6r\xf0ur</option>\n<option value="401">401 \xcdsafj\xf6r\xf0ur</option>\n<option value="410">410 Hn\xedfsdalur</option>\n<option value="415">415 Bolungarv\xedk</option>\n<option value="420">420 S\xfa\xf0av\xedk</option>\n<option value="425">425 Flateyri</option>\n<option value="430">430 Su\xf0ureyri</option>\n<option value="450">450 Patreksfj\xf6r\xf0ur</option>\n<option value="451">451 Patreksfj\xf6r\xf0ur</option>\n<option value="460">460 T\xe1lknafj\xf6r\xf0ur</option>\n<option value="465">465 B\xedldudalur</option>\n<option value="470">470 \xdeingeyri</option>\n<option value="471">471 \xdeingeyri</option>\n<option value="500">500 Sta\xf0ur</option>\n<option value="510">510 H\xf3lmav\xedk</option>\n<option value="512">512 H\xf3lmav\xedk</option>\n<option value="520">520 Drangsnes</option>\n<option value="522">522 Kj\xf6rvogur</option>\n<option value="523">523 B\xe6r</option>\n<option value="524">524 Nor\xf0urfj\xf6r\xf0ur</option>\n<option value="530">530 Hvammstangi</option>\n<option value="531">531 Hvammstangi</option>\n<option value="540">540 Bl\xf6ndu\xf3s</option>\n<option value="541">541 Bl\xf6ndu\xf3s</option>\n<option value="545">545 Skagastr\xf6nd</option>\n<option value="550">550 Sau\xf0\xe1rkr\xf3kur</option>\n<option value="551">551 Sau\xf0\xe1rkr\xf3kur</option>\n<option value="560">560 Varmahl\xed\xf0</option>\n<option value="565">565 Hofs\xf3s</option>\n<option value="566">566 Hofs\xf3s</option>\n<option value="570">570 Flj\xf3t</option>\n<option value="580">580 Siglufj\xf6r\xf0ur</option>\n<option value="600">600 Akureyri</option>\n<option value="601">601 Akureyri</option>\n<option value="602">602 Akureyri</option>\n<option value="603">603 Akureyri</option>\n<option value="610">610 Greniv\xedk</option>\n<option value="611">611 Gr\xedmsey</option>\n<option value="620">620 Dalv\xedk</option>\n<option value="621">621 Dalv\xedk</option>\n<option value="625">625 \xd3lafsfj\xf6r\xf0ur</option>\n<option value="630">630 Hr\xedsey</option>\n<option value="640">640 H\xfasav\xedk</option>\n<option value="641">641 H\xfasav\xedk</option>\n<option value="645">645 Fossh\xf3ll</option>\n<option value="650">650 Laugar</option>\n<option value="660">660 M\xfdvatn</option>\n<option value="670">670 K\xf3pasker</option>\n<option value="671">671 K\xf3pasker</option>\n<option value="675">675 Raufarh\xf6fn</option>\n<option value="680">680 \xde\xf3rsh\xf6fn</option>\n<option value="681">681 \xde\xf3rsh\xf6fn</option>\n<option value="685">685 Bakkafj\xf6r\xf0ur</option>\n<option value="690">690 Vopnafj\xf6r\xf0ur</option>\n<option value="700">700 Egilssta\xf0ir</option>\n<option value="701">701 Egilssta\xf0ir</option>\n<option value="710">710 Sey\xf0isfj\xf6r\xf0ur</option>\n<option value="715">715 Mj\xf3ifj\xf6r\xf0ur</option>\n<option value="720">720 Borgarfj\xf6r\xf0ur eystri</option>\n<option value="730">730 Rey\xf0arfj\xf6r\xf0ur</option>\n<option value="735">735 Eskifj\xf6r\xf0ur</option>\n<option value="740">740 Neskaupsta\xf0ur</option>\n<option value="750">750 F\xe1skr\xfa\xf0sfj\xf6r\xf0ur</option>\n<option value="755">755 St\xf6\xf0varfj\xf6r\xf0ur</option>\n<option value="760">760 Brei\xf0dalsv\xedk</option>\n<option value="765">765 Dj\xfapivogur</option>\n<option value="780">780 H\xf6fn \xed Hornafir\xf0i</option>\n<option value="781">781 H\xf6fn \xed Hornafir\xf0i</option>\n<option value="785">785 \xd6r\xe6fi</option>\n<option value="800">800 Selfoss</option>\n<option value="801">801 Selfoss</option>\n<option value="802">802 Selfoss</option>\n<option value="810">810 Hverager\xf0i</option>\n<option value="815">815 \xdeorl\xe1ksh\xf6fn</option>\n<option value="820">820 Eyrarbakki</option>\n<option value="825">825 Stokkseyri</option>\n<option value="840">840 Laugarvatn</option>\n<option value="845">845 Fl\xfa\xf0ir</option>\n<option value="850">850 Hella</option>\n<option value="851">851 Hella</option>\n<option value="860">860 Hvolsv\xf6llur</option>\n<option value="861">861 Hvolsv\xf6llur</option>\n<option value="870">870 V\xedk</option>\n<option value="871">871 V\xedk</option>\n<option value="880">880 Kirkjub\xe6jarklaustur</option>\n<option value="900">900 Vestmannaeyjar</option>\n<option value="902">902 Vestmannaeyjar</option>\n</select>' + +## CLRutField ############################################################# + +CLRutField is a Field that checks the validity of the Chilean +personal identification number (RUT). It has two modes relaxed (default) and +strict. + +>>> from django.contrib.localflavor.cl.forms import CLRutField +>>> rut = CLRutField() + +>>> rut.clean('11-6') +'11-6' +>>> rut.clean('116') +'11-6' + +# valid format, bad verifier. +>>> rut.clean('11.111.111-0') +Traceback (most recent call last): +... +ValidationError: [u'The Chilean RUT is not valid.'] +>>> rut.clean('111') +Traceback (most recent call last): +... +ValidationError: [u'The Chilean RUT is not valid.'] + +>>> rut.clean('767484100') +'76.748.410-0' +>>> rut.clean('78.412.790-7') +'78.412.790-7' +>>> rut.clean('8.334.6043') +'8.334.604-3' +>>> rut.clean('76793310-K') +'76.793.310-K' + +Strict RUT usage (does not allow imposible values) +>>> rut = CLRutField(strict=True) + +>>> rut.clean('11-6') +Traceback (most recent call last): +... +ValidationError: [u'Enter valid a Chilean RUT. The format is XX.XXX.XXX-X.'] + +# valid format, bad verifier. +>>> rut.clean('11.111.111-0') +Traceback (most recent call last): +... +ValidationError: [u'The Chilean RUT is not valid.'] + +# Correct input, invalid format. +>>> rut.clean('767484100') +Traceback (most recent call last): +... +ValidationError: [u'Enter valid a Chilean RUT. The format is XX.XXX.XXX-X.'] +>>> rut.clean('78.412.790-7') +'78.412.790-7' +>>> rut.clean('8.334.6043') +Traceback (most recent call last): +... +ValidationError: [u'Enter valid a Chilean RUT. The format is XX.XXX.XXX-X.'] +>>> rut.clean('76793310-K') +Traceback (most recent call last): +... +ValidationError: [u'Enter valid a Chilean RUT. The format is XX.XXX.XXX-X.'] + +""" diff --git a/tests/regressiontests/forms/regressions.py b/tests/regressiontests/forms/regressions.py new file mode 100644 index 0000000000..5fe057b5d8 --- /dev/null +++ b/tests/regressiontests/forms/regressions.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +# Tests to prevent against recurrences of earlier bugs. + +regression_tests = r""" +It should be possible to re-use attribute dictionaries (#3810) +>>> from django.newforms import * +>>> extra_attrs = {'class': 'special'} +>>> class TestForm(Form): +... f1 = CharField(max_length=10, widget=TextInput(attrs=extra_attrs)) +... f2 = CharField(widget=TextInput(attrs=extra_attrs)) +>>> TestForm(auto_id=False).as_p() +u'<p>F1: <input type="text" class="special" name="f1" maxlength="10" /></p>\n<p>F2: <input type="text" class="special" name="f2" /></p>' + +####################### +# Tests for form i18n # +####################### +There were some problems with form translations in #3600 + +>>> from django.utils.translation import gettext_lazy, activate, deactivate +>>> class SomeForm(Form): +... username = CharField(max_length=10, label=gettext_lazy('Username')) +>>> f = SomeForm() +>>> print f.as_p() +<p><label for="id_username">Username:</label> <input id="id_username" type="text" name="username" maxlength="10" /></p> +>>> activate('de') +>>> print f.as_p() +<p><label for="id_username">Benutzername:</label> <input id="id_username" type="text" name="username" maxlength="10" /></p> +>>> deactivate() + +Unicode decoding problems... +>>> GENDERS = (('0', u'En tied\xe4'), ('1', u'Mies'), ('2', u'Nainen')) +>>> class SomeForm(Form): +... somechoice = ChoiceField(choices=GENDERS, widget=RadioSelect()) +>>> f = SomeForm() +>>> f.as_p() +u'<p><label for="id_somechoice_0">Somechoice:</label> <ul>\n<li><label><input type="radio" id="id_somechoice_0" value="0" name="somechoice" /> En tied\xe4</label></li>\n<li><label><input type="radio" id="id_somechoice_1" value="1" name="somechoice" /> Mies</label></li>\n<li><label><input type="radio" id="id_somechoice_2" value="2" name="somechoice" /> Nainen</label></li>\n</ul></p>' + +####################### +# Miscellaneous Tests # +####################### + +There once was a problem with Form fields called "data". Let's make sure that +doesn't come back. +>>> class DataForm(Form): +... data = CharField(max_length=10) +>>> f = DataForm({'data': 'xyzzy'}) +>>> f.is_valid() +True +>>> f.cleaned_data +{'data': u'xyzzy'} +""" diff --git a/tests/regressiontests/forms/tests.py b/tests/regressiontests/forms/tests.py index 50bc7a7595..4844267b43 100644 --- a/tests/regressiontests/forms/tests.py +++ b/tests/regressiontests/forms/tests.py @@ -1,8 +1,16 @@ # -*- coding: utf-8 -*- -r""" +from localflavor import localflavor_tests +from regressions import regression_tests + +form_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 ########### # Widgets # @@ -72,6 +80,22 @@ u'<input type="password" class="special" name="email" />' >>> w.render('email', 'ŠĐĆŽćžšđ', attrs={'class': 'fun'}) u'<input type="password" class="fun" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" name="email" />' +The render_value argument lets you specify whether the widget should render +its value. You may want to do this for security reasons. +>>> w = PasswordInput(render_value=True) +>>> w.render('email', 'secret') +u'<input type="password" name="email" value="secret" />' +>>> w = PasswordInput(render_value=False) +>>> w.render('email', '') +u'<input type="password" name="email" />' +>>> w.render('email', None) +u'<input type="password" name="email" />' +>>> w.render('email', 'secret') +u'<input type="password" name="email" />' +>>> w = PasswordInput(attrs={'class': 'fun'}, render_value=False) +>>> w.render('email', 'secret') +u'<input type="password" class="fun" name="email" />' + # HiddenInput Widget ############################################################ >>> w = HiddenInput() @@ -106,6 +130,46 @@ u'<input type="hidden" class="fun" value="\u0160\u0110\u0106\u017d\u0107\u017e\u >>> w.render('email', '', attrs={'class': 'special'}) u'<input type="hidden" class="special" name="email" />' +# MultipleHiddenInput Widget ################################################## + +>>> w = MultipleHiddenInput() +>>> w.render('email', []) +u'' +>>> w.render('email', None) +u'' +>>> w.render('email', ['test@example.com']) +u'<input type="hidden" name="email" value="test@example.com" />' +>>> w.render('email', ['some "quoted" & ampersanded value']) +u'<input type="hidden" name="email" value="some "quoted" & ampersanded value" />' +>>> w.render('email', ['test@example.com', 'foo@example.com']) +u'<input type="hidden" name="email" value="test@example.com" />\n<input type="hidden" name="email" value="foo@example.com" />' +>>> w.render('email', ['test@example.com'], attrs={'class': 'fun'}) +u'<input type="hidden" name="email" value="test@example.com" class="fun" />' +>>> w.render('email', ['test@example.com', 'foo@example.com'], attrs={'class': 'fun'}) +u'<input type="hidden" name="email" value="test@example.com" class="fun" />\n<input type="hidden" name="email" value="foo@example.com" class="fun" />' + +You can also pass 'attrs' to the constructor: +>>> w = MultipleHiddenInput(attrs={'class': 'fun'}) +>>> w.render('email', []) +u'' +>>> w.render('email', ['foo@example.com']) +u'<input type="hidden" class="fun" value="foo@example.com" name="email" />' +>>> w.render('email', ['foo@example.com', 'test@example.com']) +u'<input type="hidden" class="fun" value="foo@example.com" name="email" />\n<input type="hidden" class="fun" value="test@example.com" name="email" />' + +'attrs' passed to render() get precedence over those passed to the constructor: +>>> w = MultipleHiddenInput(attrs={'class': 'pretty'}) +>>> w.render('email', ['foo@example.com'], attrs={'class': 'special'}) +u'<input type="hidden" class="special" value="foo@example.com" name="email" />' + +>>> w.render('email', ['ŠĐĆŽćžšđ'], attrs={'class': 'fun'}) +u'<input type="hidden" class="fun" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" name="email" />' + +'attrs' passed to render() get precedence over those passed to the constructor: +>>> w = MultipleHiddenInput(attrs={'class': 'pretty'}) +>>> w.render('email', ['foo@example.com'], attrs={'class': 'special'}) +u'<input type="hidden" class="special" value="foo@example.com" name="email" />' + # FileInput Widget ############################################################ >>> w = FileInput() @@ -134,30 +198,30 @@ u'<input type="file" class="fun" value="\u0160\u0110\u0106\u017d\u0107\u017e\u01 >>> w = Textarea() >>> w.render('msg', '') -u'<textarea name="msg"></textarea>' +u'<textarea rows="10" cols="40" name="msg"></textarea>' >>> w.render('msg', None) -u'<textarea name="msg"></textarea>' +u'<textarea rows="10" cols="40" name="msg"></textarea>' >>> w.render('msg', 'value') -u'<textarea name="msg">value</textarea>' +u'<textarea rows="10" cols="40" name="msg">value</textarea>' >>> w.render('msg', 'some "quoted" & ampersanded value') -u'<textarea name="msg">some "quoted" & ampersanded value</textarea>' ->>> w.render('msg', 'value', attrs={'class': 'pretty'}) -u'<textarea name="msg" class="pretty">value</textarea>' +u'<textarea rows="10" cols="40" name="msg">some "quoted" & ampersanded value</textarea>' +>>> w.render('msg', 'value', attrs={'class': 'pretty', 'rows': 20}) +u'<textarea class="pretty" rows="20" cols="40" name="msg">value</textarea>' You can also pass 'attrs' to the constructor: >>> w = Textarea(attrs={'class': 'pretty'}) >>> w.render('msg', '') -u'<textarea class="pretty" name="msg"></textarea>' +u'<textarea rows="10" cols="40" name="msg" class="pretty"></textarea>' >>> w.render('msg', 'example') -u'<textarea class="pretty" name="msg">example</textarea>' +u'<textarea rows="10" cols="40" name="msg" class="pretty">example</textarea>' 'attrs' passed to render() get precedence over those passed to the constructor: >>> w = Textarea(attrs={'class': 'pretty'}) >>> w.render('msg', '', attrs={'class': 'special'}) -u'<textarea class="special" name="msg"></textarea>' +u'<textarea rows="10" cols="40" name="msg" class="special"></textarea>' >>> w.render('msg', 'ŠĐĆŽćžšđ', attrs={'class': 'fun'}) -u'<textarea class="fun" name="msg">\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111</textarea>' +u'<textarea rows="10" cols="40" name="msg" class="fun">\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111</textarea>' # CheckboxInput Widget ######################################################## @@ -262,6 +326,7 @@ The value is compared to its str(): </select> The 'choices' argument can be any iterable: +>>> from itertools import chain >>> def get_choices(): ... for i in range(5): ... yield (i, i) @@ -273,6 +338,17 @@ The 'choices' argument can be any iterable: <option value="3">3</option> <option value="4">4</option> </select> +>>> things = ({'id': 1, 'name': 'And Boom'}, {'id': 2, 'name': 'One More Thing!'}) +>>> class SomeForm(Form): +... somechoice = ChoiceField(choices=chain((('', '-'*9),), [(thing['id'], thing['name']) for thing in things])) +>>> f = SomeForm() +>>> f.as_table() +u'<tr><th><label for="id_somechoice">Somechoice:</label></th><td><select name="somechoice" id="id_somechoice">\n<option value="" selected="selected">---------</option>\n<option value="1">And Boom</option>\n<option value="2">One More Thing!</option>\n</select></td></tr>' +>>> f.as_table() +u'<tr><th><label for="id_somechoice">Somechoice:</label></th><td><select name="somechoice" id="id_somechoice">\n<option value="" selected="selected">---------</option>\n<option value="1">And Boom</option>\n<option value="2">One More Thing!</option>\n</select></td></tr>' +>>> f = SomeForm({'somechoice': 2}) +>>> f.as_table() +u'<tr><th><label for="id_somechoice">Somechoice:</label></th><td><select name="somechoice" id="id_somechoice">\n<option value="">---------</option>\n<option value="1">And Boom</option>\n<option value="2" selected="selected">One More Thing!</option>\n</select></td></tr>' You can also pass 'choices' to the constructor: >>> w = Select(choices=[(1, 1), (2, 2), (3, 3)]) @@ -296,6 +372,60 @@ If 'choices' is passed to both the constructor and render(), then they'll both b >>> w.render('email', 'ŠĐĆŽćžšđ', choices=[('ŠĐĆŽćžšđ', 'ŠĐabcĆŽćžšđ'), ('ćžšđ', 'abcćžšđ')]) u'<select name="email">\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" selected="selected">\u0160\u0110abc\u0106\u017d\u0107\u017e\u0161\u0111</option>\n<option value="\u0107\u017e\u0161\u0111">abc\u0107\u017e\u0161\u0111</option>\n</select>' +If choices is passed to the constructor and is a generator, it can be iterated +over multiple times without getting consumed: +>>> w = Select(choices=get_choices()) +>>> print w.render('num', 2) +<select name="num"> +<option value="0">0</option> +<option value="1">1</option> +<option value="2" selected="selected">2</option> +<option value="3">3</option> +<option value="4">4</option> +</select> +>>> print w.render('num', 3) +<select name="num"> +<option value="0">0</option> +<option value="1">1</option> +<option value="2">2</option> +<option value="3" selected="selected">3</option> +<option value="4">4</option> +</select> + +# NullBooleanSelect Widget #################################################### + +>>> w = NullBooleanSelect() +>>> print w.render('is_cool', True) +<select name="is_cool"> +<option value="1">Unknown</option> +<option value="2" selected="selected">Yes</option> +<option value="3">No</option> +</select> +>>> print w.render('is_cool', False) +<select name="is_cool"> +<option value="1">Unknown</option> +<option value="2">Yes</option> +<option value="3" selected="selected">No</option> +</select> +>>> print w.render('is_cool', None) +<select name="is_cool"> +<option value="1" selected="selected">Unknown</option> +<option value="2">Yes</option> +<option value="3">No</option> +</select> +>>> print w.render('is_cool', '2') +<select name="is_cool"> +<option value="1">Unknown</option> +<option value="2" selected="selected">Yes</option> +<option value="3">No</option> +</select> +>>> print w.render('is_cool', '3') +<select name="is_cool"> +<option value="1">Unknown</option> +<option value="2">Yes</option> +<option value="3" selected="selected">No</option> +</select> + # SelectMultiple Widget ####################################################### >>> w = SelectMultiple() @@ -527,12 +657,37 @@ True >>> r[1].is_checked() False >>> r[1].name, r[1].value, r[1].choice_value, r[1].choice_label -('beatle', u'J', 'P', 'Paul') +('beatle', u'J', u'P', u'Paul') >>> r[10] Traceback (most recent call last): ... IndexError: list index out of range +# Unicode choices are correctly rendered as HTML +>>> w = RadioSelect() +>>> unicode(w.render('email', 'ŠĐĆŽćžšđ', choices=[('ŠĐĆŽćžšđ', 'ŠĐabcĆŽćžšđ'), ('ćžšđ', 'abcćžšđ')])) +u'<ul>\n<li><label><input checked="checked" type="radio" name="email" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" /> \u0160\u0110abc\u0106\u017d\u0107\u017e\u0161\u0111</label></li>\n<li><label><input type="radio" name="email" value="\u0107\u017e\u0161\u0111" /> abc\u0107\u017e\u0161\u0111</label></li>\n</ul>' + +# Attributes provided at instantiation are passed to the constituent inputs +>>> w = RadioSelect(attrs={'id':'foo'}) +>>> print w.render('beatle', 'J', choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))) +<ul> +<li><label><input checked="checked" type="radio" id="foo_0" value="J" name="beatle" /> John</label></li> +<li><label><input type="radio" id="foo_1" value="P" name="beatle" /> Paul</label></li> +<li><label><input type="radio" id="foo_2" value="G" name="beatle" /> George</label></li> +<li><label><input type="radio" id="foo_3" value="R" name="beatle" /> Ringo</label></li> +</ul> + +# Attributes provided at render-time are passed to the constituent inputs +>>> w = RadioSelect() +>>> print w.render('beatle', 'J', choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo')), attrs={'id':'bar'}) +<ul> +<li><label><input checked="checked" type="radio" id="bar_0" value="J" name="beatle" /> John</label></li> +<li><label><input type="radio" id="bar_1" value="P" name="beatle" /> Paul</label></li> +<li><label><input type="radio" id="bar_2" value="G" name="beatle" /> George</label></li> +<li><label><input type="radio" id="bar_3" value="R" name="beatle" /> Ringo</label></li> +</ul> + # CheckboxSelectMultiple Widget ############################################### >>> w = CheckboxSelectMultiple() @@ -640,6 +795,44 @@ If 'choices' is passed to both the constructor and render(), then they'll both b >>> w.render('nums', ['ŠĐĆŽćžšđ'], choices=[('ŠĐĆŽćžšđ', 'ŠĐabcĆŽćžšđ'), ('ćžšđ', 'abcćžšđ')]) u'<ul>\n<li><label><input type="checkbox" name="nums" value="1" /> 1</label></li>\n<li><label><input type="checkbox" name="nums" value="2" /> 2</label></li>\n<li><label><input type="checkbox" name="nums" value="3" /> 3</label></li>\n<li><label><input checked="checked" type="checkbox" name="nums" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" /> \u0160\u0110abc\u0106\u017d\u0107\u017e\u0161\u0111</label></li>\n<li><label><input type="checkbox" name="nums" value="\u0107\u017e\u0161\u0111" /> abc\u0107\u017e\u0161\u0111</label></li>\n</ul>' +# MultiWidget ################################################################# + +>>> class MyMultiWidget(MultiWidget): +... def decompress(self, value): +... if value: +... return value.split('__') +... return ['', ''] +... def format_output(self, rendered_widgets): +... return u'<br />'.join(rendered_widgets) +>>> w = MyMultiWidget(widgets=(TextInput(attrs={'class': 'big'}), TextInput(attrs={'class': 'small'}))) +>>> w.render('name', ['john', 'lennon']) +u'<input type="text" class="big" value="john" name="name_0" /><br /><input type="text" class="small" value="lennon" name="name_1" />' +>>> w.render('name', 'john__lennon') +u'<input type="text" class="big" value="john" name="name_0" /><br /><input type="text" class="small" value="lennon" name="name_1" />' +>>> w.render('name', 'john__lennon', attrs={'id':'foo'}) +u'<input id="foo_0" type="text" class="big" value="john" name="name_0" /><br /><input id="foo_1" type="text" class="small" value="lennon" name="name_1" />' +>>> w = MyMultiWidget(widgets=(TextInput(attrs={'class': 'big'}), TextInput(attrs={'class': 'small'})), attrs={'id': 'bar'}) +>>> w.render('name', ['john', 'lennon']) +u'<input id="bar_0" type="text" class="big" value="john" name="name_0" /><br /><input id="bar_1" type="text" class="small" value="lennon" name="name_1" />' + +# SplitDateTimeWidget ######################################################### + +>>> w = SplitDateTimeWidget() +>>> w.render('date', '') +u'<input type="text" name="date_0" /><input type="text" name="date_1" />' +>>> w.render('date', None) +u'<input type="text" name="date_0" /><input type="text" name="date_1" />' +>>> w.render('date', datetime.datetime(2006, 1, 10, 7, 30)) +u'<input type="text" name="date_0" value="2006-01-10" /><input type="text" name="date_1" value="07:30:00" />' +>>> w.render('date', [datetime.date(2006, 1, 10), datetime.time(7, 30)]) +u'<input type="text" name="date_0" value="2006-01-10" /><input type="text" name="date_1" value="07:30:00" />' + +You can also pass 'attrs' to the constructor. In this case, the attrs will be +included on both widgets. +>>> w = SplitDateTimeWidget(attrs={'class': 'pretty'}) +>>> w.render('date', datetime.datetime(2006, 1, 10, 7, 30)) +u'<input type="text" class="pretty" value="2006-01-10" name="date_0" /><input type="text" class="pretty" value="07:30:00" name="date_1" />' + ########## # Fields # ########## @@ -658,6 +851,8 @@ Each Field's __init__() takes at least these parameters: 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. @@ -764,9 +959,11 @@ ValidationError: [u'Enter a whole number.'] >>> f = IntegerField(required=False) >>> f.clean('') -u'' +>>> repr(f.clean('')) +'None' >>> f.clean(None) -u'' +>>> repr(f.clean(None)) +'None' >>> f.clean('1') 1 >>> isinstance(f.clean('1'), int) @@ -853,6 +1050,133 @@ 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('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('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 = 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") + # DateField ################################################################### >>> import datetime @@ -1283,8 +1607,13 @@ ValidationError: [u'This URL appears to be a broken link.'] 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' -EmailField also access min_length and max_length parameters, for convenience. +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): @@ -1353,7 +1682,7 @@ u'1' >>> f.clean('3') Traceback (most recent call last): ... -ValidationError: [u'Select a valid choice. 3 is not one of the available choices.'] +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('') @@ -1367,7 +1696,7 @@ u'1' >>> f.clean('3') Traceback (most recent call last): ... -ValidationError: [u'Select a valid choice. 3 is not one of the available choices.'] +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') @@ -1375,7 +1704,21 @@ u'J' >>> f.clean('John') Traceback (most recent call last): ... -ValidationError: [u'Select a valid choice. John is not one of the available choices.'] +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 ######################################################### @@ -1483,6 +1826,58 @@ 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(None) +>>> 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.'] + ######### # Forms # ######### @@ -1500,6 +1895,8 @@ You can pass it data in __init__(), as a dictionary. 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() @@ -1508,7 +1905,7 @@ True u'' >>> p.errors.as_text() u'' ->>> p.clean_data +>>> p.cleaned_data {'first_name': u'John', 'last_name': u'Lennon', 'birthday': datetime.date(1940, 10, 9)} >>> print p['first_name'] <input type="text" name="first_name" value="John" id="id_first_name" /> @@ -1538,10 +1935,16 @@ Birthday 1940-10-9 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 <tr><th><label for="id_first_name">First name:</label></th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="first_name" id="id_first_name" /></td></tr> <tr><th><label for="id_last_name">Last name:</label></th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="last_name" id="id_last_name" /></td></tr> @@ -1563,13 +1966,19 @@ False <p><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" /></p> 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. +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 <tr><th><label for="id_first_name">First name:</label></th><td><input type="text" name="first_name" id="id_first_name" /></td></tr> <tr><th><label for="id_last_name">Last name:</label></th><td><input type="text" name="last_name" id="id_last_name" /></td></tr> @@ -1608,9 +2017,10 @@ u'<ul class="errorlist"><li>first_name<ul class="errorlist"><li>This field is re * This field is required. * birthday * This field is required. ->>> p.clean_data ->>> repr(p.clean_data) -'None' +>>> 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() @@ -1626,6 +2036,45 @@ u'* This field is required.' >>> print p['birthday'] <input type="text" name="birthday" id="id_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 <label> around @@ -1703,12 +2152,12 @@ Any Field can have a Widget class passed to its constructor: >>> print f['subject'] <input type="text" name="subject" /> >>> print f['message'] -<textarea name="message"></textarea> +<textarea rows="10" cols="40" name="message"></textarea> as_textarea(), as_text() and as_hidden() are shortcuts for changing the output widget type: >>> f['subject'].as_textarea() -u'<textarea name="subject"></textarea>' +u'<textarea rows="10" cols="40" name="subject"></textarea>' >>> f['message'].as_text() u'<input type="text" name="message" />' >>> f['message'].as_hidden() @@ -1728,7 +2177,7 @@ as_hidden(): u'<input type="text" name="message" />' >>> f = ContactForm({'subject': 'Hello', 'message': 'I love you.'}, auto_id=False) >>> f['subject'].as_textarea() -u'<textarea name="subject">Hello</textarea>' +u'<textarea rows="10" cols="40" name="subject">Hello</textarea>' >>> f['message'].as_text() u'<input type="text" name="message" value="I love you." />' >>> f['message'].as_hidden() @@ -1751,6 +2200,70 @@ For a form with a <select>, use ChoiceField: <option value="J">Java</option> </select> +A subtlety: If one of the choices' value is the empty string and the form is +unbound, then the <option> for the empty-string choice will get selected="selected". +>>> class FrameworkForm(Form): +... name = CharField() +... language = ChoiceField(choices=[('', '------'), ('P', 'Python'), ('J', 'Java')]) +>>> f = FrameworkForm(auto_id=False) +>>> print f['language'] +<select name="language"> +<option value="" selected="selected">------</option> +<option value="P">Python</option> +<option value="J">Java</option> +</select> + +You can specify widget attributes in the Widget constructor. +>>> class FrameworkForm(Form): +... name = CharField() +... language = ChoiceField(choices=[('P', 'Python'), ('J', 'Java')], widget=Select(attrs={'class': 'foo'})) +>>> f = FrameworkForm(auto_id=False) +>>> print f['language'] +<select class="foo" name="language"> +<option value="P">Python</option> +<option value="J">Java</option> +</select> +>>> f = FrameworkForm({'name': 'Django', 'language': 'P'}, auto_id=False) +>>> print f['language'] +<select class="foo" name="language"> +<option value="P" selected="selected">Python</option> +<option value="J">Java</option> +</select> + +When passing a custom widget instance to ChoiceField, note that setting +'choices' on the widget is meaningless. The widget will use the choices +defined on the Field, not the ones defined on the Widget. +>>> class FrameworkForm(Form): +... name = CharField() +... language = ChoiceField(choices=[('P', 'Python'), ('J', 'Java')], widget=Select(choices=[('R', 'Ruby'), ('P', 'Perl')], attrs={'class': 'foo'})) +>>> f = FrameworkForm(auto_id=False) +>>> print f['language'] +<select class="foo" name="language"> +<option value="P">Python</option> +<option value="J">Java</option> +</select> +>>> f = FrameworkForm({'name': 'Django', 'language': 'P'}, auto_id=False) +>>> print f['language'] +<select class="foo" name="language"> +<option value="P" selected="selected">Python</option> +<option value="J">Java</option> +</select> + +You can set a ChoiceField's choices after the fact. +>>> class FrameworkForm(Form): +... name = CharField() +... language = ChoiceField() +>>> f = FrameworkForm(auto_id=False) +>>> print f['language'] +<select name="language"> +</select> +>>> f.fields['language'].choices = [('P', 'Python'), ('J', 'Java')] +>>> print f['language'] +<select name="language"> +<option value="P">Python</option> +<option value="J">Java</option> +</select> + Add widget=RadioSelect to use that widget with a ChoiceField. >>> class FrameworkForm(Form): ... name = CharField() @@ -1832,6 +2345,17 @@ MultipleChoiceField is a special case, as its data is required to be a list: <option value="P" selected="selected">Paul McCartney</option> </select> +MultipleChoiceField rendered as_hidden() is a special case. Because it can +have multiple values, its as_hidden() renders multiple <input type="hidden"> +tags. +>>> f = SongForm({'name': 'Yesterday', 'composers': ['P']}, auto_id=False) +>>> print f['composers'].as_hidden() +<input type="hidden" name="composers" value="P" /> +>>> f = SongForm({'name': 'From Me To You', 'composers': ['P', 'J']}, auto_id=False) +>>> print f['composers'].as_hidden() +<input type="hidden" name="composers" value="P" /> +<input type="hidden" name="composers" value="J" /> + MultipleChoiceField can also be used with the CheckboxSelectMultiple widget. >>> class SongForm(Form): ... name = CharField() @@ -1855,6 +2379,16 @@ MultipleChoiceField can also be used with the CheckboxSelectMultiple widget. <li><label><input checked="checked" type="checkbox" name="composers" value="P" /> Paul McCartney</label></li> </ul> +Regarding auto_id, CheckboxSelectMultiple is a special case. Each checkbox +gets a distinct ID, formed by appending an underscore plus the checkbox's +zero-based index. +>>> f = SongForm(auto_id='%s_id') +>>> print f['composers'] +<ul> +<li><label><input type="checkbox" name="composers" value="J" id="composers_id_0" /> John Lennon</label></li> +<li><label><input type="checkbox" name="composers" value="P" id="composers_id_1" /> Paul McCartney</label></li> +</ul> + Data for a MultipleChoiceField should be a list. QueryDict and MultiValueDict conveniently work with this. >>> data = {'name': 'Yesterday', 'composers': ['J', 'P']} @@ -1867,11 +2401,20 @@ conveniently work with this. >>> f.errors {} >>> from django.utils.datastructures import MultiValueDict ->>> data = MultiValueDict(dict(name='Yesterday', composers=['J', 'P'])) +>>> data = MultiValueDict(dict(name=['Yesterday'], composers=['J', 'P'])) >>> f = SongForm(data) >>> f.errors {} +The MultipleHiddenInput widget renders multiple values as hidden fields. +>>> class SongFormHidden(Form): +... name = CharField() +... composers = MultipleChoiceField(choices=[('J', 'John Lennon'), ('P', 'Paul McCartney')], widget=MultipleHiddenInput) +>>> f = SongFormHidden(MultiValueDict(dict(name=['Yesterday'], composers=['J', 'P'])), auto_id=False) +>>> print f.as_ul() +<li>Name: <input type="text" name="name" value="Yesterday" /><input type="hidden" name="composers" value="J" /> +<input type="hidden" name="composers" value="P" /></li> + When using CheckboxSelectMultiple, the framework expects a list of input and returns a list of input. >>> f = SongForm({'name': 'Yesterday'}, auto_id=False) @@ -1880,19 +2423,34 @@ returns a list of input. >>> f = SongForm({'name': 'Yesterday', 'composers': ['J']}, auto_id=False) >>> f.errors {} ->>> f.clean_data +>>> f.cleaned_data {'composers': [u'J'], 'name': u'Yesterday'} >>> f = SongForm({'name': 'Yesterday', 'composers': ['J', 'P']}, auto_id=False) >>> f.errors {} ->>> f.clean_data +>>> f.cleaned_data {'composers': [u'J', u'P'], 'name': u'Yesterday'} +Validation errors are HTML-escaped when output as HTML. +>>> class EscapingForm(Form): +... special_name = CharField() +... def clean_special_name(self): +... raise ValidationError("Something's wrong with '%s'" % self.cleaned_data['special_name']) + +>>> f = EscapingForm({'special_name': "Nothing to escape"}, auto_id=False) +>>> print f +<tr><th>Special name:</th><td><ul class="errorlist"><li>Something's wrong with 'Nothing to escape'</li></ul><input type="text" name="special_name" value="Nothing to escape" /></td></tr> +>>> f = EscapingForm({'special_name': "Should escape < & > and <script>alert('xss')</script>"}, auto_id=False) +>>> print f +<tr><th>Special name:</th><td><ul class="errorlist"><li>Something's wrong with 'Should escape < & > and <script>alert('xss')</script>'</li></ul><input type="text" name="special_name" value="Should escape < & > and <script>alert('xss')</script>" /></td></tr> + +# Validating multiple fields in relation to another ########################### + 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. In the -clean_XXX() method, you have access to self.clean_data, which is a dictionary +clean_XXX() method, you have access to self.cleaned_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): @@ -1900,9 +2458,9 @@ including the current field (e.g., the field XXX if you're in clean_XXX()). ... password1 = CharField(widget=PasswordInput) ... password2 = CharField(widget=PasswordInput) ... def clean_password2(self): -... if self.clean_data.get('password1') and self.clean_data.get('password2') and self.clean_data['password1'] != self.clean_data['password2']: +... if self.cleaned_data.get('password1') and self.cleaned_data.get('password2') and self.cleaned_data['password1'] != self.cleaned_data['password2']: ... raise ValidationError(u'Please make sure your passwords match.') -... return self.clean_data['password2'] +... return self.cleaned_data['password2'] >>> f = UserRegistration(auto_id=False) >>> f.errors {} @@ -1915,14 +2473,14 @@ including the current field (e.g., the field XXX if you're in clean_XXX()). >>> f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'foo'}, auto_id=False) >>> f.errors {} ->>> f.clean_data +>>> f.cleaned_data {'username': u'adrian', 'password1': u'foo', 'password2': u'foo'} 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 in Form.clean(), you have access to self.clean_data, a dictionary of +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. >>> class UserRegistration(Form): @@ -1930,9 +2488,9 @@ Form.clean() is required to return a dictionary of all clean data. ... 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']: +... if self.cleaned_data.get('password1') and self.cleaned_data.get('password2') and self.cleaned_data['password1'] != self.cleaned_data['password2']: ... raise ValidationError(u'Please make sure your passwords match.') -... return self.clean_data +... return self.cleaned_data >>> f = UserRegistration(auto_id=False) >>> f.errors {} @@ -1959,9 +2517,11 @@ Form.clean() is required to return a dictionary of all clean data. >>> f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'foo'}, auto_id=False) >>> f.errors {} ->>> f.clean_data +>>> f.cleaned_data {'username': u'adrian', 'password1': u'foo', 'password2': u'foo'} +# Dynamic construction ######################################################## + It's possible to construct a Form dynamically by adding to the self.fields dictionary in __init__(). Don't forget to call Form.__init__() within the subclass' __init__(). @@ -1977,6 +2537,83 @@ subclass' __init__(). <tr><th>Last name:</th><td><input type="text" name="last_name" /></td></tr> <tr><th>Birthday:</th><td><input type="text" name="birthday" /></td></tr> +Instances of a dynamic Form do not persist fields from one Form instance to +the next. +>>> class MyForm(Form): +... def __init__(self, data=None, auto_id=False, field_list=[]): +... Form.__init__(self, data, auto_id) +... for field in field_list: +... self.fields[field[0]] = field[1] +>>> field_list = [('field1', CharField()), ('field2', CharField())] +>>> my_form = MyForm(field_list=field_list) +>>> print my_form +<tr><th>Field1:</th><td><input type="text" name="field1" /></td></tr> +<tr><th>Field2:</th><td><input type="text" name="field2" /></td></tr> +>>> field_list = [('field3', CharField()), ('field4', CharField())] +>>> my_form = MyForm(field_list=field_list) +>>> print my_form +<tr><th>Field3:</th><td><input type="text" name="field3" /></td></tr> +<tr><th>Field4:</th><td><input type="text" name="field4" /></td></tr> + +>>> class MyForm(Form): +... default_field_1 = CharField() +... default_field_2 = CharField() +... def __init__(self, data=None, auto_id=False, field_list=[]): +... Form.__init__(self, data, auto_id) +... for field in field_list: +... self.fields[field[0]] = field[1] +>>> field_list = [('field1', CharField()), ('field2', CharField())] +>>> my_form = MyForm(field_list=field_list) +>>> print my_form +<tr><th>Default field 1:</th><td><input type="text" name="default_field_1" /></td></tr> +<tr><th>Default field 2:</th><td><input type="text" name="default_field_2" /></td></tr> +<tr><th>Field1:</th><td><input type="text" name="field1" /></td></tr> +<tr><th>Field2:</th><td><input type="text" name="field2" /></td></tr> +>>> field_list = [('field3', CharField()), ('field4', CharField())] +>>> my_form = MyForm(field_list=field_list) +>>> print my_form +<tr><th>Default field 1:</th><td><input type="text" name="default_field_1" /></td></tr> +<tr><th>Default field 2:</th><td><input type="text" name="default_field_2" /></td></tr> +<tr><th>Field3:</th><td><input type="text" name="field3" /></td></tr> +<tr><th>Field4:</th><td><input type="text" name="field4" /></td></tr> + +Similarly, changes to field attributes do not persist from one Form instance +to the next. +>>> class Person(Form): +... first_name = CharField(required=False) +... last_name = CharField(required=False) +... def __init__(self, names_required=False, *args, **kwargs): +... super(Person, self).__init__(*args, **kwargs) +... if names_required: +... self.fields['first_name'].required = True +... self.fields['last_name'].required = True +>>> f = Person(names_required=False) +>>> f['first_name'].field.required, f['last_name'].field.required +(False, False) +>>> f = Person(names_required=True) +>>> f['first_name'].field.required, f['last_name'].field.required +(True, True) +>>> f = Person(names_required=False) +>>> f['first_name'].field.required, f['last_name'].field.required +(False, False) +>>> class Person(Form): +... first_name = CharField(max_length=30) +... last_name = CharField(max_length=30) +... def __init__(self, name_max_length=None, *args, **kwargs): +... super(Person, self).__init__(*args, **kwargs) +... if name_max_length: +... self.fields['first_name'].max_length = name_max_length +... self.fields['last_name'].max_length = name_max_length +>>> f = Person(name_max_length=None) +>>> f['first_name'].field.max_length, f['last_name'].field.max_length +(30, 30) +>>> f = Person(name_max_length=20) +>>> f['first_name'].field.max_length, f['last_name'].field.max_length +(20, 20) +>>> f = Person(name_max_length=None) +>>> f['first_name'].field.max_length, f['last_name'].field.max_length +(30, 30) + HiddenInput widgets are displayed differently in the as_table(), as_ul() and as_p() output of a Form -- their verbose names are not displayed, and a separate row is not displayed. They're displayed in the last row of the @@ -2108,6 +2745,8 @@ in "attrs". <li>Username: <input type="text" name="username" maxlength="10" /></li> <li>Password: <input type="password" name="password" maxlength="10" /></li> +# Specifying labels ########################################################### + You can specify the label for a field by using the 'label' argument to a Field class. If you don't specify 'label', Django will use the field name with underscores converted to spaces, and the initial letter capitalized. @@ -2121,6 +2760,27 @@ underscores converted to spaces, and the initial letter capitalized. <li>Password1: <input type="password" name="password1" /></li> <li>Password (again): <input type="password" name="password2" /></li> +Labels for as_* methods will only end in a colon if they don't end in other +punctuation already. +>>> class Questions(Form): +... q1 = CharField(label='The first question') +... q2 = CharField(label='What is your name?') +... q3 = CharField(label='The answer to life is:') +... q4 = CharField(label='Answer this question!') +... q5 = CharField(label='The last question. Period.') +>>> print Questions(auto_id=False).as_p() +<p>The first question: <input type="text" name="q1" /></p> +<p>What is your name? <input type="text" name="q2" /></p> +<p>The answer to life is: <input type="text" name="q3" /></p> +<p>Answer this question! <input type="text" name="q4" /></p> +<p>The last question. Period. <input type="text" name="q5" /></p> +>>> print Questions().as_p() +<p><label for="id_q1">The first question:</label> <input type="text" name="q1" id="id_q1" /></p> +<p><label for="id_q2">What is your name?</label> <input type="text" name="q2" id="id_q2" /></p> +<p><label for="id_q3">The answer to life is:</label> <input type="text" name="q3" id="id_q3" /></p> +<p><label for="id_q4">Answer this question!</label> <input type="text" name="q4" id="id_q4" /></p> +<p><label for="id_q5">The last question. Period.</label> <input type="text" name="q5" id="id_q5" /></p> + A label can be a Unicode object or a bytestring with special characters. >>> class UserRegistration(Form): ... username = CharField(max_length=10, label='ŠĐĆŽćžšđ') @@ -2156,6 +2816,242 @@ is default behavior. <li><label for="id_username">Username:</label> <input id="id_username" type="text" name="username" maxlength="10" /></li> <li><label for="id_password">Password:</label> <input type="password" name="password" id="id_password" /></li> +# Initial data ################################################################ + +You can specify initial data for a field by using the 'initial' argument to a +Field class. This initial data is displayed when a Form is rendered with *no* +data. It is not displayed when a Form is rendered with any data (including an +empty dictionary). Also, the initial value is *not* used if data for a +particular required field isn't provided. +>>> class UserRegistration(Form): +... username = CharField(max_length=10, initial='django') +... password = CharField(widget=PasswordInput) + +Here, we're not submitting any data, so the initial value will be displayed. +>>> p = UserRegistration(auto_id=False) +>>> print p.as_ul() +<li>Username: <input type="text" name="username" value="django" maxlength="10" /></li> +<li>Password: <input type="password" name="password" /></li> + +Here, we're submitting data, so the initial value will *not* be displayed. +>>> p = UserRegistration({}, auto_id=False) +>>> print p.as_ul() +<li><ul class="errorlist"><li>This field is required.</li></ul>Username: <input type="text" name="username" maxlength="10" /></li> +<li><ul class="errorlist"><li>This field is required.</li></ul>Password: <input type="password" name="password" /></li> +>>> p = UserRegistration({'username': u''}, auto_id=False) +>>> print p.as_ul() +<li><ul class="errorlist"><li>This field is required.</li></ul>Username: <input type="text" name="username" maxlength="10" /></li> +<li><ul class="errorlist"><li>This field is required.</li></ul>Password: <input type="password" name="password" /></li> +>>> p = UserRegistration({'username': u'foo'}, auto_id=False) +>>> print p.as_ul() +<li>Username: <input type="text" name="username" value="foo" maxlength="10" /></li> +<li><ul class="errorlist"><li>This field is required.</li></ul>Password: <input type="password" name="password" /></li> + +An 'initial' value is *not* used as a fallback if data is not provided. In this +example, we don't provide a value for 'username', and the form raises a +validation error rather than using the initial value for 'username'. +>>> p = UserRegistration({'password': 'secret'}) +>>> p.errors +{'username': [u'This field is required.']} +>>> p.is_valid() +False + +# Dynamic initial data ######################################################## + +The previous technique dealt with "hard-coded" initial data, but it's also +possible to specify initial data after you've already created the Form class +(i.e., at runtime). Use the 'initial' parameter to the Form constructor. This +should be a dictionary containing initial values for one or more fields in the +form, keyed by field name. + +>>> class UserRegistration(Form): +... username = CharField(max_length=10) +... password = CharField(widget=PasswordInput) + +Here, we're not submitting any data, so the initial value will be displayed. +>>> p = UserRegistration(initial={'username': 'django'}, auto_id=False) +>>> print p.as_ul() +<li>Username: <input type="text" name="username" value="django" maxlength="10" /></li> +<li>Password: <input type="password" name="password" /></li> +>>> p = UserRegistration(initial={'username': 'stephane'}, auto_id=False) +>>> print p.as_ul() +<li>Username: <input type="text" name="username" value="stephane" maxlength="10" /></li> +<li>Password: <input type="password" name="password" /></li> + +The 'initial' parameter is meaningless if you pass data. +>>> p = UserRegistration({}, initial={'username': 'django'}, auto_id=False) +>>> print p.as_ul() +<li><ul class="errorlist"><li>This field is required.</li></ul>Username: <input type="text" name="username" maxlength="10" /></li> +<li><ul class="errorlist"><li>This field is required.</li></ul>Password: <input type="password" name="password" /></li> +>>> p = UserRegistration({'username': u''}, initial={'username': 'django'}, auto_id=False) +>>> print p.as_ul() +<li><ul class="errorlist"><li>This field is required.</li></ul>Username: <input type="text" name="username" maxlength="10" /></li> +<li><ul class="errorlist"><li>This field is required.</li></ul>Password: <input type="password" name="password" /></li> +>>> p = UserRegistration({'username': u'foo'}, initial={'username': 'django'}, auto_id=False) +>>> print p.as_ul() +<li>Username: <input type="text" name="username" value="foo" maxlength="10" /></li> +<li><ul class="errorlist"><li>This field is required.</li></ul>Password: <input type="password" name="password" /></li> + +A dynamic 'initial' value is *not* used as a fallback if data is not provided. +In this example, we don't provide a value for 'username', and the form raises a +validation error rather than using the initial value for 'username'. +>>> p = UserRegistration({'password': 'secret'}, initial={'username': 'django'}) +>>> p.errors +{'username': [u'This field is required.']} +>>> p.is_valid() +False + +If a Form defines 'initial' *and* 'initial' is passed as a parameter to Form(), +then the latter will get precedence. +>>> class UserRegistration(Form): +... username = CharField(max_length=10, initial='django') +... password = CharField(widget=PasswordInput) +>>> p = UserRegistration(initial={'username': 'babik'}, auto_id=False) +>>> print p.as_ul() +<li>Username: <input type="text" name="username" value="babik" maxlength="10" /></li> +<li>Password: <input type="password" name="password" /></li> + +# Callable initial data ######################################################## + +The previous technique dealt with raw values as initial data, but it's also +possible to specify callable data. + +>>> class UserRegistration(Form): +... username = CharField(max_length=10) +... password = CharField(widget=PasswordInput) + +We need to define functions that get called later. +>>> def initial_django(): +... return 'django' +>>> def initial_stephane(): +... return 'stephane' + +Here, we're not submitting any data, so the initial value will be displayed. +>>> p = UserRegistration(initial={'username': initial_django}, auto_id=False) +>>> print p.as_ul() +<li>Username: <input type="text" name="username" value="django" maxlength="10" /></li> +<li>Password: <input type="password" name="password" /></li> + +The 'initial' parameter is meaningless if you pass data. +>>> p = UserRegistration({}, initial={'username': initial_django}, auto_id=False) +>>> print p.as_ul() +<li><ul class="errorlist"><li>This field is required.</li></ul>Username: <input type="text" name="username" maxlength="10" /></li> +<li><ul class="errorlist"><li>This field is required.</li></ul>Password: <input type="password" name="password" /></li> +>>> p = UserRegistration({'username': u''}, initial={'username': initial_django}, auto_id=False) +>>> print p.as_ul() +<li><ul class="errorlist"><li>This field is required.</li></ul>Username: <input type="text" name="username" maxlength="10" /></li> +<li><ul class="errorlist"><li>This field is required.</li></ul>Password: <input type="password" name="password" /></li> +>>> p = UserRegistration({'username': u'foo'}, initial={'username': initial_django}, auto_id=False) +>>> print p.as_ul() +<li>Username: <input type="text" name="username" value="foo" maxlength="10" /></li> +<li><ul class="errorlist"><li>This field is required.</li></ul>Password: <input type="password" name="password" /></li> + +A callable 'initial' value is *not* used as a fallback if data is not provided. +In this example, we don't provide a value for 'username', and the form raises a +validation error rather than using the initial value for 'username'. +>>> p = UserRegistration({'password': 'secret'}, initial={'username': initial_django}) +>>> p.errors +{'username': [u'This field is required.']} +>>> p.is_valid() +False + +If a Form defines 'initial' *and* 'initial' is passed as a parameter to Form(), +then the latter will get precedence. +>>> class UserRegistration(Form): +... username = CharField(max_length=10, initial=initial_django) +... password = CharField(widget=PasswordInput) +>>> p = UserRegistration(auto_id=False) +>>> print p.as_ul() +<li>Username: <input type="text" name="username" value="django" maxlength="10" /></li> +<li>Password: <input type="password" name="password" /></li> +>>> p = UserRegistration(initial={'username': initial_stephane}, auto_id=False) +>>> print p.as_ul() +<li>Username: <input type="text" name="username" value="stephane" maxlength="10" /></li> +<li>Password: <input type="password" name="password" /></li> + +# Help text ################################################################### + +You can specify descriptive text for a field by using the 'help_text' argument +to a Field class. This help text is displayed when a Form is rendered. +>>> class UserRegistration(Form): +... username = CharField(max_length=10, help_text='e.g., user@example.com') +... password = CharField(widget=PasswordInput, help_text='Choose wisely.') +>>> p = UserRegistration(auto_id=False) +>>> print p.as_ul() +<li>Username: <input type="text" name="username" maxlength="10" /> e.g., user@example.com</li> +<li>Password: <input type="password" name="password" /> Choose wisely.</li> +>>> print p.as_p() +<p>Username: <input type="text" name="username" maxlength="10" /> e.g., user@example.com</p> +<p>Password: <input type="password" name="password" /> Choose wisely.</p> +>>> print p.as_table() +<tr><th>Username:</th><td><input type="text" name="username" maxlength="10" /><br />e.g., user@example.com</td></tr> +<tr><th>Password:</th><td><input type="password" name="password" /><br />Choose wisely.</td></tr> + +The help text is displayed whether or not data is provided for the form. +>>> p = UserRegistration({'username': u'foo'}, auto_id=False) +>>> print p.as_ul() +<li>Username: <input type="text" name="username" value="foo" maxlength="10" /> e.g., user@example.com</li> +<li><ul class="errorlist"><li>This field is required.</li></ul>Password: <input type="password" name="password" /> Choose wisely.</li> + +help_text is not displayed for hidden fields. It can be used for documentation +purposes, though. +>>> class UserRegistration(Form): +... username = CharField(max_length=10, help_text='e.g., user@example.com') +... password = CharField(widget=PasswordInput) +... next = CharField(widget=HiddenInput, initial='/', help_text='Redirect destination') +>>> p = UserRegistration(auto_id=False) +>>> print p.as_ul() +<li>Username: <input type="text" name="username" maxlength="10" /> e.g., user@example.com</li> +<li>Password: <input type="password" name="password" /><input type="hidden" name="next" value="/" /></li> + +Help text can include arbitrary Unicode characters. +>>> class UserRegistration(Form): +... username = CharField(max_length=10, help_text='ŠĐĆŽćžšđ') +>>> p = UserRegistration(auto_id=False) +>>> p.as_ul() +u'<li>Username: <input type="text" name="username" maxlength="10" /> \u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111</li>' + +# Subclassing forms ########################################################### + +You can subclass a Form to add fields. The resulting form subclass will have +all of the fields of the parent Form, plus whichever fields you define in the +subclass. +>>> class Person(Form): +... first_name = CharField() +... last_name = CharField() +... birthday = DateField() +>>> class Musician(Person): +... instrument = CharField() +>>> p = Person(auto_id=False) +>>> print p.as_ul() +<li>First name: <input type="text" name="first_name" /></li> +<li>Last name: <input type="text" name="last_name" /></li> +<li>Birthday: <input type="text" name="birthday" /></li> +>>> m = Musician(auto_id=False) +>>> print m.as_ul() +<li>First name: <input type="text" name="first_name" /></li> +<li>Last name: <input type="text" name="last_name" /></li> +<li>Birthday: <input type="text" name="birthday" /></li> +<li>Instrument: <input type="text" name="instrument" /></li> + +Yes, you can subclass multiple forms. The fields are added in the order in +which the parent classes are listed. +>>> class Person(Form): +... first_name = CharField() +... last_name = CharField() +... birthday = DateField() +>>> class Instrument(Form): +... instrument = CharField() +>>> class Beatle(Person, Instrument): +... haircut_type = CharField() +>>> b = Beatle(auto_id=False) +>>> print b.as_ul() +<li>First name: <input type="text" name="first_name" /></li> +<li>Last name: <input type="text" name="last_name" /></li> +<li>Birthday: <input type="text" name="birthday" /></li> +<li>Instrument: <input type="text" name="instrument" /></li> +<li>Haircut type: <input type="text" name="haircut_type" /></li> + # Forms with prefixes ######################################################### Sometimes it's necessary to have multiple forms display on the same HTML page, @@ -2189,7 +3085,7 @@ actual field name. {} >>> p.is_valid() True ->>> p.clean_data +>>> p.cleaned_data {'first_name': u'John', 'last_name': u'Lennon', 'birthday': datetime.date(1940, 10, 9)} Let's try submitting some bad data to make sure form.errors and field.errors @@ -2233,12 +3129,12 @@ of the same form. >>> p1 = Person(data, prefix='person1') >>> p1.is_valid() True ->>> p1.clean_data +>>> p1.cleaned_data {'first_name': u'John', 'last_name': u'Lennon', 'birthday': datetime.date(1940, 10, 9)} >>> p2 = Person(data, prefix='person2') >>> p2.is_valid() True ->>> p2.clean_data +>>> p2.cleaned_data {'first_name': u'Jim', 'last_name': u'Morrison', 'birthday': datetime.date(1943, 12, 8)} By default, forms append a hyphen between the prefix and the field name, but a @@ -2264,9 +3160,60 @@ self.prefix. >>> p = Person(data, prefix='foo') >>> p.is_valid() True ->>> p.clean_data +>>> p.cleaned_data {'first_name': u'John', 'last_name': u'Lennon', 'birthday': datetime.date(1940, 10, 9)} +# Forms with NullBooleanFields ################################################ + +NullBooleanField is a bit of a special case because its presentation (widget) +is different than its data. This is handled transparently, though. + +>>> class Person(Form): +... name = CharField() +... is_cool = NullBooleanField() +>>> p = Person({'name': u'Joe'}, auto_id=False) +>>> print p['is_cool'] +<select name="is_cool"> +<option value="1" selected="selected">Unknown</option> +<option value="2">Yes</option> +<option value="3">No</option> +</select> +>>> p = Person({'name': u'Joe', 'is_cool': u'1'}, auto_id=False) +>>> print p['is_cool'] +<select name="is_cool"> +<option value="1" selected="selected">Unknown</option> +<option value="2">Yes</option> +<option value="3">No</option> +</select> +>>> p = Person({'name': u'Joe', 'is_cool': u'2'}, auto_id=False) +>>> print p['is_cool'] +<select name="is_cool"> +<option value="1">Unknown</option> +<option value="2" selected="selected">Yes</option> +<option value="3">No</option> +</select> +>>> p = Person({'name': u'Joe', 'is_cool': u'3'}, auto_id=False) +>>> print p['is_cool'] +<select name="is_cool"> +<option value="1">Unknown</option> +<option value="2">Yes</option> +<option value="3" selected="selected">No</option> +</select> +>>> p = Person({'name': u'Joe', 'is_cool': True}, auto_id=False) +>>> print p['is_cool'] +<select name="is_cool"> +<option value="1">Unknown</option> +<option value="2" selected="selected">Yes</option> +<option value="3">No</option> +</select> +>>> p = Person({'name': u'Joe', 'is_cool': False}, auto_id=False) +>>> print p['is_cool'] +<select name="is_cool"> +<option value="1">Unknown</option> +<option value="2">Yes</option> +<option value="3" selected="selected">No</option> +</select> + # Basic form processing in a view ############################################# >>> from django.template import Template, Context @@ -2275,16 +3222,16 @@ True ... 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']: +... if self.cleaned_data.get('password1') and self.cleaned_data.get('password2') and self.cleaned_data['password1'] != self.cleaned_data['password2']: ... raise ValidationError(u'Please make sure your passwords match.') -... return self.clean_data +... return self.cleaned_data >>> def my_function(method, post_data): ... if method == 'POST': ... form = UserRegistration(post_data, auto_id=False) ... else: ... form = UserRegistration(auto_id=False) ... if form.is_valid(): -... return 'VALID: %r' % form.clean_data +... return 'VALID: %r' % form.cleaned_data ... t = Template('<form action="" method="post">\n<table>\n{{ form }}\n</table>\n<input type="submit" />\n</form>') ... return t.render(Context({'form': form})) @@ -2318,13 +3265,13 @@ VALID: {'username': u'adrian', 'password1': u'secret', 'password2': u'secret'} # Some ideas for using templates with forms ################################### >>> class UserRegistration(Form): -... username = CharField(max_length=10) +... username = CharField(max_length=10, help_text="Good luck picking a username that doesn't already exist.") ... 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']: +... if self.cleaned_data.get('password1') and self.cleaned_data.get('password2') and self.cleaned_data['password1'] != self.cleaned_data['password2']: ... raise ValidationError(u'Please make sure your passwords match.') -... return self.clean_data +... return self.cleaned_data You have full flexibility in displaying form fields in a template. Just pass a Form instance to the template, and use "dot" access to refer to individual @@ -2395,6 +3342,33 @@ field an "id" attribute. <input type="submit" /> </form> +User form.[field].help_text to output a field's help text. If the given field +does not have help text, nothing will be output. +>>> t = Template('''<form action=""> +... <p>{{ form.username.label_tag }}: {{ form.username }}<br />{{ form.username.help_text }}</p> +... <p>{{ form.password1.label_tag }}: {{ form.password1 }}</p> +... <p>{{ form.password2.label_tag }}: {{ form.password2 }}</p> +... <input type="submit" /> +... </form>''') +>>> print t.render(Context({'form': UserRegistration(auto_id=False)})) +<form action=""> +<p>Username: <input type="text" name="username" maxlength="10" /><br />Good luck picking a username that doesn't already exist.</p> +<p>Password1: <input type="password" name="password1" /></p> +<p>Password2: <input type="password" name="password2" /></p> +<input type="submit" /> +</form> +>>> Template('{{ form.password1.help_text }}').render(Context({'form': UserRegistration(auto_id=False)})) +'' + +The label_tag() method takes an optional attrs argument: a dictionary of HTML +attributes to add to the <label> tag. +>>> f = UserRegistration(auto_id='id_%s') +>>> for bf in f: +... print bf.label_tag(attrs={'class': 'pretty'}) +<label for="id_username" class="pretty">Username</label> +<label for="id_password1" class="pretty">Password1</label> +<label for="id_password2" class="pretty">Password2</label> + To display the errors that aren't associated with a particular field -- e.g., the errors caused by Form.clean() -- use {{ form.non_field_errors }} in the template. If used on its own, it is displayed as a <ul> (or an empty string, if @@ -2428,17 +3402,17 @@ the list of errors is empty). You can also use it in {% if %} statements. <input type="submit" /> </form> -################# -# Extra widgets # -################# +############### +# Extra stuff # +############### -The newforms library comes with some extra, higher-level Widget classes that -demonstrate some of the library's abilities. +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() +>>> w = SelectDateWidget(years=('2007','2008','2009','2010','2011','2012','2013','2014','2015','2016')) >>> print w.render('mydate', '') <select name="mydate_month"> <option value="1">January</option> @@ -2488,7 +3462,6 @@ demonstrate some of the library's abilities. <option value="31">31</option> </select> <select name="mydate_year"> -<option value="2006">2006</option> <option value="2007">2007</option> <option value="2008">2008</option> <option value="2009">2009</option> @@ -2498,6 +3471,7 @@ demonstrate some of the library's abilities. <option value="2013">2013</option> <option value="2014">2014</option> <option value="2015">2015</option> +<option value="2016">2016</option> </select> >>> w.render('mydate', None) == w.render('mydate', '') True @@ -2550,7 +3524,6 @@ True <option value="31">31</option> </select> <select name="mydate_year"> -<option value="2006">2006</option> <option value="2007">2007</option> <option value="2008">2008</option> <option value="2009">2009</option> @@ -2560,10 +3533,155 @@ True <option value="2013">2013</option> <option value="2014">2014</option> <option value="2015">2015</option> +<option value="2016">2016</option> +</select> + +# 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') +<input type="text" name="name_0" value="some text" /> +<select multiple="multiple" name="name_1"> +<option value="J" selected="selected">John</option> +<option value="P" selected="selected">Paul</option> +<option value="G">George</option> +<option value="R">Ringo</option> </select> +<input type="text" name="name_2_0" value="2007-04-25" /><input type="text" name="name_2_1" value="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 +<tr><th><label for="id_field1_0">Field1:</label></th><td><input type="text" name="field1_0" id="id_field1_0" /> +<select multiple="multiple" name="field1_1" id="id_field1_1"> +<option value="J">John</option> +<option value="P">Paul</option> +<option value="G">George</option> +<option value="R">Ringo</option> +</select> +<input type="text" name="field1_2_0" id="id_field1_2_0" /><input type="text" name="field1_2_1" id="id_field1_2_1" /></td></tr> + +>>> f = ComplexFieldForm({'field1_0':'some text','field1_1':['J','P'], 'field1_2_0':'2007-04-25', 'field1_2_1':'06:24:00'}) +>>> print f +<tr><th><label for="id_field1_0">Field1:</label></th><td><input type="text" name="field1_0" value="some text" id="id_field1_0" /> +<select multiple="multiple" name="field1_1" id="id_field1_1"> +<option value="J" selected="selected">John</option> +<option value="P" selected="selected">Paul</option> +<option value="G">George</option> +<option value="R">Ringo</option> +</select> +<input type="text" name="field1_2_0" value="2007-04-25" id="id_field1_2_0" /><input type="text" name="field1_2_1" value="06:24:00" id="id_field1_2_1" /></td></tr> + +>>> f.cleaned_data +{'field1': u'some text,JP,2007-04-25 06:24:00'} + +################################# +# 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' + +# flatatt tests +>>> from django.newforms.util import flatatt +>>> flatatt({'id': "header"}) +u' id="header"' +>>> flatatt({'class': "news", 'title': "Read this"}) +u' class="news" title="Read this"' +>>> flatatt({}) +u'' + +#################################### +# 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__ = { + 'form_tests': form_tests, + 'localflavor': localflavor_tests, + 'regressions': regression_tests, +} + if __name__ == "__main__": import doctest doctest.testmod() |
