summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AUTHORS2
-rw-r--r--django/conf/locale/de/LC_MESSAGES/django.mobin37729 -> 38123 bytes
-rw-r--r--django/conf/locale/de/LC_MESSAGES/django.po222
-rw-r--r--django/conf/locale/el/LC_MESSAGES/django.mobin8187 -> 15668 bytes
-rw-r--r--django/conf/locale/el/LC_MESSAGES/django.po264
-rw-r--r--django/conf/locale/el/LC_MESSAGES/djangojs.mobin0 -> 1810 bytes
-rw-r--r--django/conf/locale/el/LC_MESSAGES/djangojs.po109
-rw-r--r--django/conf/locale/es_AR/LC_MESSAGES/django.mobin36332 -> 37293 bytes
-rw-r--r--django/conf/locale/es_AR/LC_MESSAGES/django.po258
-rw-r--r--django/conf/locale/es_AR/LC_MESSAGES/djangojs.mobin1566 -> 1576 bytes
-rw-r--r--django/conf/locale/es_AR/LC_MESSAGES/djangojs.po34
-rw-r--r--django/conf/project_template/urls.py2
-rw-r--r--django/contrib/admin/media/js/admin/DateTimeShortcuts.js10
-rw-r--r--django/contrib/admin/media/js/core.js8
-rw-r--r--django/contrib/admin/templates/admin/search_form.html2
-rw-r--r--django/contrib/admin/views/main.py14
-rw-r--r--django/contrib/contenttypes/management.py11
-rw-r--r--django/contrib/formtools/__init__.py0
-rw-r--r--django/contrib/formtools/preview.py160
-rw-r--r--django/contrib/formtools/templates/formtools/form.html15
-rw-r--r--django/contrib/formtools/templates/formtools/preview.html36
-rw-r--r--django/contrib/sitemaps/__init__.py2
-rw-r--r--django/contrib/sitemaps/templates/sitemap.xml2
-rw-r--r--django/contrib/sitemaps/templates/sitemap_index.xml2
-rw-r--r--django/core/handlers/base.py6
-rw-r--r--django/core/handlers/wsgi.py8
-rw-r--r--django/core/mail.py8
-rw-r--r--django/core/servers/fastcgi.py2
-rw-r--r--django/core/xheaders.py2
-rw-r--r--django/db/__init__.py2
-rw-r--r--django/db/models/fields/__init__.py13
-rw-r--r--django/http/__init__.py2
-rw-r--r--django/middleware/gzip.py1
-rw-r--r--django/newforms/__init__.py13
-rw-r--r--django/newforms/fields.py81
-rw-r--r--django/newforms/forms.py206
-rw-r--r--django/newforms/models.py13
-rw-r--r--django/newforms/util.py19
-rw-r--r--django/newforms/widgets.py183
-rw-r--r--django/template/__init__.py6
-rw-r--r--django/views/generic/list_detail.py2
-rw-r--r--docs/add_ons.txt17
-rw-r--r--docs/newforms.txt79
-rw-r--r--docs/sessions.txt2
-rw-r--r--docs/settings.txt2
-rw-r--r--docs/sitemaps.txt19
-rw-r--r--docs/templates.txt2
-rw-r--r--docs/templates_python.txt2
-rw-r--r--docs/testing.txt4
-rw-r--r--setup.py10
-rw-r--r--tests/regressiontests/forms/tests.py1182
-rw-r--r--tests/regressiontests/templates/tests.py18
52 files changed, 2408 insertions, 649 deletions
diff --git a/AUTHORS b/AUTHORS
index b291f22400..dbbf6e7bad 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -51,6 +51,7 @@ answer newbie questions, and generally made Django that much better:
Jiri Barton
Ned Batchelder <http://www.nedbatchelder.com/>
Shannon -jj Behrens <http://jjinux.blogspot.com/>
+ Esdras Beleza <linux@esdrasbeleza.com>
James Bennett
Paul Bissex <http://e-scribe.com/>
Simon Blanchard
@@ -150,6 +151,7 @@ answer newbie questions, and generally made Django that much better:
SmileyChris <smileychris@gmail.com>
sopel
Thomas Steinacher <tom@eggdrop.ch>
+ nowell strite
Radek Å varz <http://www.svarz.cz/translate/>
Swaroop C H <http://www.swaroopch.info>
Aaron Swartz <http://www.aaronsw.com/>
diff --git a/django/conf/locale/de/LC_MESSAGES/django.mo b/django/conf/locale/de/LC_MESSAGES/django.mo
index aef68099d3..0dd2b26dff 100644
--- a/django/conf/locale/de/LC_MESSAGES/django.mo
+++ b/django/conf/locale/de/LC_MESSAGES/django.mo
Binary files differ
diff --git a/django/conf/locale/de/LC_MESSAGES/django.po b/django/conf/locale/de/LC_MESSAGES/django.po
index 614ea69247..c5e3f4282a 100644
--- a/django/conf/locale/de/LC_MESSAGES/django.po
+++ b/django/conf/locale/de/LC_MESSAGES/django.po
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Django 1.0\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2006-09-25 16:04+0200\n"
+"POT-Creation-Date: 2006-11-15 18:35+0100\n"
"PO-Revision-Date: 2005-10-08 00:03+0200\n"
"Last-Translator: Georg Bauer <gb@bofh.ms>\n"
"MIME-Version: 1.0\n"
@@ -452,7 +452,7 @@ msgid ""
"Looks like your browser isn't configured to accept cookies. Please enable "
"cookies, reload this page, and try again."
msgstr ""
-"Es sieht danach aus, das der Browser keine Cookies akzeptiert. Bitte im "
+"Es sieht danach aus, dass der Browser keine Cookies akzeptiert. Bitte im "
"Browser Cookies aktivieren und diese Seite neu laden."
#: contrib/admin/views/decorators.py:83
@@ -470,13 +470,13 @@ msgstr ""
msgid "Site administration"
msgstr "Website Verwaltung"
-#: contrib/admin/views/main.py:257 contrib/admin/views/auth.py:17
+#: contrib/admin/views/main.py:257 contrib/admin/views/auth.py:18
#, python-format
msgid "The %(name)s \"%(obj)s\" was added successfully."
msgstr "%(name)s \"%(obj)s\" wurde erfolgreich hinzugefügt."
#: contrib/admin/views/main.py:261 contrib/admin/views/main.py:347
-#: contrib/admin/views/auth.py:22
+#: contrib/admin/views/auth.py:23
msgid "You may edit it again below."
msgstr "Das Element kann jetzt weiter geändert werden."
@@ -496,7 +496,7 @@ msgid "Added %s."
msgstr "%s hinzugefügt."
#: contrib/admin/views/main.py:335 contrib/admin/views/main.py:337
-#: contrib/admin/views/main.py:339
+#: contrib/admin/views/main.py:339 db/models/manipulators.py:306
msgid "and"
msgstr "und"
@@ -702,7 +702,7 @@ msgstr "XML Text"
msgid "%s does not appear to be a urlpattern object"
msgstr "%s ist scheinbar kein urlpattern Objekt"
-#: contrib/admin/views/auth.py:28
+#: contrib/admin/views/auth.py:29
msgid "Add user"
msgstr "Benutzer zufügen"
@@ -861,10 +861,6 @@ msgstr "Keine vorhanden"
msgid "Add %(name)s"
msgstr "%(name)s zufügen"
-#: contrib/admin/templates/admin/login.html:22
-msgid "Have you <a href=\"/password_reset/\">forgotten your password</a>?"
-msgstr "Haben Sie <a href=\"/password_reset/\">ihr Passwort vergessen</a>?"
-
#: contrib/admin/templates/admin/base.html:25
msgid "Welcome,"
msgstr "Willkommen,"
@@ -891,7 +887,7 @@ msgid ""
"Are you sure you want to delete the %(object_name)s \"%(escaped_object)s\"? "
"All of the following related items will be deleted:"
msgstr ""
-"Sind Sie sicher, das Sie %(object_name)s \"%(escaped_object)s\" löschen "
+"Sind Sie sicher, dass Sie %(object_name)s \"%(escaped_object)s\" löschen "
"wollen? Es werden zusätzlich die folgenden abhängigen Daten mit gelöscht:"
#: contrib/admin/templates/admin/delete_confirmation.html:26
@@ -1061,7 +1057,7 @@ msgid ""
"password twice so we can verify you typed it in correctly."
msgstr ""
"Bitte geben Sie aus Sicherheitsgründen erst Ihr altes Kennwort und darunter "
-"dann zweimal (um sicherzustellen, das Sie es korrekt eingegeben haben) das "
+"dann zweimal (um sicherzustellen, dass Sie es korrekt eingegeben haben) das "
"neue Kennwort ein."
#: contrib/admin/templates/registration/password_change_form.html:17
@@ -1087,7 +1083,7 @@ msgstr "Sie erhalten diese Mail, weil Sie ein neues Kennwort"
#: contrib/admin/templates/registration/password_reset_email.html:3
#, python-format
msgid "for your user account at %(site_name)s"
-msgstr "für ihren Benutzer bei %(site_name)s angefordert haben."
+msgstr "für Ihren Benutzer bei %(site_name)s angefordert haben."
#: contrib/admin/templates/registration/password_reset_email.html:5
#, python-format
@@ -1104,7 +1100,7 @@ msgstr "Ihr Benutzername, falls Sie ihn vergessen haben:"
#: contrib/admin/templates/registration/password_reset_email.html:13
msgid "Thanks for using our site!"
-msgstr "Vielen Dank, das Sie unsere Seiten benutzen!"
+msgstr "Vielen Dank, dass Sie unsere Seiten benutzen!"
#: contrib/admin/templates/registration/password_reset_email.html:15
#, python-format
@@ -1367,7 +1363,7 @@ msgid ""
"Designates that this user has all permissions without explicitly assigning "
"them."
msgstr ""
-"Bestimmt, das dieser Benutzer alle Berechtigungen hat, ohne diese einzeln "
+"Bestimmt, dass dieser Benutzer alle Berechtigungen hat, ohne diese einzeln "
"zuweisen zu müssen."
#: contrib/auth/models.py:98
@@ -1414,10 +1410,18 @@ msgstr "Wichtige Daten"
msgid "Groups"
msgstr "Gruppen"
-#: contrib/auth/models.py:256
+#: contrib/auth/models.py:258
msgid "message"
msgstr "Mitteilung"
+#: contrib/auth/forms.py:16
+msgid "The two password fields didn't match."
+msgstr "Die zwei Passwörter sind nicht gleich."
+
+#: contrib/auth/forms.py:24
+msgid "A user with that username already exists."
+msgstr "Ein Benutzer mit diesem Namen existiert bereits."
+
#: contrib/auth/forms.py:52
msgid ""
"Your Web browser doesn't appear to have cookies enabled. Cookies are "
@@ -1432,10 +1436,10 @@ msgstr "Dieser Benutzer ist inaktiv."
#: contrib/auth/forms.py:84
msgid ""
-"That e-mail address doesn't have an associated user acount. Are you sure "
+"That e-mail address doesn't have an associated user account. Are you sure "
"you've registered?"
msgstr ""
-"Die Email-Adresse hat keinen Benutzer zugeordnet. Sicher, das die Adresse "
+"Die Email-Adresse hat keinen Benutzer zugeordnet. Sicher, dass die Adresse "
"hier angemeldet ist?"
#: contrib/auth/forms.py:116
@@ -1783,58 +1787,62 @@ msgid "Norwegian"
msgstr "Norwegisch"
#: conf/global_settings.py:59
+msgid "Polish"
+msgstr "Polnisch"
+
+#: conf/global_settings.py:60
msgid "Brazilian"
msgstr "Brasilianisch"
-#: conf/global_settings.py:60
+#: conf/global_settings.py:61
msgid "Romanian"
msgstr "Rumänisch"
-#: conf/global_settings.py:61
+#: conf/global_settings.py:62
msgid "Russian"
msgstr "Russisch"
-#: conf/global_settings.py:62
+#: conf/global_settings.py:63
msgid "Slovak"
msgstr "Slowakisch"
-#: conf/global_settings.py:63
+#: conf/global_settings.py:64
msgid "Slovenian"
msgstr "Slowenisch"
-#: conf/global_settings.py:64
+#: conf/global_settings.py:65
msgid "Serbian"
msgstr "Serbisch"
-#: conf/global_settings.py:65
+#: conf/global_settings.py:66
msgid "Swedish"
msgstr "Schwedisch"
-#: conf/global_settings.py:66
+#: conf/global_settings.py:67
msgid "Tamil"
msgstr "Tamilisch"
-#: conf/global_settings.py:67
+#: conf/global_settings.py:68
msgid "Turkish"
msgstr "Türkisch"
-#: conf/global_settings.py:68
+#: conf/global_settings.py:69
msgid "Ukrainian"
msgstr "Ukrainisch"
-#: conf/global_settings.py:69
+#: conf/global_settings.py:70
msgid "Simplified Chinese"
msgstr "Vereinfachtes Chinesisch"
-#: conf/global_settings.py:70
+#: conf/global_settings.py:71
msgid "Traditional Chinese"
msgstr "Traditionelles Chinesisch"
-#: core/validators.py:63
+#: core/validators.py:64
msgid "This value must contain only letters, numbers and underscores."
msgstr "Dieser Wert darf nur Buchstaben, Ziffern und Unterstriche enthalten."
-#: core/validators.py:67
+#: core/validators.py:68
msgid ""
"This value must contain only letters, numbers, underscores, dashes or "
"slashes."
@@ -1842,85 +1850,85 @@ msgstr ""
"Dieser Wert darf nur Buchstaben, Ziffern, Unterstriche und Schrägstriche "
"enthalten."
-#: core/validators.py:71
+#: core/validators.py:72
msgid "This value must contain only letters, numbers, underscores or hyphens."
msgstr ""
"Dieser Wert darf nur Buchstaben, Ziffern, Unterstriche und Bindestriche "
"enthalten."
-#: core/validators.py:75
+#: core/validators.py:76
msgid "Uppercase letters are not allowed here."
msgstr "Großbuchstaben sind hier nicht erlaubt."
-#: core/validators.py:79
+#: core/validators.py:80
msgid "Lowercase letters are not allowed here."
msgstr "Kleinbuchstaben sind hier nicht erlaubt."
-#: core/validators.py:86
+#: core/validators.py:87
msgid "Enter only digits separated by commas."
msgstr "Hier sind nur durch Komma getrennte Ziffern erlaubt."
-#: core/validators.py:98
+#: core/validators.py:99
msgid "Enter valid e-mail addresses separated by commas."
msgstr "Bitte mit Komma getrennte, gültige eMail-Adressen eingeben."
-#: core/validators.py:102
+#: core/validators.py:103
msgid "Please enter a valid IP address."
msgstr "Bitte eine gültige IP-Adresse eingeben."
-#: core/validators.py:106
+#: core/validators.py:107
msgid "Empty values are not allowed here."
msgstr "Dieses Feld darf nicht leer sein."
-#: core/validators.py:110
+#: core/validators.py:111
msgid "Non-numeric characters aren't allowed here."
msgstr "Nichtnumerische Zeichen sind hier nicht erlaubt."
-#: core/validators.py:114
+#: core/validators.py:115
msgid "This value can't be comprised solely of digits."
msgstr "Dieser Wert darf nicht nur aus Ziffern bestehen."
-#: core/validators.py:119
+#: core/validators.py:120
msgid "Enter a whole number."
msgstr "Bitte eine ganze Zahl eingeben."
-#: core/validators.py:123
+#: core/validators.py:124
msgid "Only alphabetical characters are allowed here."
msgstr "Nur alphabetische Zeichen sind hier erlaubt."
-#: core/validators.py:138
+#: core/validators.py:139
msgid "Year must be 1900 or later."
msgstr "Das Jahr muss 1900 oder später sein."
-#: core/validators.py:142
+#: core/validators.py:143
#, python-format
msgid "Invalid date: %s."
msgstr "Ungültiges Datum: %s"
-#: core/validators.py:146 db/models/fields/__init__.py:415
+#: core/validators.py:147 db/models/fields/__init__.py:424
msgid "Enter a valid date in YYYY-MM-DD format."
msgstr "Bitte ein gültiges Datum im Format JJJJ-MM-TT eingeben."
-#: core/validators.py:151
+#: core/validators.py:152
msgid "Enter a valid time in HH:MM format."
msgstr "Bitte eine gültige Zeit im Format SS:MM eingeben."
-#: core/validators.py:155 db/models/fields/__init__.py:477
+#: core/validators.py:156 db/models/fields/__init__.py:488
msgid "Enter a valid date/time in YYYY-MM-DD HH:MM format."
msgstr ""
"Bitte eine gültige Datums- und Zeitangabe im Format JJJJ-MM-TT SS:MM "
"eingeben."
-#: core/validators.py:160
+#: core/validators.py:161
msgid "Enter a valid e-mail address."
msgstr "Bitte eine gültige eMail-Adresse eingeben"
-#: core/validators.py:172 core/validators.py:401 forms/__init__.py:661
+#: core/validators.py:173 core/validators.py:442 forms/__init__.py:667
msgid "No file was submitted. Check the encoding type on the form."
msgstr ""
"Es wurde keine Datei geschickt. Eventuell ist das Formular-Encoding falsch."
-#: core/validators.py:176
+#: core/validators.py:177
msgid ""
"Upload a valid image. The file you uploaded was either not an image or a "
"corrupted image."
@@ -1928,27 +1936,27 @@ msgstr ""
"Bitte ein Bild hochladen. Die Datei, die hochgeladen wurde, ist kein Bild "
"oder ist defekt."
-#: core/validators.py:183
+#: core/validators.py:184
#, python-format
msgid "The URL %s does not point to a valid image."
msgstr "Die URL %s zeigt nicht auf ein gültiges Bild."
-#: core/validators.py:187
+#: core/validators.py:188
#, python-format
msgid "Phone numbers must be in XXX-XXX-XXXX format. \"%s\" is invalid."
msgstr ""
"Telefonnummern müssen im Format XXX-XXX-XXXX sein. \"%s\" ist ungültig."
-#: core/validators.py:195
+#: core/validators.py:196
#, python-format
msgid "The URL %s does not point to a valid QuickTime video."
msgstr "Die URL %s zeigt nicht auf ein gültiges QuickTime video."
-#: core/validators.py:199
+#: core/validators.py:200
msgid "A valid URL is required."
msgstr "Eine gültige URL ist hier verlangt."
-#: core/validators.py:213
+#: core/validators.py:214
#, python-format
msgid ""
"Valid HTML is required. Specific errors are:\n"
@@ -1957,71 +1965,83 @@ msgstr ""
"Bitte gültiges HTML eingeben. Fehler sind:\n"
"%s"
-#: core/validators.py:220
+#: core/validators.py:221
#, python-format
msgid "Badly formed XML: %s"
msgstr "Ungültiges XML: %s"
-#: core/validators.py:230
+#: core/validators.py:238
#, python-format
msgid "Invalid URL: %s"
msgstr "Ungültige URL: %s"
-#: core/validators.py:234 core/validators.py:236
+#: core/validators.py:243 core/validators.py:245
#, python-format
msgid "The URL %s is a broken link."
msgstr "Die URL %s funktioniert nicht."
-#: core/validators.py:242
+#: core/validators.py:251
msgid "Enter a valid U.S. state abbreviation."
msgstr "Bitte eine gültige Abkürzung für einen US-Staat eingeben."
-#: core/validators.py:256
+#: core/validators.py:265
#, python-format
msgid "Watch your mouth! The word %s is not allowed here."
msgid_plural "Watch your mouth! The words %s are not allowed here."
msgstr[0] "Keine Schimpfworte! Das Wort %s ist hier nicht gern gesehen!"
msgstr[1] "Keine Schimpfworte! Die Wörter %s sind hier nicht gern gesehen!"
-#: core/validators.py:263
+#: core/validators.py:272
#, python-format
msgid "This field must match the '%s' field."
msgstr "Dieses Feld muss zum Feld '%s' passen."
-#: core/validators.py:282
+#: core/validators.py:291
msgid "Please enter something for at least one field."
msgstr "Bitte mindestens eines der Felder ausfüllen."
-#: core/validators.py:291 core/validators.py:302
+#: core/validators.py:300 core/validators.py:311
msgid "Please enter both fields or leave them both empty."
msgstr "Bitte entweder beide Felder ausfüllen, oder beide leer lassen."
-#: core/validators.py:309
+#: core/validators.py:318
#, python-format
msgid "This field must be given if %(field)s is %(value)s"
msgstr ""
"Dieses Feld muss gefüllt sein, wenn Feld %(field)s den Wert %(value)s hat."
-#: core/validators.py:321
+#: core/validators.py:330
#, python-format
msgid "This field must be given if %(field)s is not %(value)s"
msgstr ""
"Dieses Feld muss gefüllt sein, wenn Feld %(field)s nicht %(value)s ist."
-#: core/validators.py:340
+#: core/validators.py:349
msgid "Duplicate values are not allowed."
msgstr "Doppelte Werte sind hier nicht erlaubt."
-#: core/validators.py:363
+#: core/validators.py:364
+msgid "This value must be between %s and %s."
+msgstr "Dieser Wert muss zwischen %s und %s sein."
+
+#: core/validators.py:366
+msgid "This value must be at least %s."
+msgstr "Dieser Wert muss mindestens %s sein."
+
+#: core/validators.py:368
+msgid "This value must be no more than %s."
+msgstr "Dieser Wert darf maximal %s sein."
+
+#: core/validators.py:404
#, python-format
msgid "This value must be a power of %s."
msgstr "Dieser Wert muss eine Potenz von %s sein."
-#: core/validators.py:374
+#: core/validators.py:415
msgid "Please enter a valid decimal number."
msgstr "Bitte eine gültige Dezimalzahl eingeben."
-#: core/validators.py:378
+#: core/validators.py:419
#, python-format
msgid "Please enter a valid decimal number with at most %s total digit."
msgid_plural ""
@@ -2029,7 +2049,7 @@ msgid_plural ""
msgstr[0] "Bitte eine gültige Dezimalzahl mit maximal %s Ziffer eingeben."
msgstr[1] "Bitte eine gültige Dezimalzahl mit maximal %s Ziffern eingeben."
-#: core/validators.py:381
+#: core/validators.py:422
#, python-format
msgid ""
"Please enter a valid decimal number with a whole part of at most %s digit."
@@ -2038,7 +2058,7 @@ msgid_plural ""
msgstr[0] "Bitte eine gültige Dezimalzahl mit maximal %s Ziffer eingeben."
msgstr[1] "Bitte eine gültige Dezimalzahl mit maximal %s Ziffern eingeben."
-#: core/validators.py:384
+#: core/validators.py:425
#, python-format
msgid "Please enter a valid decimal number with at most %s decimal place."
msgid_plural ""
@@ -2048,39 +2068,39 @@ msgstr[0] ""
msgstr[1] ""
"Bitte eine gültige Dezimalzahl mit maximal %s Dezimalstellen eingeben."
-#: core/validators.py:394
+#: core/validators.py:435
#, python-format
msgid "Make sure your uploaded file is at least %s bytes big."
msgstr ""
"Bitte sicherstellen, daß die hochgeladene Datei mindestens %s Bytes gross "
"ist."
-#: core/validators.py:395
+#: core/validators.py:436
#, python-format
msgid "Make sure your uploaded file is at most %s bytes big."
msgstr ""
"Bitte sicherstellen, daß die hochgeladene Datei maximal %s Bytes gross ist."
-#: core/validators.py:412
+#: core/validators.py:453
msgid "The format for this field is wrong."
msgstr "Das Format für dieses Feld ist falsch."
-#: core/validators.py:427
+#: core/validators.py:468
msgid "This field is invalid."
msgstr "Dieses Feld ist ungültig."
-#: core/validators.py:463
+#: core/validators.py:504
#, python-format
msgid "Could not retrieve anything from %s."
msgstr "Konnte nichts von %s empfangen."
-#: core/validators.py:466
+#: core/validators.py:507
#, python-format
msgid ""
"The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'."
msgstr "Die URL %(url)s lieferte den falschen Content-Type '%(contenttype)s'."
-#: core/validators.py:499
+#: core/validators.py:540
#, python-format
msgid ""
"Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with "
@@ -2089,7 +2109,7 @@ msgstr ""
"Bitte das ungeschlossene %(tag)s Tag in Zeile %(line)s schließen. Die Zeile "
"beginnt mit \"%(start)s\"."
-#: core/validators.py:503
+#: core/validators.py:544
#, python-format
msgid ""
"Some text starting on line %(line)s is not allowed in that context. (Line "
@@ -2098,7 +2118,7 @@ msgstr ""
"In Zeile %(line)s ist Text, der nicht in dem Kontext erlaubt ist. Die Zeile "
"beginnt mit \"%(start)s\"."
-#: core/validators.py:508
+#: core/validators.py:549
#, python-format
msgid ""
"\"%(attr)s\" on line %(line)s is an invalid attribute. (Line starts with \"%"
@@ -2107,7 +2127,7 @@ msgstr ""
"Das Attribute %(attr)s in Zeile %(line)s ist ungültig. Die Zeile beginnt mit "
"\"%(start)s\"."
-#: core/validators.py:513
+#: core/validators.py:554
#, python-format
msgid ""
"\"<%(tag)s>\" on line %(line)s is an invalid tag. (Line starts with \"%"
@@ -2116,7 +2136,7 @@ msgstr ""
"<%(tag)s> in Zeile %(line)s ist ungültig. Die Zeile beginnt mit \"%(start)s"
"\"."
-#: core/validators.py:517
+#: core/validators.py:558
#, python-format
msgid ""
"A tag on line %(line)s is missing one or more required attributes. (Line "
@@ -2125,7 +2145,7 @@ msgstr ""
"Ein Tag in Zeile %(line)s hat eines oder mehrere Pflichtattribute nicht. Die "
"Zeile beginnt mit \"%(start)s\"."
-#: core/validators.py:522
+#: core/validators.py:563
#, python-format
msgid ""
"The \"%(attr)s\" attribute on line %(line)s has an invalid value. (Line "
@@ -2149,37 +2169,37 @@ msgstr "%(verbose_name)s wurde erfolgreich aktualisiert."
msgid "The %(verbose_name)s was deleted."
msgstr "%(verbose_name)s wurde gelöscht"
-#: db/models/manipulators.py:302
+#: db/models/manipulators.py:305
#, python-format
msgid "%(object)s with this %(type)s already exists for the given %(field)s."
msgstr ""
"Ein '%(object)s' in dieser '%(type)s' existiert bereits für dieses '%(field)"
"s'."
-#: db/models/fields/__init__.py:40
+#: db/models/fields/__init__.py:41
#, python-format
msgid "%(optname)s with this %(fieldname)s already exists."
msgstr "Ein '%(optname)s' mit diesem '%(fieldname)s' existiert bereits."
-#: db/models/fields/__init__.py:114 db/models/fields/__init__.py:265
-#: db/models/fields/__init__.py:551 db/models/fields/__init__.py:562
-#: forms/__init__.py:346
+#: db/models/fields/__init__.py:115 db/models/fields/__init__.py:266
+#: db/models/fields/__init__.py:569 db/models/fields/__init__.py:580
+#: forms/__init__.py:352
msgid "This field is required."
msgstr "Dieses Feld ist zwingend."
-#: db/models/fields/__init__.py:340
+#: db/models/fields/__init__.py:349
msgid "This value must be an integer."
msgstr "Dieser Wert muss eine Ganzzahl sein."
-#: db/models/fields/__init__.py:372
+#: db/models/fields/__init__.py:381
msgid "This value must be either True or False."
msgstr "Dieser Wert muss wahr oder falsch sein."
-#: db/models/fields/__init__.py:388
+#: db/models/fields/__init__.py:397
msgid "This field cannot be null."
msgstr "Dieses Feld darf nicht leer sein."
-#: db/models/fields/__init__.py:571
+#: db/models/fields/__init__.py:589
msgid "Enter a valid filename."
msgstr "Bitte einen gültigen Dateinamen eingeben"
@@ -2209,36 +2229,36 @@ msgstr[0] ""
msgstr[1] ""
"Bitte gültige IDs für %(self)s eingeben. Die Werte %(value)r sind ungültig."
-#: forms/__init__.py:381
+#: forms/__init__.py:387
#, python-format
msgid "Ensure your text is less than %s character."
msgid_plural "Ensure your text is less than %s characters."
-msgstr[0] "Bitte sicherstellen, das der Text weniger als %s Zeichen hat."
-msgstr[1] "Bitte sicherstellen, das der Text weniger als %s Zeichen hat."
+msgstr[0] "Bitte sicherstellen, dass der Text weniger als %s Zeichen hat."
+msgstr[1] "Bitte sicherstellen, dass der Text weniger als %s Zeichen hat."
-#: forms/__init__.py:386
+#: forms/__init__.py:392
msgid "Line breaks are not allowed here."
msgstr "Zeilenumbrüche sind hier nicht erlaubt."
-#: forms/__init__.py:487 forms/__init__.py:560 forms/__init__.py:599
+#: forms/__init__.py:493 forms/__init__.py:566 forms/__init__.py:605
#, python-format
msgid "Select a valid choice; '%(data)s' is not in %(choices)s."
msgstr ""
"Bitte eine gültige Auswahl treffen; '%(data)s' ist nicht in %(choices)s."
-#: forms/__init__.py:663
+#: forms/__init__.py:669
msgid "The submitted file is empty."
msgstr "Die ausgewählte Datei ist leer."
-#: forms/__init__.py:719
+#: forms/__init__.py:725
msgid "Enter a whole number between -32,768 and 32,767."
msgstr "Bitte eine ganze Zahl zwischen -32.768 und 32.767 eingeben."
-#: forms/__init__.py:729
+#: forms/__init__.py:735
msgid "Enter a positive number."
msgstr "Bitte eine ganze, positive Zahl eingeben."
-#: forms/__init__.py:739
+#: forms/__init__.py:745
msgid "Enter a whole number between 0 and 32,767."
msgstr "Bitte eine ganze Zahl zwischen 0 und 32.767 eingeben."
diff --git a/django/conf/locale/el/LC_MESSAGES/django.mo b/django/conf/locale/el/LC_MESSAGES/django.mo
index 1c95d6b5d9..4a7d8e41f0 100644
--- a/django/conf/locale/el/LC_MESSAGES/django.mo
+++ b/django/conf/locale/el/LC_MESSAGES/django.mo
Binary files differ
diff --git a/django/conf/locale/el/LC_MESSAGES/django.po b/django/conf/locale/el/LC_MESSAGES/django.po
index 173b300d04..06099eb9da 100644
--- a/django/conf/locale/el/LC_MESSAGES/django.po
+++ b/django/conf/locale/el/LC_MESSAGES/django.po
@@ -107,6 +107,11 @@ msgid ""
"\n"
"http://%(domain)s%(url)s"
msgstr ""
+"Σχόλιο από τον/την %(user)s την %(date)s\n"
+"\n"
+"%(comment)s\n"
+"\n"
+"http://%(domain)s%(url)s"
#: contrib/comments/models.py:168
msgid "person's name"
@@ -141,11 +146,11 @@ msgstr "ημεÏομηνία βαθμολογίας"
#: contrib/comments/models.py:237
#, fuzzy
msgid "karma score"
-msgstr "βαθμολογία"
+msgstr "karma"
#: contrib/comments/models.py:238
msgid "karma scores"
-msgstr ""
+msgstr "karma"
#: contrib/comments/models.py:242
#, python-format
@@ -159,7 +164,7 @@ msgid ""
"\n"
"%(text)s"
msgstr ""
-"Αυτο το σχόλιο σημειώθει απο %(χÏήστη)ες\n"
+"Αυτο το σχόλιο σημειώθηκε απο %(χÏήστη)ες\n"
"\n"
"%(κείμενο)α"
@@ -245,7 +250,7 @@ msgstr ""
#: contrib/comments/views/comments.py:193
#: contrib/comments/views/comments.py:284
msgid "One or more of the required fields wasn't submitted"
-msgstr "Ένα ή πεÏισσότεÏα από τα απαιτοÏμενα πεδία δεν υποβλίθει"
+msgstr "Ένα ή πεÏισσότεÏα από τα απαιτοÏμενα πεδία δεν υπεβλήθει"
#: contrib/comments/views/comments.py:197
#: contrib/comments/views/comments.py:286
@@ -268,7 +273,7 @@ msgstr ""
#: contrib/comments/templates/comments/form.html:8
#: contrib/admin/templates/admin/login.html:17
msgid "Username:"
-msgstr ""
+msgstr "Όνομα χÏήστη:"
#: contrib/comments/templates/comments/form.html:6
#: contrib/admin/templates/admin/login.html:20
@@ -298,21 +303,21 @@ msgstr "Ξεχάσατε τον κωδικό σας;"
#: contrib/admin/templates/admin_doc/index.html:4
#: contrib/admin/templates/admin_doc/model_index.html:5
msgid "Log out"
-msgstr ""
+msgstr "ΑποσÏνδεση"
#: contrib/comments/templates/comments/form.html:12
msgid "Ratings"
-msgstr ""
+msgstr "Βαθμολογίες"
#: contrib/comments/templates/comments/form.html:12
#: contrib/comments/templates/comments/form.html:23
msgid "Required"
-msgstr ""
+msgstr "ΑπαÏαίτητο"
#: contrib/comments/templates/comments/form.html:12
#: contrib/comments/templates/comments/form.html:23
msgid "Optional"
-msgstr ""
+msgstr "ΠÏοαιÏετικό"
#: contrib/comments/templates/comments/form.html:23
msgid "Post a photo"
@@ -326,11 +331,11 @@ msgstr "Σχόλιο:"
#: contrib/comments/templates/comments/form.html:32
#: contrib/comments/templates/comments/freeform.html:9
msgid "Preview comment"
-msgstr ""
+msgstr "ΠÏοεπισκόπηση σχολίου"
#: contrib/comments/templates/comments/freeform.html:4
msgid "Your name:"
-msgstr ""
+msgstr "Το όνομα σας:"
#: contrib/admin/filterspecs.py:40
#, python-format
@@ -338,15 +343,17 @@ msgid ""
"<h3>By %s:</h3>\n"
"<ul>\n"
msgstr ""
+"<h3>Από %s:</h3>\n"
+"<ul>\n"
#: contrib/admin/filterspecs.py:70 contrib/admin/filterspecs.py:88
#: contrib/admin/filterspecs.py:143
msgid "All"
-msgstr ""
+msgstr "Όλα"
#: contrib/admin/filterspecs.py:109
msgid "Any date"
-msgstr ""
+msgstr "Όλες οι ημεÏομηνίες"
#: contrib/admin/filterspecs.py:110
msgid "Today"
@@ -354,7 +361,7 @@ msgstr "ΣήμεÏα"
#: contrib/admin/filterspecs.py:113
msgid "Past 7 days"
-msgstr ""
+msgstr "Τις Ï€ÏοηγοÏμενες 7 ημέÏες"
#: contrib/admin/filterspecs.py:115
msgid "This month"
@@ -374,7 +381,7 @@ msgstr "Όχι"
#: contrib/admin/filterspecs.py:150
msgid "Unknown"
-msgstr "’γνωστο"
+msgstr "Άγνωστο"
#: contrib/admin/models.py:16
msgid "action time"
@@ -441,7 +448,7 @@ msgstr ""
#: contrib/admin/views/decorators.py:82
msgid "Usernames cannot contain the '@' character."
-msgstr "Τα ονόματα των ΧÏηστών δεν μποÏόυν να πεÏιέχουν τον χαÏακτήÏα '@'."
+msgstr "Τα ονόματα των χÏηστών δεν μποÏόυν να πεÏιέχουν τον χαÏακτήÏα '@'."
#: contrib/admin/views/decorators.py:84
#, python-format
@@ -452,16 +459,16 @@ msgstr ""
#: contrib/admin/views/main.py:226
msgid "Site administration"
-msgstr "ΔιαχείÏιση του Î”Î¹Î±Î´Ï…ÎºÏ„Î¹Î±ÎºÎ¿Ï Ï‡ÏŽÏου"
+msgstr "ΔιαχείÏιση του Î”Î¹Î±Î´Î¹ÎºÏ„Ï…Î±ÎºÎ¿Ï Ï‡ÏŽÏου"
#: contrib/admin/views/main.py:260
#, python-format
msgid "The %(name)s \"%(obj)s\" was added successfully."
-msgstr ""
+msgstr "Το %(name)s \"%(obj)s\" αποθηκεÏτηκε επιτυχώς."
#: contrib/admin/views/main.py:264 contrib/admin/views/main.py:348
msgid "You may edit it again below."
-msgstr ""
+msgstr "ΜποÏείτε να το επεξεÏγαστείτε ξανα παÏακάτω."
#: contrib/admin/views/main.py:272 contrib/admin/views/main.py:357
#, python-format
@@ -471,12 +478,12 @@ msgstr "ΜποÏείτε να Ï€Ïοσθέσετε ακόμα ένα %s απο Î
#: contrib/admin/views/main.py:290
#, python-format
msgid "Add %s"
-msgstr ""
+msgstr "ΠÏοσθήκη %s"
#: contrib/admin/views/main.py:336
#, python-format
msgid "Added %s."
-msgstr ""
+msgstr "ΠÏοστέθηκε %s."
#: contrib/admin/views/main.py:336 contrib/admin/views/main.py:338
#: contrib/admin/views/main.py:340
@@ -486,32 +493,33 @@ msgstr "και"
#: contrib/admin/views/main.py:338
#, python-format
msgid "Changed %s."
-msgstr ""
+msgstr "ΕπεξεÏγάσθηκε %s."
#: contrib/admin/views/main.py:340
#, python-format
msgid "Deleted %s."
-msgstr ""
+msgstr "ΔιεγÏάφη %s."
#: contrib/admin/views/main.py:343
msgid "No fields changed."
-msgstr ""
+msgstr "Κανένα πεδίο δεν άλλαξε."
#: contrib/admin/views/main.py:346
#, python-format
msgid "The %(name)s \"%(obj)s\" was changed successfully."
-msgstr ""
+msgstr "Το %(name)s \"%(obj)s\" επεξεÏγάσθηκε επιτυχώς."
#: contrib/admin/views/main.py:354
#, python-format
msgid ""
"The %(name)s \"%(obj)s\" was added successfully. You may edit it again below."
msgstr ""
+"Το %(name)s \"%(obj)s\" αποθηκεÏθηκε επιτυχώς. ΜποÏείτε να το επεξεÏγαστείτε πάλι παÏακάτω."
#: contrib/admin/views/main.py:392
#, python-format
msgid "Change %s"
-msgstr ""
+msgstr "Αλλαγή %s"
#: contrib/admin/views/main.py:470
#, python-format
@@ -535,17 +543,17 @@ msgstr "Είστε σίγουÏος;"
#: contrib/admin/views/main.py:533
#, python-format
msgid "Change history: %s"
-msgstr ""
+msgstr "ΙστοÏικό Αλλαγών: %s"
#: contrib/admin/views/main.py:565
#, python-format
msgid "Select %s"
-msgstr ""
+msgstr "Επιλογή %s"
#: contrib/admin/views/main.py:565
#, python-format
msgid "Select %s to change"
-msgstr ""
+msgstr "Επιλέξτε %s Ï€Ïος αλλαγή"
#: contrib/admin/views/doc.py:277 contrib/admin/views/doc.py:286
#: contrib/admin/views/doc.py:288 contrib/admin/views/doc.py:294
@@ -555,7 +563,7 @@ msgstr "ΑκέÏαιος"
#: contrib/admin/views/doc.py:278
msgid "Boolean (Either True or False)"
-msgstr "Boolean (Είτε Αλήθεια ή Ψέμα)"
+msgstr "Boolean (Είτε Αληθές ή Ψέυδές)"
#: contrib/admin/views/doc.py:279 contrib/admin/views/doc.py:296
#, python-format
@@ -580,7 +588,7 @@ msgstr "ΗλεκτÏονική διεÏθυνση"
#: contrib/admin/views/doc.py:284 contrib/admin/views/doc.py:287
msgid "File path"
-msgstr ""
+msgstr "Τοποθεσία ΑÏχείου"
#: contrib/admin/views/doc.py:285
msgid "Decimal number"
@@ -592,7 +600,7 @@ msgstr ""
#: contrib/admin/views/doc.py:292
msgid "Relation to parent model"
-msgstr ""
+msgstr "Σχέση με το γονεϊκό μοντέλο"
#: contrib/admin/views/doc.py:293
msgid "Phone number"
@@ -627,7 +635,7 @@ msgstr ""
#: contrib/admin/templates/registration/password_change_form.html:3
#: contrib/admin/templates/admin_doc/bookmarklets.html:3
msgid "Documentation"
-msgstr ""
+msgstr "ΤεκμηÏίωση"
#: contrib/admin/templates/admin/object_history.html:3
#: contrib/admin/templates/admin/change_list.html:5
@@ -667,11 +675,11 @@ msgstr "Home"
#: contrib/admin/templates/admin/object_history.html:5
#: contrib/admin/templates/admin/change_form.html:20
msgid "History"
-msgstr ""
+msgstr "ΙστοÏικό"
#: contrib/admin/templates/admin/object_history.html:18
msgid "Date/time"
-msgstr ""
+msgstr "ΗμεÏομηνία/ÎÏα"
#: contrib/admin/templates/admin/object_history.html:19
msgid "User"
@@ -679,7 +687,7 @@ msgstr "ΧÏήστης"
#: contrib/admin/templates/admin/object_history.html:20
msgid "Action"
-msgstr ""
+msgstr "ΔÏάση"
#: contrib/admin/templates/admin/object_history.html:26
msgid "DATE_WITH_TIME_FULL"
@@ -697,19 +705,19 @@ msgstr "ΔιαχειÏιστής ιστοσελίδας Django"
#: contrib/admin/templates/admin/base_site.html:7
msgid "Django administration"
-msgstr ""
+msgstr "ΔιαχείÏιση Django"
#: contrib/admin/templates/admin/500.html:4
msgid "Server error"
-msgstr ""
+msgstr "Σφάλμα Διακομιστή"
#: contrib/admin/templates/admin/500.html:6
msgid "Server error (500)"
-msgstr ""
+msgstr "Σφάλμα Διακομιστή (500)"
#: contrib/admin/templates/admin/500.html:9
msgid "Server Error <em>(500)</em>"
-msgstr ""
+msgstr "Σφάλμα Διακομιστή <em>(500)</em>"
#: contrib/admin/templates/admin/500.html:10
msgid ""
@@ -720,7 +728,7 @@ msgstr ""
#: contrib/admin/templates/admin/404.html:4
#: contrib/admin/templates/admin/404.html:8
msgid "Page not found"
-msgstr ""
+msgstr "Η σελίδα δε βÏέθηκε."
#: contrib/admin/templates/admin/404.html:10
msgid "We're sorry, but the requested page could not be found."
@@ -729,24 +737,24 @@ msgstr ""
#: contrib/admin/templates/admin/index.html:17
#, python-format
msgid "Models available in the %(name)s application."
-msgstr ""
+msgstr "Διαθέσιμα μοντέλα στην εφαÏμογή %(name)s."
#: contrib/admin/templates/admin/index.html:28
#: contrib/admin/templates/admin/change_form.html:15
msgid "Add"
-msgstr ""
+msgstr "ΠÏοσθήκη"
#: contrib/admin/templates/admin/index.html:34
msgid "Change"
-msgstr ""
+msgstr "ΕπεξεÏγασία"
#: contrib/admin/templates/admin/index.html:44
msgid "You don't have permission to edit anything."
-msgstr ""
+msgstr "Δεν έχετε άδεια να επεξεÏγαστείτε τίποτα."
#: contrib/admin/templates/admin/index.html:52
msgid "Recent Actions"
-msgstr ""
+msgstr "ΠÏόσφατες ΠÏάξεις"
#: contrib/admin/templates/admin/index.html:53
msgid "My Actions"
@@ -754,16 +762,16 @@ msgstr "Οι Ï€Ïάξεις μου"
#: contrib/admin/templates/admin/index.html:57
msgid "None available"
-msgstr ""
+msgstr "Κανένα διαθέσιμο"
#: contrib/admin/templates/admin/change_list.html:11
#, python-format
msgid "Add %(name)s"
-msgstr ""
+msgstr "ΠÏοσθήκη %(name)s"
#: contrib/admin/templates/admin/login.html:22
msgid "Have you <a href=\"/password_reset/\">forgotten your password</a>?"
-msgstr ""
+msgstr "<a href=\"/password_reset/\">Ξεχάσατε τον κωδικό σας;</a> "
#: contrib/admin/templates/admin/base.html:23
msgid "Welcome,"
@@ -772,7 +780,7 @@ msgstr "ΚαλωσήÏθατε,"
#: contrib/admin/templates/admin/delete_confirmation.html:9
#: contrib/admin/templates/admin/submit_line.html:3
msgid "Delete"
-msgstr ""
+msgstr "ΔιαγÏαφή"
#: contrib/admin/templates/admin/delete_confirmation.html:14
#, python-format
@@ -800,37 +808,37 @@ msgstr ""
#: contrib/admin/templates/admin/search_form.html:8
msgid "Go"
-msgstr ""
+msgstr "Πήγαινε"
#: contrib/admin/templates/admin/change_form.html:21
msgid "View on site"
-msgstr ""
+msgstr "ΠÏοβολή στην ιστοσελίδα"
#: contrib/admin/templates/admin/change_form.html:30
msgid "Please correct the error below."
msgid_plural "Please correct the errors below."
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "ΠαÏακαλώ διοÏθώστε το παÏακάτω λάθος."
+msgstr[1] "ΠαÏακαλώ διοÏθώστε τα παÏακάτω λάθη."
#: contrib/admin/templates/admin/change_form.html:48
msgid "Ordering"
-msgstr ""
+msgstr "ΣειÏά"
#: contrib/admin/templates/admin/change_form.html:51
msgid "Order:"
-msgstr ""
+msgstr "ΣειÏά:"
#: contrib/admin/templates/admin/submit_line.html:4
msgid "Save as new"
-msgstr ""
+msgstr "Αποθήκευση καινοÏÏιου"
#: contrib/admin/templates/admin/submit_line.html:5
msgid "Save and add another"
-msgstr ""
+msgstr "Αποθήκευση και Ï€Ïοσθήκη καινοÏÏιου."
#: contrib/admin/templates/admin/submit_line.html:6
msgid "Save and continue editing"
-msgstr ""
+msgstr "Αποθήκευση και συνέχεια επεξεÏγασίας"
#: contrib/admin/templates/admin/submit_line.html:7
msgid "Save"
@@ -841,12 +849,12 @@ msgstr "Αποθήκευση"
#: contrib/admin/templates/registration/password_change_form.html:6
#: contrib/admin/templates/registration/password_change_form.html:10
msgid "Password change"
-msgstr ""
+msgstr "Αλλαγή ΚωδικοÏ"
#: contrib/admin/templates/registration/password_change_done.html:6
#: contrib/admin/templates/registration/password_change_done.html:10
msgid "Password change successful"
-msgstr ""
+msgstr "Αλλαγή ÎºÏ‰Î´Î¹ÎºÎ¿Ï ÎµÏ€Î¹Ï„Ï…Ï‡Î®Ï‚"
#: contrib/admin/templates/registration/password_change_done.html:12
msgid "Your password was changed."
@@ -857,7 +865,7 @@ msgstr "Ο κωδίκός σας άλλαξε."
#: contrib/admin/templates/registration/password_reset_form.html:10
#: contrib/admin/templates/registration/password_reset_done.html:4
msgid "Password reset"
-msgstr ""
+msgstr "ΕπαναφοÏά κωδικοÏ"
#: contrib/admin/templates/registration/password_reset_form.html:12
msgid ""
@@ -867,11 +875,11 @@ msgstr ""
#: contrib/admin/templates/registration/password_reset_form.html:16
msgid "E-mail address:"
-msgstr ""
+msgstr "E-mail διεÏθυνση:"
#: contrib/admin/templates/registration/password_reset_form.html:16
msgid "Reset my password"
-msgstr ""
+msgstr "ΕπαναφοÏά του ÎºÏ‰Î´Î¹ÎºÎ¿Ï Î¼Î¿Ï…"
#: contrib/admin/templates/registration/logged_out.html:8
msgid "Thanks for spending some quality time with the Web site today."
@@ -880,7 +888,7 @@ msgstr ""
#: contrib/admin/templates/registration/logged_out.html:10
msgid "Log in again"
-msgstr ""
+msgstr "Εισαγωγή ξανά"
#: contrib/admin/templates/registration/password_reset_done.html:6
#: contrib/admin/templates/registration/password_reset_done.html:10
@@ -909,11 +917,11 @@ msgstr "Îέος κωδικός:"
#: contrib/admin/templates/registration/password_change_form.html:21
msgid "Confirm password:"
-msgstr ""
+msgstr "Επιβεβαίωση κωδικοÏ"
#: contrib/admin/templates/registration/password_change_form.html:23
msgid "Change my password"
-msgstr ""
+msgstr "Αλλαγή του ÎºÏ‰Î´Î¹ÎºÎ¿Ï Î¼Î¿Ï…"
#: contrib/admin/templates/registration/password_reset_email.html:2
msgid "You're receiving this e-mail because you requested a password reset"
@@ -1003,19 +1011,19 @@ msgstr ""
#: contrib/admin/templates/widget/date_time.html:3
msgid "Date:"
-msgstr ""
+msgstr "Ημ/νία:"
#: contrib/admin/templates/widget/date_time.html:4
msgid "Time:"
-msgstr ""
+msgstr "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ÎÏα:"
#: contrib/admin/templates/widget/file.html:2
msgid "Currently:"
-msgstr ""
+msgstr "ΤÏέχον:"
#: contrib/admin/templates/widget/file.html:3
msgid "Change:"
-msgstr ""
+msgstr "Αλλαγή:"
#: contrib/redirects/models.py:7
msgid "redirect from"
@@ -1271,192 +1279,192 @@ msgstr "P"
#: utils/dates.py:6
msgid "Monday"
-msgstr ""
+msgstr "ΔευτέÏα"
#: utils/dates.py:6
msgid "Tuesday"
-msgstr ""
+msgstr "ΤÏίτη"
#: utils/dates.py:6
msgid "Wednesday"
-msgstr ""
+msgstr "ΤετάÏτη"
#: utils/dates.py:6
msgid "Thursday"
-msgstr ""
+msgstr "Πέμπτη"
#: utils/dates.py:6
msgid "Friday"
-msgstr ""
+msgstr "ΠαÏασκευή"
#: utils/dates.py:7
msgid "Saturday"
-msgstr ""
+msgstr "Σάββατο"
#: utils/dates.py:7
msgid "Sunday"
-msgstr ""
+msgstr "ΚυÏιακή"
#: utils/dates.py:14
msgid "January"
-msgstr ""
+msgstr "ΙανουάÏιος"
#: utils/dates.py:14
msgid "February"
-msgstr ""
+msgstr "ΦεβÏουάÏιος"
#: utils/dates.py:14 utils/dates.py:27
msgid "March"
-msgstr ""
+msgstr "ΜάÏτιος"
#: utils/dates.py:14 utils/dates.py:27
msgid "April"
-msgstr ""
+msgstr "ΑπÏίλιος"
#: utils/dates.py:14 utils/dates.py:27
msgid "May"
-msgstr ""
+msgstr "Μάιος"
#: utils/dates.py:14 utils/dates.py:27
msgid "June"
-msgstr ""
+msgstr "ΙοÏνιος"
#: utils/dates.py:15 utils/dates.py:27
msgid "July"
-msgstr ""
+msgstr "ΙοÏλιος"
#: utils/dates.py:15
msgid "August"
-msgstr ""
+msgstr "ΑÏγουστος"
#: utils/dates.py:15
msgid "September"
-msgstr ""
+msgstr "ΣεπτέμβÏιος"
#: utils/dates.py:15
msgid "October"
-msgstr ""
+msgstr "ΟκτώβÏιος"
#: utils/dates.py:15
msgid "November"
-msgstr ""
+msgstr "ÎοέμβÏιος"
#: utils/dates.py:16
msgid "December"
-msgstr ""
+msgstr "ΔεκέμβÏιος"
#: utils/dates.py:19
#, fuzzy
msgid "jan"
-msgstr "και"
+msgstr "Ιαν"
#: utils/dates.py:19
msgid "feb"
-msgstr ""
+msgstr "Φεβ"
#: utils/dates.py:19
msgid "mar"
-msgstr ""
+msgstr "ΜάÏ"
#: utils/dates.py:19
msgid "apr"
-msgstr ""
+msgstr "ΑπÏ"
#: utils/dates.py:19
msgid "may"
-msgstr ""
+msgstr "Μάι"
#: utils/dates.py:19
msgid "jun"
-msgstr ""
+msgstr "ΙοÏν"
#: utils/dates.py:20
msgid "jul"
-msgstr ""
+msgstr "ΙοÏλ"
#: utils/dates.py:20
msgid "aug"
-msgstr ""
+msgstr "ΑÏγ"
#: utils/dates.py:20
msgid "sep"
-msgstr ""
+msgstr "Σεπ"
#: utils/dates.py:20
msgid "oct"
-msgstr ""
+msgstr "Οκτ"
#: utils/dates.py:20
msgid "nov"
-msgstr ""
+msgstr "Îοέ"
#: utils/dates.py:20
msgid "dec"
-msgstr ""
+msgstr "Δεκ"
#: utils/dates.py:27
msgid "Jan."
-msgstr ""
+msgstr "Ιάν."
#: utils/dates.py:27
msgid "Feb."
-msgstr ""
+msgstr "Φεβ."
#: utils/dates.py:28
msgid "Aug."
-msgstr ""
+msgstr "ΑÏγ."
#: utils/dates.py:28
msgid "Sept."
-msgstr ""
+msgstr "Σεπτ."
#: utils/dates.py:28
msgid "Oct."
-msgstr ""
+msgstr "Οκτ."
#: utils/dates.py:28
msgid "Nov."
-msgstr ""
+msgstr "Îοέ."
#: utils/dates.py:28
msgid "Dec."
-msgstr ""
+msgstr "Δεκ."
#: utils/timesince.py:12
msgid "year"
msgid_plural "years"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "χÏόνος"
+msgstr[1] "χÏόνια"
#: utils/timesince.py:13
msgid "month"
msgid_plural "months"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "μήνας"
+msgstr[1] "μήνες"
#: utils/timesince.py:14
msgid "week"
msgid_plural "weeks"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "εβδομάδα"
+msgstr[1] "εβδομάδες"
#: utils/timesince.py:15
msgid "day"
msgid_plural "days"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "ημέÏα"
+msgstr[1] "ημέÏες"
#: utils/timesince.py:16
msgid "hour"
msgid_plural "hours"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "ÏŽÏα"
+msgstr[1] "ÏŽÏες"
#: utils/timesince.py:17
msgid "minute"
msgid_plural "minutes"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "λεπτό"
+msgstr[1] "λεπτά"
#: conf/global_settings.py:37
msgid "Bengali"
@@ -1630,7 +1638,7 @@ msgstr ""
#: core/validators.py:136
msgid "Enter a valid e-mail address."
-msgstr ""
+msgstr "Εισάγετε ένα σωστό e-mail."
#: core/validators.py:148
msgid ""
@@ -1745,12 +1753,12 @@ msgstr[1] ""
#: core/validators.py:362
#, python-format
msgid "Make sure your uploaded file is at least %s bytes big."
-msgstr "ΣιγουÏευτείτε ότι το αÏχείου που ανεβάζετε είναι %s bytes τουλάχιστον."
+msgstr "ΣιγουÏευτείτε ότι το αÏχείο που ανεβάζετε είναι %s bytes τουλάχιστον."
#: core/validators.py:363
#, python-format
msgid "Make sure your uploaded file is at most %s bytes big."
-msgstr ""
+msgstr "ΣιγουÏευτείτε ότι το αÏχείο που ανεβάζετε έχει μέγεθος μέχÏι %s bytes."
#: core/validators.py:376
msgid "The format for this field is wrong."
@@ -1827,7 +1835,7 @@ msgstr ""
#: db/models/fields/__init__.py:542 db/models/fields/__init__.py:553
#: forms/__init__.py:346
msgid "This field is required."
-msgstr ""
+msgstr "Αυτό το πεδίο είναι απαÏαίτητο"
#: db/models/fields/__init__.py:337
msgid "This value must be an integer."
@@ -1836,12 +1844,12 @@ msgstr ""
#: db/models/fields/__init__.py:369
#, fuzzy
msgid "This value must be either True or False."
-msgstr "Boolean (Είτε Αλήθεια ή Ψέμα)"
+msgstr "Boolean (Είτε Αληθές ή Ψευδές)"
#: db/models/fields/__init__.py:385
#, fuzzy
msgid "This field cannot be null."
-msgstr "Αυτό το πεδίο είναι άκυÏο"
+msgstr "Αυτό το πεδίο δεν μποÏεί να είναι κενό (null)"
#: db/models/fields/__init__.py:562
msgid "Enter a valid filename."
@@ -1850,7 +1858,7 @@ msgstr "Εισάγετε ένα έγκυÏο όνομα αÏχείου"
#: db/models/fields/related.py:43
#, python-format
msgid "Please enter a valid %s."
-msgstr ""
+msgstr "ΠαÏακαλώ εισάγετε ένα/μία έγκυÏο/η %s"
#: db/models/fields/related.py:579
#, fuzzy
diff --git a/django/conf/locale/el/LC_MESSAGES/djangojs.mo b/django/conf/locale/el/LC_MESSAGES/djangojs.mo
new file mode 100644
index 0000000000..7d43b315fe
--- /dev/null
+++ b/django/conf/locale/el/LC_MESSAGES/djangojs.mo
Binary files differ
diff --git a/django/conf/locale/el/LC_MESSAGES/djangojs.po b/django/conf/locale/el/LC_MESSAGES/djangojs.po
new file mode 100644
index 0000000000..545f9f8601
--- /dev/null
+++ b/django/conf/locale/el/LC_MESSAGES/djangojs.po
@@ -0,0 +1,109 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) 2006 and beyond.
+# This file is distributed under the same license as the Django package.
+# Orestis Markou <orestis@orestis.gr>, 2006.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2005-12-09 11:51+0100\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: Orestis Markou <orestis@orestis.gr>\n"
+"Language-Team: Greek\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=utf-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: contrib/admin/media/js/SelectFilter2.js:33
+#, perl-format
+msgid "Available %s"
+msgstr "Διαθέσιμο %s"
+
+#: contrib/admin/media/js/SelectFilter2.js:41
+msgid "Choose all"
+msgstr "Επιλογή Όλων"
+
+#: contrib/admin/media/js/SelectFilter2.js:46
+msgid "Add"
+msgstr "ΠÏοσθήκη"
+
+#: contrib/admin/media/js/SelectFilter2.js:48
+msgid "Remove"
+msgstr "ΑφαίÏεση"
+
+#: contrib/admin/media/js/SelectFilter2.js:53
+#, perl-format
+msgid "Chosen %s"
+msgstr "Επιλεχθέντα %s"
+
+#: contrib/admin/media/js/SelectFilter2.js:54
+msgid "Select your choice(s) and click "
+msgstr "Επιλέξτε και κάντε κλικ."
+
+#: contrib/admin/media/js/SelectFilter2.js:59
+msgid "Clear all"
+msgstr "ΚαθαÏισμός όλων"
+
+#: contrib/admin/media/js/dateparse.js:26
+#: contrib/admin/media/js/calendar.js:24
+msgid ""
+"January February March April May June July August September October November "
+"December"
+msgstr "ΙανουάÏιος ΦεβÏουάÏιος ΜάÏτιος ΑπÏίλιος Μάιος ΙοÏνιος ΙοÏλιος ΑÏγουστος ΣεπτέμβÏιος ΟκτώβÏιος ÎοέμβÏιος "
+"ΔεκέμβÏιος"
+
+#: contrib/admin/media/js/dateparse.js:27
+msgid "Sunday Monday Tuesday Wednesday Thursday Friday Saturday"
+msgstr "ΚυÏιακή ΔευτέÏα ΤÏίτη ΤετάÏτη Πέμπτη ΠαÏασκευή Σάββατο"
+
+#: contrib/admin/media/js/calendar.js:25
+msgid "S M T W T F S"
+msgstr "Κ Δ Τ Τ Π Π Σ"
+
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:45
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:80
+msgid "Now"
+msgstr "ΤώÏα"
+
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:48
+msgid "Clock"
+msgstr "Ρολόι"
+
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:77
+msgid "Choose a time"
+msgstr "Διαλέξτε ÏŽÏα"
+
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:81
+msgid "Midnight"
+msgstr "Μεσάνυχτα"
+
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:82
+msgid "6 a.m."
+msgstr "6 π.μ."
+
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:83
+msgid "Noon"
+msgstr "ΜεσημέÏι"
+
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:87
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:168
+msgid "Cancel"
+msgstr "ΆκυÏο"
+
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:111
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:162
+msgid "Today"
+msgstr "ΣήμεÏα"
+
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:114
+msgid "Calendar"
+msgstr "ΗμεÏολόγιο"
+
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:160
+msgid "Yesterday"
+msgstr "Χθες"
+
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:164
+msgid "Tomorrow"
+msgstr "ΑÏÏιο"
diff --git a/django/conf/locale/es_AR/LC_MESSAGES/django.mo b/django/conf/locale/es_AR/LC_MESSAGES/django.mo
index dd96bf99ce..a247bb2385 100644
--- a/django/conf/locale/es_AR/LC_MESSAGES/django.mo
+++ b/django/conf/locale/es_AR/LC_MESSAGES/django.mo
Binary files differ
diff --git a/django/conf/locale/es_AR/LC_MESSAGES/django.po b/django/conf/locale/es_AR/LC_MESSAGES/django.po
index e5169e9c4a..36cae2f7f2 100644
--- a/django/conf/locale/es_AR/LC_MESSAGES/django.po
+++ b/django/conf/locale/es_AR/LC_MESSAGES/django.po
@@ -8,8 +8,8 @@ msgid ""
msgstr ""
"Project-Id-Version: django\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2006-08-18 18:57-0300\n"
-"PO-Revision-Date: 2006-08-21 18:06-0300\n"
+"POT-Creation-Date: 2006-11-05 19:57-0300\n"
+"PO-Revision-Date: 2006-11-05 20:00-0300\n"
"Last-Translator: Ramiro Morales <rm0@gmx.net>\n"
"Language-Team: Spanish <es@li.org>\n"
"MIME-Version: 1.0\n"
@@ -165,10 +165,18 @@ msgstr "Fechas importantes"
msgid "Groups"
msgstr "Grupos"
-#: contrib/auth/models.py:256
+#: contrib/auth/models.py:258
msgid "message"
msgstr "mensaje"
+#: contrib/auth/forms.py:16
+msgid "The two password fields didn't match."
+msgstr "Los dos campos de contraseñas no coinciden entre si."
+
+#: contrib/auth/forms.py:24
+msgid "A user with that username already exists."
+msgstr "Ya existe un usuario con ese nombre."
+
#: contrib/auth/forms.py:52
msgid ""
"Your Web browser doesn't appear to have cookies enabled. Cookies are "
@@ -189,6 +197,24 @@ msgstr ""
msgid "This account is inactive."
msgstr "Esta cuenta está inactiva"
+#: contrib/auth/forms.py:84
+msgid ""
+"That e-mail address doesn't have an associated user account. Are you sure "
+"you've registered?"
+msgstr ""
+"Esa dirección de e-mail no está asociada a ninguna cuenta de usuario. ¿Está "
+"seguro de que ya se ha registrado?"
+
+#: contrib/auth/forms.py:116
+msgid "The two 'new password' fields didn't match."
+msgstr "Los dos campos 'nueva contraseña' no coinciden entre si."
+
+#: contrib/auth/forms.py:123
+msgid "Your old password was entered incorrectly. Please enter it again."
+msgstr ""
+"La antigua contraseña ingresada es incorrecta. Por favor ingrésela "
+"nuevamente."
+
#: contrib/redirects/models.py:7
msgid "redirect from"
msgstr "redirigir desde"
@@ -754,13 +780,13 @@ msgstr ""
msgid "Site administration"
msgstr "Sitio administrativo"
-#: contrib/admin/views/main.py:257 contrib/admin/views/auth.py:14
+#: contrib/admin/views/main.py:257 contrib/admin/views/auth.py:18
#, python-format
msgid "The %(name)s \"%(obj)s\" was added successfully."
-msgstr "Se agregó con éxito el %(name)s \"%(obj)s\"."
+msgstr "Se agregó con éxito %(name)s \"%(obj)s\"."
#: contrib/admin/views/main.py:261 contrib/admin/views/main.py:347
-#: contrib/admin/views/auth.py:19
+#: contrib/admin/views/auth.py:23
msgid "You may edit it again below."
msgstr "Puede modificarlo nuevamente abajo."
@@ -780,7 +806,7 @@ msgid "Added %s."
msgstr "Agregado %s."
#: contrib/admin/views/main.py:335 contrib/admin/views/main.py:337
-#: contrib/admin/views/main.py:339
+#: contrib/admin/views/main.py:339 db/models/manipulators.py:306
msgid "and"
msgstr "y"
@@ -801,15 +827,14 @@ msgstr "No ha modificado ningún campo."
#: contrib/admin/views/main.py:345
#, python-format
msgid "The %(name)s \"%(obj)s\" was changed successfully."
-msgstr "Se modificó con éxito el %(name)s \"%(obj)s."
+msgstr "Se modificó con éxito %(name)s \"%(obj)s."
#: contrib/admin/views/main.py:353
#, python-format
msgid ""
"The %(name)s \"%(obj)s\" was added successfully. You may edit it again below."
msgstr ""
-"Se agregó con éxito el %(name)s \"%(obj)s. Puede modificarlo nuevamente "
-"abajo."
+"Se agregó con éxito %(name)s \"%(obj)s. Puede modificarlo nuevamente abajo."
#: contrib/admin/views/main.py:391
#, python-format
@@ -829,7 +854,7 @@ msgstr "Uno o más %(fieldname)s en %(name)s:"
#: contrib/admin/views/main.py:511
#, python-format
msgid "The %(name)s \"%(obj)s\" was deleted successfully."
-msgstr "Se eliminó con éxito el %(name)s \"%(obj)s\"."
+msgstr "Se eliminó con éxito %(name)s \"%(obj)s\"."
#: contrib/admin/views/main.py:514
msgid "Are you sure?"
@@ -850,7 +875,7 @@ msgstr "Seleccione %s"
msgid "Select %s to change"
msgstr "Seleccione %s a modificar"
-#: contrib/admin/views/main.py:756
+#: contrib/admin/views/main.py:758
msgid "Database error"
msgstr "Error de base de datos"
@@ -977,12 +1002,12 @@ msgstr "Estado de los EEUU (dos letras mayúsculas)"
msgid "XML text"
msgstr "Texto XML"
-#: contrib/admin/views/doc.py:339
+#: contrib/admin/views/doc.py:343
#, python-format
msgid "%s does not appear to be a urlpattern object"
msgstr "%s no parece ser un objeto urlpattern"
-#: contrib/admin/views/auth.py:25
+#: contrib/admin/views/auth.py:29
msgid "Add user"
msgstr "Agregar usuario"
@@ -1181,10 +1206,6 @@ msgstr "Página no encontrada"
msgid "We're sorry, but the requested page could not be found."
msgstr "Lo sentimos, pero no se encuentra la página solicitada."
-#: contrib/admin/templates/admin/login.html:22
-msgid "Have you <a href=\"/password_reset/\">forgotten your password</a>?"
-msgstr "¿Ha <a href=\"/password_reset/\">olvidado su contraseña</a>?"
-
#: contrib/admin/templates/admin/filters.html:4
msgid "Filter"
msgstr "Filtrar"
@@ -1726,123 +1747,134 @@ msgid "Argentinean Spanish"
msgstr "Español Argentino"
#: conf/global_settings.py:49
+msgid "Finnish"
+msgstr "Finlandés"
+
+#: conf/global_settings.py:50
msgid "French"
msgstr "Francés"
-#: conf/global_settings.py:50
+#: conf/global_settings.py:51
msgid "Galician"
msgstr "Gallego"
-#: conf/global_settings.py:51
+#: conf/global_settings.py:52
msgid "Hungarian"
msgstr "Húngaro"
-#: conf/global_settings.py:52
+#: conf/global_settings.py:53
msgid "Hebrew"
msgstr "Hebreo"
-#: conf/global_settings.py:53
+#: conf/global_settings.py:54
msgid "Icelandic"
msgstr "Islandés"
-#: conf/global_settings.py:54
+#: conf/global_settings.py:55
msgid "Italian"
msgstr "Italiano"
-#: conf/global_settings.py:55
+#: conf/global_settings.py:56
msgid "Japanese"
msgstr "Japonés"
-#: conf/global_settings.py:56
+#: conf/global_settings.py:57
msgid "Dutch"
msgstr "Holandés"
-#: conf/global_settings.py:57
+#: conf/global_settings.py:58
msgid "Norwegian"
msgstr "Noruego"
-#: conf/global_settings.py:58
+#: conf/global_settings.py:59
+msgid "Polish"
+msgstr "Polaco"
+
+#: conf/global_settings.py:60
msgid "Brazilian"
msgstr "Brasileño"
-#: conf/global_settings.py:59
+#: conf/global_settings.py:61
msgid "Romanian"
msgstr "Rumano"
-#: conf/global_settings.py:60
+#: conf/global_settings.py:62
msgid "Russian"
msgstr "Ruso"
-#: conf/global_settings.py:61
+#: conf/global_settings.py:63
msgid "Slovak"
msgstr "Eslovaco"
-#: conf/global_settings.py:62
+#: conf/global_settings.py:64
msgid "Slovenian"
msgstr "Esloveno"
-#: conf/global_settings.py:63
+#: conf/global_settings.py:65
msgid "Serbian"
msgstr "Serbio"
-#: conf/global_settings.py:64
+#: conf/global_settings.py:66
msgid "Swedish"
msgstr "Sueco"
-#: conf/global_settings.py:65
+#: conf/global_settings.py:67
msgid "Tamil"
msgstr "Tamil"
-#: conf/global_settings.py:66
+#: conf/global_settings.py:68
+msgid "Turkish"
+msgstr "Turco"
+
+#: conf/global_settings.py:69
msgid "Ukrainian"
msgstr "Ucraniano"
-#: conf/global_settings.py:67
+#: conf/global_settings.py:70
msgid "Simplified Chinese"
msgstr "Chino simplificado"
-#: conf/global_settings.py:68
+#: conf/global_settings.py:71
msgid "Traditional Chinese"
msgstr "Chino tradicional"
-#: db/models/manipulators.py:302
+#: db/models/manipulators.py:305
#, python-format
msgid "%(object)s with this %(type)s already exists for the given %(field)s."
-msgstr ""
-"Ya existen %(object)s con este %(type)s para el %(field)s especificado."
+msgstr "Ya existe un(a) %(object)s con este/a %(type)s para %(field)s."
-#: db/models/fields/__init__.py:40
+#: db/models/fields/__init__.py:41
#, python-format
msgid "%(optname)s with this %(fieldname)s already exists."
msgstr "Ya existe %(optname)s con este %(fieldname)s."
-#: db/models/fields/__init__.py:114 db/models/fields/__init__.py:265
-#: db/models/fields/__init__.py:551 db/models/fields/__init__.py:562
-#: forms/__init__.py:346
+#: db/models/fields/__init__.py:115 db/models/fields/__init__.py:266
+#: db/models/fields/__init__.py:569 db/models/fields/__init__.py:580
+#: forms/__init__.py:347
msgid "This field is required."
msgstr "Este campo es obligatorio."
-#: db/models/fields/__init__.py:340
+#: db/models/fields/__init__.py:349
msgid "This value must be an integer."
msgstr "Este valor debe ser un número entero."
-#: db/models/fields/__init__.py:372
+#: db/models/fields/__init__.py:381
msgid "This value must be either True or False."
msgstr "Este valor debe ser True o False."
-#: db/models/fields/__init__.py:388
+#: db/models/fields/__init__.py:397
msgid "This field cannot be null."
msgstr "Este campo no puede ser nulo."
-#: db/models/fields/__init__.py:415 core/validators.py:127
+#: db/models/fields/__init__.py:424 core/validators.py:146
msgid "Enter a valid date in YYYY-MM-DD format."
msgstr "Introduzca una fecha válida en formato AAAA-MM-DD."
-#: db/models/fields/__init__.py:477 core/validators.py:135
+#: db/models/fields/__init__.py:488 core/validators.py:155
msgid "Enter a valid date/time in YYYY-MM-DD HH:MM format."
msgstr "Introduzca una fecha/hora válida en formato YYYY-MM-DD HH:MM."
-#: db/models/fields/__init__.py:571
+#: db/models/fields/__init__.py:589
msgid "Enter a valid filename."
msgstr "Introduzca un nombre de achivo válido"
@@ -1859,7 +1891,8 @@ msgstr " Separe múltiples IDs con comas."
msgid ""
"Hold down \"Control\", or \"Command\" on a Mac, to select more than one."
msgstr ""
-"Pulse \"Control\", o \"Command\" en un Mac, para seleccionar más de uno."
+"Mantenga presionada \"Control\" (\"Command\" en un Mac) para seleccionar más "
+"de uno."
#: db/models/fields/related.py:664
#, python-format
@@ -1873,42 +1906,42 @@ msgstr[1] ""
"Por favor, introduzca IDs de %(self)s válidos. Los valores %(value)r no son "
"válidos."
-#: forms/__init__.py:381
+#: forms/__init__.py:382
#, python-format
msgid "Ensure your text is less than %s character."
msgid_plural "Ensure your text is less than %s characters."
msgstr[0] "Asegúrese de que su texto tiene menos de %s carácter."
msgstr[1] "Asegúrese de que su texto tiene menos de %s caracteres."
-#: forms/__init__.py:386
+#: forms/__init__.py:387
msgid "Line breaks are not allowed here."
msgstr "No se permiten saltos de línea."
-#: forms/__init__.py:487 forms/__init__.py:560 forms/__init__.py:599
+#: forms/__init__.py:488 forms/__init__.py:561 forms/__init__.py:600
#, python-format
msgid "Select a valid choice; '%(data)s' is not in %(choices)s."
msgstr "Seleccione una opción válida; '%(data)s' no está en %(choices)s."
-#: forms/__init__.py:661 core/validators.py:151 core/validators.py:379
+#: forms/__init__.py:662 core/validators.py:172 core/validators.py:401
msgid "No file was submitted. Check the encoding type on the form."
msgstr ""
"No se envió un archivo. Verifique el tipo de codificación en el formulario."
-#: forms/__init__.py:663
+#: forms/__init__.py:664
msgid "The submitted file is empty."
msgstr "El archivo enviado está vacío."
-#: forms/__init__.py:719
+#: forms/__init__.py:720
msgid "Enter a whole number between -32,768 and 32,767."
-msgstr "Introduzca un número entero entre -32,768 y 32,767."
+msgstr "Introduzca un número entero entre -32.768 y 32.767."
-#: forms/__init__.py:729
+#: forms/__init__.py:730
msgid "Enter a positive number."
msgstr "Introduzca un número positivo."
-#: forms/__init__.py:739
+#: forms/__init__.py:740
msgid "Enter a whole number between 0 and 32,767."
-msgstr "Introduzca un número entero entre 0 y 32,767."
+msgstr "Introduzca un número entero entre 0 y 32.767."
#: core/validators.py:63
msgid "This value must contain only letters, numbers and underscores."
@@ -1919,8 +1952,13 @@ msgid ""
"This value must contain only letters, numbers, underscores, dashes or "
"slashes."
msgstr ""
-"Este valor debe contener sólo letras, números, guiones bajos, barras (/) o "
-"slashes."
+"Este valor debe contener sólo letras, números, guiones bajos, guiones o "
+"barras (/)"
+
+#: core/validators.py:71
+msgid "This value must contain only letters, numbers, underscores or hyphens."
+msgstr ""
+"Este valor debe contener sólo letras, números, guiones bajos o guiones."
#: core/validators.py:75
msgid "Uppercase letters are not allowed here."
@@ -1962,15 +2000,24 @@ msgstr "Introduzca un número entero."
msgid "Only alphabetical characters are allowed here."
msgstr "Sólo se admiten caracteres alfabéticos."
-#: core/validators.py:131
+#: core/validators.py:138
+msgid "Year must be 1900 or later."
+msgstr "El año debe ser 1900 o posterior."
+
+#: core/validators.py:142
+#, python-format
+msgid "Invalid date: %s."
+msgstr "Fecha no válida: %s."
+
+#: core/validators.py:151
msgid "Enter a valid time in HH:MM format."
msgstr "Introduzca una hora válida en formato HH:MM."
-#: core/validators.py:139
+#: core/validators.py:160
msgid "Enter a valid e-mail address."
msgstr "Introduzca una dirección de correo electrónico válida"
-#: core/validators.py:155
+#: core/validators.py:176
msgid ""
"Upload a valid image. The file you uploaded was either not an image or a "
"corrupted image."
@@ -1978,28 +2025,28 @@ msgstr ""
"Envíe una imagen válida. El archivo que ha enviado no era una imagen o se "
"trataba de una imagen corrupta."
-#: core/validators.py:162
+#: core/validators.py:183
#, python-format
msgid "The URL %s does not point to a valid image."
msgstr "La URL %s no apunta a una imagen válida."
-#: core/validators.py:166
+#: core/validators.py:187
#, python-format
msgid "Phone numbers must be in XXX-XXX-XXXX format. \"%s\" is invalid."
msgstr ""
-"Los números de teléfono deben guardar el formato XXX-XXX-XXXX format. \"%s\" "
-"no es válido."
+"Los números telefónicos deben respetar el formato XXX-XXX-XXXX. \"%s\" no es "
+"válido."
-#: core/validators.py:174
+#: core/validators.py:195
#, python-format
msgid "The URL %s does not point to a valid QuickTime video."
msgstr "La URL %s no apunta a un vídeo QuickTime válido."
-#: core/validators.py:178
+#: core/validators.py:199
msgid "A valid URL is required."
msgstr "Se precisa una URL válida."
-#: core/validators.py:192
+#: core/validators.py:213
#, python-format
msgid ""
"Valid HTML is required. Specific errors are:\n"
@@ -2008,69 +2055,69 @@ msgstr ""
"Se precisa HTML válido. Los errores específicos son:\n"
"%s"
-#: core/validators.py:199
+#: core/validators.py:220
#, python-format
msgid "Badly formed XML: %s"
msgstr "XML mal formado: %s"
-#: core/validators.py:209
+#: core/validators.py:230
#, python-format
msgid "Invalid URL: %s"
msgstr "URL no válida: %s"
-#: core/validators.py:213 core/validators.py:215
+#: core/validators.py:234 core/validators.py:236
#, python-format
msgid "The URL %s is a broken link."
msgstr "La URL %s es un enlace roto."
-#: core/validators.py:221
+#: core/validators.py:242
msgid "Enter a valid U.S. state abbreviation."
msgstr "Introduzca una abreviatura válida de estado de los EEUU."
-#: core/validators.py:236
+#: core/validators.py:256
#, python-format
msgid "Watch your mouth! The word %s is not allowed here."
msgid_plural "Watch your mouth! The words %s are not allowed here."
msgstr[0] "¡Vigila tu boca! Aquí no admitimos la palabra %s."
msgstr[1] "¡Vigila tu boca! Aquí no admitimos las palabras %s."
-#: core/validators.py:243
+#: core/validators.py:263
#, python-format
msgid "This field must match the '%s' field."
msgstr "Este campo debe concordar con el campo '%s'."
-#: core/validators.py:262
+#: core/validators.py:282
msgid "Please enter something for at least one field."
msgstr "Por favor, introduzca algo en al menos un campo."
-#: core/validators.py:271 core/validators.py:282
+#: core/validators.py:291 core/validators.py:302
msgid "Please enter both fields or leave them both empty."
msgstr "Por favor, rellene ambos campos o deje ambos vacíos."
-#: core/validators.py:289
+#: core/validators.py:309
#, python-format
msgid "This field must be given if %(field)s is %(value)s"
msgstr "Se debe proporcionar este campo si %(field)s es %(value)s"
-#: core/validators.py:301
+#: core/validators.py:321
#, python-format
msgid "This field must be given if %(field)s is not %(value)s"
msgstr "Se debe proporcionar este campo si %(field)s no es %(value)s"
-#: core/validators.py:320
+#: core/validators.py:340
msgid "Duplicate values are not allowed."
msgstr "No se admiten valores duplicados."
-#: core/validators.py:343
+#: core/validators.py:363
#, python-format
msgid "This value must be a power of %s."
msgstr "Este valor debe ser una potencia de %s."
-#: core/validators.py:354
+#: core/validators.py:374
msgid "Please enter a valid decimal number."
msgstr "Por favor, introduzca un número decimal válido."
-#: core/validators.py:356
+#: core/validators.py:378
#, python-format
msgid "Please enter a valid decimal number with at most %s total digit."
msgid_plural ""
@@ -2082,7 +2129,7 @@ msgstr[1] ""
"Por favor, introduzca un número decimal válido con un maximo de %s dígitos "
"en total."
-#: core/validators.py:359
+#: core/validators.py:381
#, python-format
msgid ""
"Please enter a valid decimal number with a whole part of at most %s digit."
@@ -2095,7 +2142,7 @@ msgstr[1] ""
"Por favor, introduzca un número decimal válido con un máximo de %s dígitos "
"enteros."
-#: core/validators.py:362
+#: core/validators.py:384
#, python-format
msgid "Please enter a valid decimal number with at most %s decimal place."
msgid_plural ""
@@ -2107,30 +2154,30 @@ msgstr[1] ""
"Por favor, introduzca un número decimal válido con un máximo de %s "
"posiciones decimales."
-#: core/validators.py:372
+#: core/validators.py:394
#, python-format
msgid "Make sure your uploaded file is at least %s bytes big."
msgstr "Asegúrese de que el archivo que envía tiene al menos %s bytes."
-#: core/validators.py:373
+#: core/validators.py:395
#, python-format
msgid "Make sure your uploaded file is at most %s bytes big."
msgstr "Asegúrese de que el archivo que envía tiene como máximo %s bytes."
-#: core/validators.py:390
+#: core/validators.py:412
msgid "The format for this field is wrong."
msgstr "El formato de este campo es incorrecto."
-#: core/validators.py:405
+#: core/validators.py:427
msgid "This field is invalid."
msgstr "Este campo no es válido."
-#: core/validators.py:441
+#: core/validators.py:463
#, python-format
msgid "Could not retrieve anything from %s."
msgstr "No pude obtener nada de %s."
-#: core/validators.py:444
+#: core/validators.py:466
#, python-format
msgid ""
"The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'."
@@ -2138,7 +2185,7 @@ msgstr ""
"La URL %(url)s devolvió la cabecera Content-Type '%(contenttype)s', que no "
"es válida."
-#: core/validators.py:477
+#: core/validators.py:499
#, python-format
msgid ""
"Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with "
@@ -2147,7 +2194,7 @@ msgstr ""
"Por favor, cierre la etiqueta %(tag)s de la línea %(line)s. (La línea "
"empieza por \"%(start)s\".)"
-#: core/validators.py:481
+#: core/validators.py:503
#, python-format
msgid ""
"Some text starting on line %(line)s is not allowed in that context. (Line "
@@ -2156,7 +2203,7 @@ msgstr ""
"Parte del texto que comienza en la línea %(line)s no está permitido en ese "
"contexto. (La línea empieza por \"%(start)s\".)"
-#: core/validators.py:486
+#: core/validators.py:508
#, python-format
msgid ""
"\"%(attr)s\" on line %(line)s is an invalid attribute. (Line starts with \"%"
@@ -2165,7 +2212,7 @@ msgstr ""
"El \"%(attr)s\" de la línea %(line)s no es un atributo válido. (La línea "
"empieza por \"%(start)s\".)"
-#: core/validators.py:491
+#: core/validators.py:513
#, python-format
msgid ""
"\"<%(tag)s>\" on line %(line)s is an invalid tag. (Line starts with \"%"
@@ -2174,7 +2221,7 @@ msgstr ""
"La \"<%(tag)s>\" de la línea %(line)s no es una etiqueta válida. (La línea "
"empieza por \"%(start)s\".)"
-#: core/validators.py:495
+#: core/validators.py:517
#, python-format
msgid ""
"A tag on line %(line)s is missing one or more required attributes. (Line "
@@ -2183,7 +2230,7 @@ msgstr ""
"A una etiqueta de la línea %(line)s le faltan uno o más atributos "
"requeridos. (La línea empieza por \"%(start)s\".)"
-#: core/validators.py:500
+#: core/validators.py:522
#, python-format
msgid ""
"The \"%(attr)s\" attribute on line %(line)s has an invalid value. (Line "
@@ -2199,17 +2246,20 @@ msgstr "si,no,tal vez"
#: views/generic/create_update.py:43
#, python-format
msgid "The %(verbose_name)s was created successfully."
-msgstr "Se creó con éxito el %(verbose_name)."
+msgstr "Se creó con éxito %(verbose_name)."
#: views/generic/create_update.py:117
#, python-format
msgid "The %(verbose_name)s was updated successfully."
-msgstr "Se actualizó con éxito el %(verbose_name)s."
+msgstr "Se actualizó con éxito %(verbose_name)s."
#: views/generic/create_update.py:184
#, python-format
msgid "The %(verbose_name)s was deleted."
-msgstr "Se eliminó el %(verbose_name)s."
+msgstr "Se eliminó %(verbose_name)s."
+
+#~ msgid "Have you <a href=\"/password_reset/\">forgotten your password</a>?"
+#~ msgstr "¿Ha <a href=\"/password_reset/\">olvidado su contraseña</a>?"
#~ msgid "%(content_type_name)s"
#~ msgstr "tipos de contenido"
diff --git a/django/conf/locale/es_AR/LC_MESSAGES/djangojs.mo b/django/conf/locale/es_AR/LC_MESSAGES/djangojs.mo
index f24c9a12d0..3f89e3e33f 100644
--- a/django/conf/locale/es_AR/LC_MESSAGES/djangojs.mo
+++ b/django/conf/locale/es_AR/LC_MESSAGES/djangojs.mo
Binary files differ
diff --git a/django/conf/locale/es_AR/LC_MESSAGES/djangojs.po b/django/conf/locale/es_AR/LC_MESSAGES/djangojs.po
index 49d3856d3c..1865de9450 100644
--- a/django/conf/locale/es_AR/LC_MESSAGES/djangojs.po
+++ b/django/conf/locale/es_AR/LC_MESSAGES/djangojs.po
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Django JavaScript 1.0\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2006-06-19 12:15-0300\n"
+"POT-Creation-Date: 2006-09-25 15:09-0300\n"
"PO-Revision-Date: 2006-05-16 10:20-0300\n"
"Last-Translator: Ramiro Morales <rm0@gmx.net>\n"
"MIME-Version: 1.0\n"
@@ -39,7 +39,7 @@ msgstr "%s elegidos"
#: contrib/admin/media/js/SelectFilter2.js:54
msgid "Select your choice(s) and click "
-msgstr "Haga sus elecciones y haga click en "
+msgstr "Seleccione los items a agregar y haga click en "
#: contrib/admin/media/js/SelectFilter2.js:59
msgid "Clear all"
@@ -51,7 +51,7 @@ msgid ""
"January February March April May June July August September October November "
"December"
msgstr ""
-"Enero Febrero Marzo Abril Mayo Junio Julio Agosto Septiembre Octubre "
+"Enero Febrero Marzo Abril Mayo Junio Julio Agosto Setiembre Octubre "
"Noviembre Diciembre"
#: contrib/admin/media/js/dateparse.js:33
@@ -71,49 +71,49 @@ msgstr "Mostrar"
msgid "Hide"
msgstr "Ocultar"
-#: contrib/admin/media/js/admin/DateTimeShortcuts.js:45
-#: contrib/admin/media/js/admin/DateTimeShortcuts.js:89
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:47
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:81
msgid "Now"
msgstr "Ahora"
-#: contrib/admin/media/js/admin/DateTimeShortcuts.js:48
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:51
msgid "Clock"
msgstr "Reloj"
-#: contrib/admin/media/js/admin/DateTimeShortcuts.js:86
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:78
msgid "Choose a time"
msgstr "Elija una hora"
-#: contrib/admin/media/js/admin/DateTimeShortcuts.js:90
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:82
msgid "Midnight"
msgstr "Medianoche"
-#: contrib/admin/media/js/admin/DateTimeShortcuts.js:91
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:83
msgid "6 a.m."
msgstr "6 a.m."
-#: contrib/admin/media/js/admin/DateTimeShortcuts.js:92
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:84
msgid "Noon"
msgstr "Mediodía"
-#: contrib/admin/media/js/admin/DateTimeShortcuts.js:96
-#: contrib/admin/media/js/admin/DateTimeShortcuts.js:187
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:88
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:183
msgid "Cancel"
msgstr "Cancelar"
-#: contrib/admin/media/js/admin/DateTimeShortcuts.js:120
-#: contrib/admin/media/js/admin/DateTimeShortcuts.js:181
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:128
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:177
msgid "Today"
msgstr "Hoy"
-#: contrib/admin/media/js/admin/DateTimeShortcuts.js:123
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:132
msgid "Calendar"
msgstr "Calendario"
-#: contrib/admin/media/js/admin/DateTimeShortcuts.js:179
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:175
msgid "Yesterday"
msgstr "Ayer"
-#: contrib/admin/media/js/admin/DateTimeShortcuts.js:183
+#: contrib/admin/media/js/admin/DateTimeShortcuts.js:179
msgid "Tomorrow"
msgstr "Mañana"
diff --git a/django/conf/project_template/urls.py b/django/conf/project_template/urls.py
index 4483014173..402dd6536b 100644
--- a/django/conf/project_template/urls.py
+++ b/django/conf/project_template/urls.py
@@ -2,7 +2,7 @@ from django.conf.urls.defaults import *
urlpatterns = patterns('',
# Example:
- # (r'^{{ project_name }}/', include('{{ project_name }}.apps.foo.urls.foo')),
+ # (r'^{{ project_name }}/', include('{{ project_name }}.foo.urls')),
# Uncomment this for admin:
# (r'^admin/', include('django.contrib.admin.urls')),
diff --git a/django/contrib/admin/media/js/admin/DateTimeShortcuts.js b/django/contrib/admin/media/js/admin/DateTimeShortcuts.js
index 77c536b865..b1504fc819 100644
--- a/django/contrib/admin/media/js/admin/DateTimeShortcuts.js
+++ b/django/contrib/admin/media/js/admin/DateTimeShortcuts.js
@@ -44,7 +44,7 @@ var DateTimeShortcuts = {
var shortcuts_span = document.createElement('span');
inp.parentNode.insertBefore(shortcuts_span, inp.nextSibling);
var now_link = document.createElement('a');
- now_link.setAttribute('href', "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", new Date().getHourMinute());");
+ now_link.setAttribute('href', "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", new Date().getHourMinuteSecond());");
now_link.appendChild(document.createTextNode(gettext('Now')));
var clock_link = document.createElement('a');
clock_link.setAttribute('href', 'javascript:DateTimeShortcuts.openClock(' + num + ');');
@@ -80,10 +80,10 @@ var DateTimeShortcuts = {
quickElement('h2', clock_box, gettext('Choose a time'));
time_list = quickElement('ul', clock_box, '');
time_list.className = 'timelist';
- quickElement("a", quickElement("li", time_list, ""), gettext("Now"), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", new Date().getHourMinute());")
- quickElement("a", quickElement("li", time_list, ""), gettext("Midnight"), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", '00:00');")
- quickElement("a", quickElement("li", time_list, ""), gettext("6 a.m."), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", '06:00');")
- quickElement("a", quickElement("li", time_list, ""), gettext("Noon"), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", '12:00');")
+ quickElement("a", quickElement("li", time_list, ""), gettext("Now"), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", new Date().getHourMinuteSecond());")
+ quickElement("a", quickElement("li", time_list, ""), gettext("Midnight"), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", '00:00:00');")
+ quickElement("a", quickElement("li", time_list, ""), gettext("6 a.m."), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", '06:00:00');")
+ quickElement("a", quickElement("li", time_list, ""), gettext("Noon"), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", '12:00:00');")
cancel_p = quickElement('p', clock_box, '');
cancel_p.className = 'calendar-cancel';
diff --git a/django/contrib/admin/media/js/core.js b/django/contrib/admin/media/js/core.js
index d35bd29c1c..a17ac8a4d2 100644
--- a/django/contrib/admin/media/js/core.js
+++ b/django/contrib/admin/media/js/core.js
@@ -119,6 +119,10 @@ Date.prototype.getTwoDigitMinute = function() {
return (this.getMinutes() < 10) ? '0' + this.getMinutes() : this.getMinutes();
}
+Date.prototype.getTwoDigitSecond = function() {
+ return (this.getSeconds() < 10) ? '0' + this.getSeconds() : this.getSeconds();
+}
+
Date.prototype.getISODate = function() {
return this.getCorrectYear() + '-' + this.getTwoDigitMonth() + '-' + this.getTwoDigitDate();
}
@@ -127,6 +131,10 @@ Date.prototype.getHourMinute = function() {
return this.getTwoDigitHour() + ':' + this.getTwoDigitMinute();
}
+Date.prototype.getHourMinuteSecond = function() {
+ return this.getTwoDigitHour() + ':' + this.getTwoDigitMinute() + ':' + this.getTwoDigitSecond();
+}
+
// ----------------------------------------------------------------------------
// String object extensions
// ----------------------------------------------------------------------------
diff --git a/django/contrib/admin/templates/admin/search_form.html b/django/contrib/admin/templates/admin/search_form.html
index d9126c3ec5..445cca3089 100644
--- a/django/contrib/admin/templates/admin/search_form.html
+++ b/django/contrib/admin/templates/admin/search_form.html
@@ -7,7 +7,7 @@
<input type="text" size="40" name="{{ search_var }}" value="{{ cl.query|escape }}" id="searchbar" />
<input type="submit" value="{% trans 'Go' %}" />
{% if show_result_count %}
- <span class="small quiet">{% blocktrans count cl.result_count as counter %}1 result{% plural %}{{ counter }} results{% endblocktrans %} (<a href="?">{% blocktrans with cl.full_result_count as full_result_count %}{{ full_result_count }} total{% endblocktrans %}</a>)</span>
+ <span class="small quiet">{% blocktrans count cl.result_count as counter %}1 result{% plural %}{{ counter }} results{% endblocktrans %} (<a href="?{% if cl.is_popup %}pop=1{% endif %}">{% blocktrans with cl.full_result_count as full_result_count %}{{ full_result_count }} total{% endblocktrans %}</a>)</span>
{% endif %}
{% for pair in cl.params.items %}
{% ifnotequal pair.0 search_var %}<input type="hidden" name="{{ pair.0|escape }}" value="{{ pair.1|escape }}"/>{% endifnotequal %}
diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py
index 324841a669..c9cff0e374 100644
--- a/django/contrib/admin/views/main.py
+++ b/django/contrib/admin/views/main.py
@@ -226,7 +226,7 @@ index = staff_member_required(never_cache(index))
def add_stage(request, app_label, model_name, show_delete=False, form_url='', post_url=None, post_url_continue='../%s/', object_id_override=None):
model = models.get_model(app_label, model_name)
if model is None:
- raise Http404, "App %r, model %r, not found" % (app_label, model_name)
+ raise Http404("App %r, model %r, not found" % (app_label, model_name))
opts = model._meta
if not request.user.has_perm(app_label + '.' + opts.get_add_permission()):
@@ -302,7 +302,7 @@ def change_stage(request, app_label, model_name, object_id):
model = models.get_model(app_label, model_name)
object_id = unquote(object_id)
if model is None:
- raise Http404, "App %r, model %r, not found" % (app_label, model_name)
+ raise Http404("App %r, model %r, not found" % (app_label, model_name))
opts = model._meta
if not request.user.has_perm(app_label + '.' + opts.get_change_permission()):
@@ -313,8 +313,8 @@ def change_stage(request, app_label, model_name, object_id):
try:
manipulator = model.ChangeManipulator(object_id)
- except ObjectDoesNotExist:
- raise Http404
+ except model.DoesNotExist:
+ raise Http404('%s object with primary key %r does not exist' % (model_name, escape(object_id)))
if request.POST:
new_data = request.POST.copy()
@@ -490,7 +490,7 @@ def delete_stage(request, app_label, model_name, object_id):
model = models.get_model(app_label, model_name)
object_id = unquote(object_id)
if model is None:
- raise Http404, "App %r, model %r, not found" % (app_label, model_name)
+ raise Http404("App %r, model %r, not found" % (app_label, model_name))
opts = model._meta
if not request.user.has_perm(app_label + '.' + opts.get_delete_permission()):
raise PermissionDenied
@@ -527,7 +527,7 @@ def history(request, app_label, model_name, object_id):
model = models.get_model(app_label, model_name)
object_id = unquote(object_id)
if model is None:
- raise Http404, "App %r, model %r, not found" % (app_label, model_name)
+ raise Http404("App %r, model %r, not found" % (app_label, model_name))
action_list = LogEntry.objects.filter(object_id=object_id,
content_type__id__exact=ContentType.objects.get_for_model(model).id).select_related().order_by('action_time')
# If no history was found, see whether this object even exists.
@@ -743,7 +743,7 @@ class ChangeList(object):
def change_list(request, app_label, model_name):
model = models.get_model(app_label, model_name)
if model is None:
- raise Http404, "App %r, model %r, not found" % (app_label, model_name)
+ raise Http404("App %r, model %r, not found" % (app_label, model_name))
if not request.user.has_perm(app_label + '.' + model._meta.get_change_permission()):
raise PermissionDenied
try:
diff --git a/django/contrib/contenttypes/management.py b/django/contrib/contenttypes/management.py
index de3a685477..f492f54303 100644
--- a/django/contrib/contenttypes/management.py
+++ b/django/contrib/contenttypes/management.py
@@ -3,9 +3,9 @@ Creates content types for all installed models.
"""
from django.dispatch import dispatcher
-from django.db.models import get_models, signals
+from django.db.models import get_apps, get_models, signals
-def create_contenttypes(app, created_models, verbosity):
+def create_contenttypes(app, created_models, verbosity=2):
from django.contrib.contenttypes.models import ContentType
app_models = get_models(app)
if not app_models:
@@ -22,4 +22,11 @@ def create_contenttypes(app, created_models, verbosity):
if verbosity >= 2:
print "Adding content type '%s | %s'" % (ct.app_label, ct.model)
+def create_all_contenttypes(verbosity=2):
+ for app in get_apps():
+ create_contenttypes(app, None, verbosity)
+
dispatcher.connect(create_contenttypes, signal=signals.post_syncdb)
+
+if __name__ == "__main__":
+ create_all_contenttypes()
diff --git a/django/contrib/formtools/__init__.py b/django/contrib/formtools/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/django/contrib/formtools/__init__.py
diff --git a/django/contrib/formtools/preview.py b/django/contrib/formtools/preview.py
new file mode 100644
index 0000000000..9a9371b5f8
--- /dev/null
+++ b/django/contrib/formtools/preview.py
@@ -0,0 +1,160 @@
+"""
+Formtools Preview application.
+
+This is an abstraction of the following workflow:
+
+ "Display an HTML form, force a preview, then do something with the submission."
+
+Given a django.newforms.Form object that you define, this takes care of the
+following:
+
+ * Displays the form as HTML on a Web page.
+ * Validates the form data once it's submitted via POST.
+ * If it's valid, displays a preview page.
+ * If it's not valid, redisplays the form with error messages.
+ * At the preview page, if the preview confirmation button is pressed, calls
+ a hook that you define -- a done() method.
+
+The framework enforces the required preview by passing a shared-secret hash to
+the preview page. If somebody tweaks the form parameters on the preview page,
+the form submission will fail the hash comparison test.
+
+Usage
+=====
+
+Subclass FormPreview and define a done() method:
+
+ def done(self, request, clean_data):
+ # ...
+
+This method takes an HttpRequest object and a dictionary of the form data after
+it has been validated and cleaned. It should return an HttpResponseRedirect.
+
+Then, just instantiate your FormPreview subclass by passing it a Form class,
+and pass that to your URLconf, like so:
+
+ (r'^post/$', MyFormPreview(MyForm)),
+
+The FormPreview class has a few other hooks. See the docstrings in the source
+code below.
+
+The framework also uses two templates: 'formtools/preview.html' and
+'formtools/form.html'. You can override these by setting 'preview_template' and
+'form_template' attributes on your FormPreview subclass. See
+django/contrib/formtools/templates for the default templates.
+"""
+
+from django.conf import settings
+from django.core.exceptions import ImproperlyConfigured
+from django.http import Http404
+from django.shortcuts import render_to_response
+import cPickle as pickle
+import md5
+
+AUTO_ID = 'formtools_%s' # Each form here uses this as its auto_id parameter.
+
+class FormPreview(object):
+ preview_template = 'formtools/preview.html'
+ form_template = 'formtools/form.html'
+
+ # METHODS SUBCLASSES SHOULDN'T OVERRIDE ###################################
+
+ def __init__(self, form):
+ # form should be a Form class, not an instance.
+ self.form, self.state = form, {}
+
+ def __call__(self, request, *args, **kwargs):
+ stage = {'1': 'preview', '2': 'post'}.get(request.POST.get(self.unused_name('stage')), 'preview')
+ self.parse_params(*args, **kwargs)
+ try:
+ method = getattr(self, stage + '_' + request.method.lower())
+ except AttributeError:
+ raise Http404
+ return method(request)
+
+ def unused_name(self, name):
+ """
+ Given a first-choice name, adds an underscore to the name until it
+ reaches a name that isn't claimed by any field in the form.
+
+ This is calculated rather than being hard-coded so that no field names
+ are off-limits for use in the form.
+ """
+ while 1:
+ try:
+ f = self.form.fields[name]
+ except KeyError:
+ break # This field name isn't being used by the form.
+ name += '_'
+ return name
+
+ def preview_get(self, request):
+ "Displays the form"
+ f = self.form(auto_id=AUTO_ID)
+ return render_to_response(self.form_template, {'form': f, 'stage_field': self.unused_name('stage'), 'state': self.state})
+
+ def preview_post(self, request):
+ "Validates the POST data. If valid, displays the preview page. Else, redisplays form."
+ f = self.form(request.POST, auto_id=AUTO_ID)
+ context = {'form': f, 'stage_field': self.unused_name('stage'), 'state': self.state}
+ if f.is_valid():
+ context['hash_field'] = self.unused_name('hash')
+ context['hash_value'] = self.security_hash(request, f)
+ return render_to_response(self.preview_template, context)
+ else:
+ return render_to_response(self.form_template, context)
+
+ def post_post(self, request):
+ "Validates the POST data. If valid, calls done(). Else, redisplays form."
+ f = self.form(request.POST, auto_id=AUTO_ID)
+ if f.is_valid():
+ if self.security_hash(request, f) != request.POST.get(self.unused_name('hash')):
+ return self.failed_hash(request) # Security hash failed.
+ return self.done(request, f.clean_data)
+ else:
+ return render_to_response(self.form_template, {'form': f, 'stage_field': self.unused_name('stage'), 'state': self.state})
+
+ # METHODS SUBCLASSES MIGHT OVERRIDE IF APPROPRIATE ########################
+
+ def parse_params(self, *args, **kwargs):
+ """
+ Given captured args and kwargs from the URLconf, saves something in
+ self.state and/or raises Http404 if necessary.
+
+ For example, this URLconf captures a user_id variable:
+
+ (r'^contact/(?P<user_id>\d{1,6})/$', MyFormPreview(MyForm)),
+
+ In this case, the kwargs variable in parse_params would be
+ {'user_id': 32} for a request to '/contact/32/'. You can use that
+ user_id to make sure it's a valid user and/or save it for later, for
+ use in done().
+ """
+ pass
+
+ def security_hash(self, request, form):
+ """
+ Calculates the security hash for the given Form instance.
+
+ This creates a list of the form field names/values in a deterministic
+ order, pickles the result with the SECRET_KEY setting and takes an md5
+ hash of that.
+
+ Subclasses may want to take into account request-specific information
+ such as the IP address.
+ """
+ data = [(bf.name, bf.data) for bf in form] + [settings.SECRET_KEY]
+ # Use HIGHEST_PROTOCOL because it's the most efficient. It requires
+ # Python 2.3, but Django requires 2.3 anyway, so that's OK.
+ pickled = pickle.dumps(data, protocol=pickle.HIGHEST_PROTOCOL)
+ return md5.new(pickled).hexdigest()
+
+ def failed_hash(self, request):
+ "Returns an HttpResponse in the case of an invalid security hash."
+ return self.preview_post(request)
+
+ # METHODS SUBCLASSES MUST OVERRIDE ########################################
+
+ def done(self, request, clean_data):
+ "Does something with the clean_data and returns an HttpResponseRedirect."
+ raise NotImplementedError('You must define a done() method on your %s subclass.' % self.__class__.__name__)
diff --git a/django/contrib/formtools/templates/formtools/form.html b/django/contrib/formtools/templates/formtools/form.html
new file mode 100644
index 0000000000..90da8b2b2b
--- /dev/null
+++ b/django/contrib/formtools/templates/formtools/form.html
@@ -0,0 +1,15 @@
+{% extends "base.html" %}
+
+{% block content %}
+
+{% if form.errors %}<h1>Please correct the following errors</h1>{% else %}<h1>Submit</h1>{% endif %}
+
+<form action="" method="post">
+<table>
+{{ form }}
+</table>
+<input type="hidden" name="{{ stage_field }}" value="1" />
+<p><input type="submit" value="Submit" /></p>
+</form>
+
+{% endblock %}
diff --git a/django/contrib/formtools/templates/formtools/preview.html b/django/contrib/formtools/templates/formtools/preview.html
new file mode 100644
index 0000000000..c7955d46e1
--- /dev/null
+++ b/django/contrib/formtools/templates/formtools/preview.html
@@ -0,0 +1,36 @@
+{% extends "base.html" %}
+
+{% block content %}
+
+<h1>Preview your submission</h1>
+
+<table>
+{% for field in form %}
+<tr>
+<th>{{ field.verbose_name }}:</th>
+<td>{{ field.data|escape }}</td>
+</tr>
+{% endfor %}
+</table>
+
+<p>Security hash: {{ hash_value }}</p>
+
+<form action="" method="post">
+{% for field in form %}{{ field.as_hidden }}
+{% endfor %}
+<input type="hidden" name="{{ stage_field }}" value="2" />
+<input type="hidden" name="{{ hash_field }}" value="{{ hash_value }}" />
+<p><input type="submit" value="Submit" /></p>
+</form>
+
+<h1>Or edit it again</h1>
+
+<form action="" method="post">
+<table>
+{{ form }}
+</table>
+<input type="hidden" name="{{ stage_field }}" value="1" />
+<p><input type="submit" value="Submit changes" /></p>
+</form>
+
+{% endblock %}
diff --git a/django/contrib/sitemaps/__init__.py b/django/contrib/sitemaps/__init__.py
index 2c76e13c22..44ede4460a 100644
--- a/django/contrib/sitemaps/__init__.py
+++ b/django/contrib/sitemaps/__init__.py
@@ -29,7 +29,7 @@ def ping_google(sitemap_url=None, ping_url=PING_URL):
from django.contrib.sites.models import Site
current_site = Site.objects.get_current()
- url = "%s%s" % (current_site.domain, sitemap)
+ url = "%s%s" % (current_site.domain, sitemap_url)
params = urllib.urlencode({'sitemap':url})
urllib.urlopen("%s?%s" % (ping_url, params))
diff --git a/django/contrib/sitemaps/templates/sitemap.xml b/django/contrib/sitemaps/templates/sitemap.xml
index ad24c045d4..16d9a0bbe0 100644
--- a/django/contrib/sitemaps/templates/sitemap.xml
+++ b/django/contrib/sitemaps/templates/sitemap.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<urlset xmlns="http://www.google.com/schemas/sitemap/0.84">
+<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
{% spaceless %}
{% for url in urlset %}
<url>
diff --git a/django/contrib/sitemaps/templates/sitemap_index.xml b/django/contrib/sitemaps/templates/sitemap_index.xml
index c89b192ecc..a2bcce85dc 100644
--- a/django/contrib/sitemaps/templates/sitemap_index.xml
+++ b/django/contrib/sitemaps/templates/sitemap_index.xml
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
-<sitemapindex xmlns="http://www.google.com/schemas/sitemap/0.84">
+<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
{% for location in sitemaps %}<sitemap><loc>{{ location|escape }}</loc></sitemap>{% endfor %}
</sitemapindex>
diff --git a/django/core/handlers/base.py b/django/core/handlers/base.py
index c1403ea4fa..85473a6353 100644
--- a/django/core/handlers/base.py
+++ b/django/core/handlers/base.py
@@ -84,7 +84,11 @@ class BaseHandler(object):
# Complain if the view returned None (a common error).
if response is None:
- raise ValueError, "The view %s.%s didn't return an HttpResponse object." % (callback.__module__, callback.func_name)
+ try:
+ view_name = callback.func_name # If it's a function
+ except AttributeError:
+ view_name = callback.__class__.__name__ + '.__call__' # If it's a class
+ raise ValueError, "The view %s.%s didn't return an HttpResponse object." % (callback.__module__, view_name)
return response
except http.Http404, e:
diff --git a/django/core/handlers/wsgi.py b/django/core/handlers/wsgi.py
index 2998bd31f6..71cfecd9a0 100644
--- a/django/core/handlers/wsgi.py
+++ b/django/core/handlers/wsgi.py
@@ -62,7 +62,7 @@ def safe_copyfileobj(fsrc, fdst, length=16*1024, size=0):
data in the body.
"""
if not size:
- return copyfileobj(fsrc, fdst, length)
+ return
while size > 0:
buf = fsrc.read(min(length, size))
if not buf:
@@ -157,7 +157,11 @@ class WSGIRequest(http.HttpRequest):
return self._raw_post_data
except AttributeError:
buf = StringIO()
- content_length = int(self.environ['CONTENT_LENGTH'])
+ try:
+ # CONTENT_LENGTH might be absent if POST doesn't have content at all (lighttpd)
+ content_length = int(self.environ.get('CONTENT_LENGTH', 0))
+ except ValueError: # if CONTENT_LENGTH was empty string or not an integer
+ content_length = 0
safe_copyfileobj(self.environ['wsgi.input'], buf, size=content_length)
self._raw_post_data = buf.getvalue()
buf.close()
diff --git a/django/core/mail.py b/django/core/mail.py
index d1e5d0aa55..a5af6e610f 100644
--- a/django/core/mail.py
+++ b/django/core/mail.py
@@ -8,6 +8,8 @@ import socket
import time
import random
+DNS_NAME = socket.getfqdn() # Cache the hostname
+
class BadHeaderError(ValueError):
pass
@@ -53,7 +55,11 @@ def send_mass_mail(datatuple, fail_silently=False, auth_user=settings.EMAIL_HOST
msg['From'] = from_email
msg['To'] = ', '.join(recipient_list)
msg['Date'] = rfc822.formatdate()
- msg['Message-ID'] = "<%d.%d@%s>" % (time.time(), random.getrandbits(64), socket.getfqdn())
+ try:
+ random_bits = str(random.getrandbits(64))
+ except AttributeError: # Python 2.3 doesn't have random.getrandbits().
+ random_bits = ''.join([random.choice('1234567890') for i in range(19)])
+ msg['Message-ID'] = "<%d.%s@%s>" % (time.time(), random_bits, DNS_NAME)
try:
server.sendmail(from_email, recipient_list, msg.as_string())
num_sent += 1
diff --git a/django/core/servers/fastcgi.py b/django/core/servers/fastcgi.py
index fccb7bf087..649dd6942d 100644
--- a/django/core/servers/fastcgi.py
+++ b/django/core/servers/fastcgi.py
@@ -118,6 +118,8 @@ def runfastcgi(argset=[], **kwargs):
else:
return fastcgi_help("ERROR: Implementation must be one of prefork or thread.")
+ wsgi_opts['debug'] = False # Turn off flup tracebacks
+
# Prep up and go
from django.core.handlers.wsgi import WSGIHandler
diff --git a/django/core/xheaders.py b/django/core/xheaders.py
index 69f6115839..3beb930158 100644
--- a/django/core/xheaders.py
+++ b/django/core/xheaders.py
@@ -17,6 +17,6 @@ def populate_xheaders(request, response, model, object_id):
or if the request is from a logged in staff member.
"""
from django.conf import settings
- if request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS or (request.user.is_authenticated() and request.user.is_staff):
+ if request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS or (hasattr(request, 'user') and request.user.is_authenticated() and request.user.is_staff):
response['X-Object-Type'] = "%s.%s" % (model._meta.app_label, model._meta.object_name.lower())
response['X-Object-Id'] = str(object_id)
diff --git a/django/db/__init__.py b/django/db/__init__.py
index e5b4d32e0b..4176b5aa79 100644
--- a/django/db/__init__.py
+++ b/django/db/__init__.py
@@ -18,7 +18,7 @@ except ImportError, e:
available_backends = [f for f in os.listdir(backend_dir) if not f.startswith('_') and not f.startswith('.') and not f.endswith('.py') and not f.endswith('.pyc')]
available_backends.sort()
if settings.DATABASE_ENGINE not in available_backends:
- raise ImproperlyConfigured, "%r isn't an available database backend. vailable options are: %s" % \
+ raise ImproperlyConfigured, "%r isn't an available database backend. Available options are: %s" % \
(settings.DATABASE_ENGINE, ", ".join(map(repr, available_backends)))
else:
raise # If there's some other error, this must be an error in Django itself.
diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py
index bc1eaf0a6a..8e8d68aad5 100644
--- a/django/db/models/fields/__init__.py
+++ b/django/db/models/fields/__init__.py
@@ -457,9 +457,7 @@ class DateField(Field):
def get_db_prep_save(self, value):
# Casts dates into string format for entry into database.
- if isinstance(value, datetime.datetime):
- value = value.date().strftime('%Y-%m-%d')
- elif isinstance(value, datetime.date):
+ if value is not None:
value = value.strftime('%Y-%m-%d')
return Field.get_db_prep_save(self, value)
@@ -489,19 +487,12 @@ class DateTimeField(DateField):
def get_db_prep_save(self, value):
# Casts dates into string format for entry into database.
- if isinstance(value, datetime.datetime):
+ if value is not None:
# MySQL will throw a warning if microseconds are given, because it
# doesn't support microseconds.
if settings.DATABASE_ENGINE == 'mysql' and hasattr(value, 'microsecond'):
value = value.replace(microsecond=0)
value = str(value)
- elif isinstance(value, datetime.date):
- # MySQL will throw a warning if microseconds are given, because it
- # doesn't support microseconds.
- if settings.DATABASE_ENGINE == 'mysql' and hasattr(value, 'microsecond'):
- value = datetime.datetime(value.year, value.month, value.day, microsecond=0)
- value = str(value)
-
return Field.get_db_prep_save(self, value)
def get_db_prep_lookup(self, lookup_type, value):
diff --git a/django/http/__init__.py b/django/http/__init__.py
index bb0e973aae..48f10329fd 100644
--- a/django/http/__init__.py
+++ b/django/http/__init__.py
@@ -208,7 +208,7 @@ class HttpResponse(object):
if path is not None:
self.cookies[key]['path'] = path
if domain is not None:
- self.cookies[key]['domain'] = path
+ self.cookies[key]['domain'] = domain
self.cookies[key]['expires'] = 0
self.cookies[key]['max-age'] = 0
diff --git a/django/middleware/gzip.py b/django/middleware/gzip.py
index 7d860abdb1..a7c74481d0 100644
--- a/django/middleware/gzip.py
+++ b/django/middleware/gzip.py
@@ -25,4 +25,5 @@ class GZipMiddleware(object):
response.content = compress_string(response.content)
response['Content-Encoding'] = 'gzip'
+ response['Content-Length'] = str(len(response.content))
return response
diff --git a/django/newforms/__init__.py b/django/newforms/__init__.py
index 2a472d7b39..a445a21bfb 100644
--- a/django/newforms/__init__.py
+++ b/django/newforms/__init__.py
@@ -14,15 +14,4 @@ from util import ValidationError
from widgets import *
from fields import *
from forms import Form
-
-##########################
-# DATABASE API SHORTCUTS #
-##########################
-
-def form_for_model(model):
- "Returns a Form instance for the given Django model class."
- raise NotImplementedError
-
-def form_for_fields(field_list):
- "Returns a Form instance for the given list of Django database field instances."
- raise NotImplementedError
+from models import *
diff --git a/django/newforms/fields.py b/django/newforms/fields.py
index 54089cb3c3..fada75fe5d 100644
--- a/django/newforms/fields.py
+++ b/django/newforms/fields.py
@@ -2,7 +2,8 @@
Field classes
"""
-from util import ValidationError, DEFAULT_ENCODING
+from django.utils.translation import gettext
+from util import ValidationError, smart_unicode
from widgets import TextInput, CheckboxInput, Select, SelectMultiple
import datetime
import re
@@ -28,6 +29,9 @@ except NameError:
class Field(object):
widget = TextInput # Default widget to use when rendering this type of Field.
+ # Tracks each time a Field instance is created. Used to retain order.
+ creation_counter = 0
+
def __init__(self, required=True, widget=None):
self.required = required
widget = widget or self.widget
@@ -35,6 +39,10 @@ class Field(object):
widget = widget()
self.widget = widget
+ # Increase the creation counter, and save our local copy.
+ self.creation_counter = Field.creation_counter
+ Field.creation_counter += 1
+
def clean(self, value):
"""
Validates the given value and returns its "cleaned" value as an
@@ -43,7 +51,7 @@ class Field(object):
Raises ValidationError for any errors.
"""
if self.required and value in EMPTY_VALUES:
- raise ValidationError(u'This field is required.')
+ raise ValidationError(gettext(u'This field is required.'))
return value
class CharField(Field):
@@ -55,14 +63,11 @@ class CharField(Field):
"Validates max_length and min_length. Returns a Unicode object."
Field.clean(self, value)
if value in EMPTY_VALUES: value = u''
- if not isinstance(value, basestring):
- value = unicode(str(value), DEFAULT_ENCODING)
- elif not isinstance(value, unicode):
- value = unicode(value, DEFAULT_ENCODING)
+ value = smart_unicode(value)
if self.max_length is not None and len(value) > self.max_length:
- raise ValidationError(u'Ensure this value has at most %d characters.' % self.max_length)
+ raise ValidationError(gettext(u'Ensure this value has at most %d characters.') % self.max_length)
if self.min_length is not None and len(value) < self.min_length:
- raise ValidationError(u'Ensure this value has at least %d characters.' % self.min_length)
+ raise ValidationError(gettext(u'Ensure this value has at least %d characters.') % self.min_length)
return value
class IntegerField(Field):
@@ -72,10 +77,12 @@ class IntegerField(Field):
of int().
"""
super(IntegerField, self).clean(value)
+ if not self.required and value in EMPTY_VALUES:
+ return u''
try:
return int(value)
except (ValueError, TypeError):
- raise ValidationError(u'Enter a whole number.')
+ raise ValidationError(gettext(u'Enter a whole number.'))
DEFAULT_DATE_INPUT_FORMATS = (
'%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', # '2006-10-25', '10/25/2006', '10/25/06'
@@ -107,7 +114,7 @@ class DateField(Field):
return datetime.date(*time.strptime(value, format)[:3])
except ValueError:
continue
- raise ValidationError(u'Enter a valid date.')
+ raise ValidationError(gettext(u'Enter a valid date.'))
DEFAULT_DATETIME_INPUT_FORMATS = (
'%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59'
@@ -143,7 +150,7 @@ class DateTimeField(Field):
return datetime.datetime(*time.strptime(value, format)[:6])
except ValueError:
continue
- raise ValidationError(u'Enter a valid date/time.')
+ raise ValidationError(gettext(u'Enter a valid date/time.'))
class RegexField(Field):
def __init__(self, regex, error_message=None, required=True, widget=None):
@@ -156,7 +163,7 @@ class RegexField(Field):
if isinstance(regex, basestring):
regex = re.compile(regex)
self.regex = regex
- self.error_message = error_message or u'Enter a valid value.'
+ self.error_message = error_message or gettext(u'Enter a valid value.')
def clean(self, value):
"""
@@ -165,10 +172,9 @@ class RegexField(Field):
"""
Field.clean(self, value)
if value in EMPTY_VALUES: value = u''
- if not isinstance(value, basestring):
- value = unicode(str(value), DEFAULT_ENCODING)
- elif not isinstance(value, unicode):
- value = unicode(value, DEFAULT_ENCODING)
+ value = smart_unicode(value)
+ if not self.required and value == u'':
+ return value
if not self.regex.search(value):
raise ValidationError(self.error_message)
return value
@@ -180,7 +186,7 @@ email_re = re.compile(
class EmailField(RegexField):
def __init__(self, required=True, widget=None):
- RegexField.__init__(self, email_re, u'Enter a valid e-mail address.', required, widget)
+ RegexField.__init__(self, email_re, gettext(u'Enter a valid e-mail address.'), required, widget)
url_re = re.compile(
r'^https?://' # http:// or https://
@@ -198,7 +204,7 @@ except ImportError:
class URLField(RegexField):
def __init__(self, required=True, verify_exists=False, widget=None,
validator_user_agent=URL_VALIDATOR_USER_AGENT):
- RegexField.__init__(self, url_re, u'Enter a valid URL.', required, widget)
+ RegexField.__init__(self, url_re, gettext(u'Enter a valid URL.'), required, widget)
self.verify_exists = verify_exists
self.user_agent = validator_user_agent
@@ -215,12 +221,12 @@ class URLField(RegexField):
"User-Agent": self.user_agent,
}
try:
- req = urllib2.Request(field_data, None, headers)
+ req = urllib2.Request(value, None, headers)
u = urllib2.urlopen(req)
except ValueError:
- raise ValidationError(u'Enter a valid URL.')
+ raise ValidationError(gettext(u'Enter a valid URL.'))
except: # urllib2.URLError, httplib.InvalidURL, etc.
- raise ValidationError(u'This URL appears to be a broken link.')
+ raise ValidationError(gettext(u'This URL appears to be a broken link.'))
return value
class BooleanField(Field):
@@ -244,13 +250,12 @@ class ChoiceField(Field):
"""
value = Field.clean(self, value)
if value in EMPTY_VALUES: value = u''
- if not isinstance(value, basestring):
- value = unicode(str(value), DEFAULT_ENCODING)
- elif not isinstance(value, unicode):
- value = unicode(value, DEFAULT_ENCODING)
+ value = smart_unicode(value)
+ if not self.required and value == u'':
+ return value
valid_values = set([str(k) for k, v in self.choices])
if value not in valid_values:
- raise ValidationError(u'Select a valid choice. %s is not one of the available choices.' % value)
+ raise ValidationError(gettext(u'Select a valid choice. %s is not one of the available choices.') % value)
return value
class MultipleChoiceField(ChoiceField):
@@ -261,27 +266,31 @@ class MultipleChoiceField(ChoiceField):
"""
Validates that the input is a list or tuple.
"""
- if not isinstance(value, (list, tuple)):
- raise ValidationError(u'Enter a list of values.')
if self.required and not value:
- raise ValidationError(u'This field is required.')
+ raise ValidationError(gettext(u'This field is required.'))
+ elif not self.required and not value:
+ return []
+ if not isinstance(value, (list, tuple)):
+ raise ValidationError(gettext(u'Enter a list of values.'))
new_value = []
for val in value:
- if not isinstance(val, basestring):
- value = unicode(str(val), DEFAULT_ENCODING)
- elif not isinstance(val, unicode):
- value = unicode(val, DEFAULT_ENCODING)
- new_value.append(value)
+ val = smart_unicode(val)
+ new_value.append(val)
# Validate that each value in the value list is in self.choices.
- valid_values = set([k for k, v in self.choices])
+ valid_values = set([smart_unicode(k) for k, v in self.choices])
for val in new_value:
if val not in valid_values:
- raise ValidationError(u'Select a valid choice. %s is not one of the available choices.' % val)
+ raise ValidationError(gettext(u'Select a valid choice. %s is not one of the available choices.') % val)
return new_value
class ComboField(Field):
def __init__(self, fields=(), required=True, widget=None):
Field.__init__(self, required, widget)
+ # Set 'required' to False on the individual fields, because the
+ # required validation will be handled by ComboField, not by those
+ # individual fields.
+ for f in fields:
+ f.required = False
self.fields = fields
def clean(self, value):
diff --git a/django/newforms/forms.py b/django/newforms/forms.py
index e490d0d5f9..e0b3d500b5 100644
--- a/django/newforms/forms.py
+++ b/django/newforms/forms.py
@@ -2,9 +2,11 @@
Form classes
"""
+from django.utils.datastructures import SortedDict
+from django.utils.html import escape
from fields import Field
-from widgets import TextInput, Textarea
-from util import ErrorDict, ErrorList, ValidationError
+from widgets import TextInput, Textarea, HiddenInput
+from util import StrAndUnicode, ErrorDict, ErrorList, ValidationError
NON_FIELD_ERRORS = '__all__'
@@ -13,22 +15,35 @@ def pretty_name(name):
name = name[0].upper() + name[1:]
return name.replace('_', ' ')
+class SortedDictFromList(SortedDict):
+ "A dictionary that keeps its keys in the order in which they're inserted."
+ # This is different than django.utils.datastructures.SortedDict, because
+ # this takes a list/tuple as the argument to __init__().
+ def __init__(self, data=None):
+ if data is None: data = []
+ self.keyOrder = [d[0] for d in data]
+ dict.__init__(self, dict(data))
+
class DeclarativeFieldsMetaclass(type):
"Metaclass that converts Field attributes to a dictionary called 'fields'."
def __new__(cls, name, bases, attrs):
- attrs['fields'] = dict([(name, attrs.pop(name)) for name, obj in attrs.items() if isinstance(obj, Field)])
+ fields = [(name, attrs.pop(name)) for name, obj in attrs.items() if isinstance(obj, Field)]
+ fields.sort(lambda x, y: cmp(x[1].creation_counter, y[1].creation_counter))
+ attrs['fields'] = SortedDictFromList(fields)
return type.__new__(cls, name, bases, attrs)
-class Form(object):
+class Form(StrAndUnicode):
"A collection of Fields, plus their associated data."
__metaclass__ = DeclarativeFieldsMetaclass
- def __init__(self, data=None): # TODO: prefix stuff
+ def __init__(self, data=None, auto_id=False): # TODO: prefix stuff
+ self.ignore_errors = data is None
self.data = data or {}
+ self.auto_id = auto_id
self.clean_data = None # Stores the data after clean() has been called.
self.__errors = None # Stores the errors after clean() has been called.
- def __str__(self):
+ def __unicode__(self):
return self.as_table()
def __iter__(self):
@@ -43,62 +58,66 @@ class Form(object):
raise KeyError('Key %r not found in Form' % name)
return BoundField(self, field, name)
- def clean(self):
- if self.__errors is None:
- self.full_clean()
- return self.clean_data
-
- def errors(self):
+ def _errors(self):
"Returns an ErrorDict for self.data"
if self.__errors is None:
self.full_clean()
return self.__errors
+ errors = property(_errors)
def is_valid(self):
"""
- Returns True if the form has no errors. Otherwise, False. This exists
- solely for convenience, so client code can use positive logic rather
- than confusing negative logic ("if not form.errors()").
+ Returns True if the form has no errors. Otherwise, False. If errors are
+ being ignored, returns False.
"""
- return not bool(self.errors())
+ return not self.ignore_errors and not bool(self.errors)
+
+ def _html_output(self, normal_row, error_row, row_ender, errors_on_separate_row):
+ "Helper function for outputting HTML. Used by as_table(), as_ul(), as_p()."
+ top_errors = self.non_field_errors() # Errors that should be displayed above all fields.
+ output, hidden_fields = [], []
+ for name, field in self.fields.items():
+ bf = BoundField(self, field, name)
+ bf_errors = bf.errors # Cache in local variable.
+ if bf.is_hidden:
+ if bf_errors:
+ top_errors.extend(['(Hidden field %s) %s' % (name, e) for e in bf_errors])
+ hidden_fields.append(unicode(bf))
+ else:
+ if errors_on_separate_row and bf_errors:
+ output.append(error_row % bf_errors)
+ output.append(normal_row % {'errors': bf_errors, 'label': bf.label_tag(escape(bf.verbose_name+':')), 'field': bf})
+ if top_errors:
+ output.insert(0, error_row % top_errors)
+ if hidden_fields: # Insert any hidden fields in the last row.
+ str_hidden = u''.join(hidden_fields)
+ if output:
+ last_row = output[-1]
+ # Chop off the trailing row_ender (e.g. '</td></tr>') and insert the hidden fields.
+ output[-1] = last_row[:-len(row_ender)] + str_hidden + row_ender
+ else: # If there aren't any rows in the output, just append the hidden fields.
+ output.append(str_hidden)
+ return u'\n'.join(output)
def as_table(self):
- "Returns this form rendered as an HTML <table>."
- output = u'\n'.join(['<tr><td>%s:</td><td>%s</td></tr>' % (pretty_name(name), BoundField(self, field, name)) for name, field in self.fields.items()])
- return '<table>\n%s\n</table>' % output
+ "Returns this form rendered as HTML <tr>s -- excluding the <table></table>."
+ return self._html_output(u'<tr><td>%(label)s</td><td>%(field)s</td></tr>', u'<tr><td colspan="2">%s</td></tr>', '</td></tr>', True)
def as_ul(self):
- "Returns this form rendered as an HTML <ul>."
- output = u'\n'.join(['<li>%s: %s</li>' % (pretty_name(name), BoundField(self, field, name)) for name, field in self.fields.items()])
- return '<ul>\n%s\n</ul>' % output
-
- def as_table_with_errors(self):
- "Returns this form rendered as an HTML <table>, with errors."
- output = []
- if self.errors().get(NON_FIELD_ERRORS):
- # Errors not corresponding to a particular field are displayed at the top.
- output.append('<tr><td colspan="2"><ul>%s</ul></td></tr>' % '\n'.join(['<li>%s</li>' % e for e in self.errors()[NON_FIELD_ERRORS]]))
- for name, field in self.fields.items():
- bf = BoundField(self, field, name)
- if bf.errors:
- output.append('<tr><td colspan="2"><ul>%s</ul></td></tr>' % '\n'.join(['<li>%s</li>' % e for e in bf.errors]))
- output.append('<tr><td>%s:</td><td>%s</td></tr>' % (pretty_name(name), bf))
- return '<table>\n%s\n</table>' % '\n'.join(output)
-
- def as_ul_with_errors(self):
- "Returns this form rendered as an HTML <ul>, with errors."
- output = []
- if self.errors().get(NON_FIELD_ERRORS):
- # Errors not corresponding to a particular field are displayed at the top.
- output.append('<li><ul>%s</ul></li>' % '\n'.join(['<li>%s</li>' % e for e in self.errors()[NON_FIELD_ERRORS]]))
- for name, field in self.fields.items():
- bf = BoundField(self, field, name)
- line = '<li>'
- if bf.errors:
- line += '<ul>%s</ul>' % '\n'.join(['<li>%s</li>' % e for e in bf.errors])
- line += '%s: %s</li>' % (pretty_name(name), bf)
- output.append(line)
- return '<ul>\n%s\n</ul>' % '\n'.join(output)
+ "Returns this form rendered as HTML <li>s -- excluding the <ul></ul>."
+ return self._html_output(u'<li>%(errors)s%(label)s %(field)s</li>', u'<li>%s</li>', '</li>', False)
+
+ def as_p(self):
+ "Returns this form rendered as HTML <p>s."
+ return self._html_output(u'<p>%(label)s %(field)s</p>', u'<p>%s</p>', '</p>', True)
+
+ def non_field_errors(self):
+ """
+ Returns an ErrorList of errors that aren't associated with a particular
+ field -- i.e., from Form.clean(). Returns an empty ErrorList if there
+ are none.
+ """
+ return self.errors.get(NON_FIELD_ERRORS, ErrorList())
def full_clean(self):
"""
@@ -106,8 +125,14 @@ class Form(object):
"""
self.clean_data = {}
errors = ErrorDict()
+ if self.ignore_errors: # Stop further processing.
+ self.__errors = errors
+ return
for name, field in self.fields.items():
- value = self.data.get(name, None)
+ # value_from_datadict() gets the data from the dictionary.
+ # Each widget type knows how to retrieve its own data, because some
+ # widgets split data over several HTML fields.
+ value = field.widget.value_from_datadict(self.data, name)
try:
value = field.clean(value)
self.clean_data[name] = value
@@ -127,36 +152,45 @@ class Form(object):
def clean(self):
"""
Hook for doing any extra form-wide cleaning after Field.clean() been
- called on every field.
+ called on every field. Any ValidationError raised by this method will
+ not be associated with a particular field; it will have a special-case
+ association with the field named '__all__'.
"""
return self.clean_data
-class BoundField(object):
+class BoundField(StrAndUnicode):
"A Field plus data"
def __init__(self, form, field, name):
- self._form = form
- self._field = field
- self._name = name
+ self.form = form
+ self.field = field
+ self.name = name
- def __str__(self):
+ def __unicode__(self):
"Renders this field as an HTML widget."
# Use the 'widget' attribute on the field to determine which type
# of HTML widget to use.
- return self.as_widget(self._field.widget)
+ value = self.as_widget(self.field.widget)
+ if not isinstance(value, basestring):
+ # Some Widget render() methods -- notably RadioSelect -- return a
+ # "special" object rather than a string. Call the __str__() on that
+ # object to get its rendered value.
+ value = value.__str__()
+ return value
def _errors(self):
"""
Returns an ErrorList for this field. Returns an empty ErrorList
if there are none.
"""
- try:
- return self._form.errors()[self._name]
- except KeyError:
- return ErrorList()
+ return self.form.errors.get(self.name, ErrorList())
errors = property(_errors)
def as_widget(self, widget, attrs=None):
- return widget.render(self._name, self._form.data.get(self._name, None), attrs=attrs)
+ attrs = attrs or {}
+ auto_id = self.auto_id
+ if auto_id and not attrs.has_key('id') and not widget.attrs.has_key('id'):
+ attrs['id'] = auto_id
+ return widget.render(self.name, self.data, attrs=attrs)
def as_text(self, attrs=None):
"""
@@ -167,3 +201,49 @@ class BoundField(object):
def as_textarea(self, attrs=None):
"Returns a string of HTML for representing this as a <textarea>."
return self.as_widget(Textarea(), attrs)
+
+ def as_hidden(self, attrs=None):
+ """
+ Returns a string of HTML for representing this as an <input type="hidden">.
+ """
+ return self.as_widget(HiddenInput(), attrs)
+
+ def _data(self):
+ "Returns the data for this BoundField, or None if it wasn't given."
+ return self.form.data.get(self.name, None)
+ data = property(_data)
+
+ def _verbose_name(self):
+ return pretty_name(self.name)
+ verbose_name = property(_verbose_name)
+
+ def label_tag(self, contents=None):
+ """
+ Wraps the given contents in a <label>, if the field has an ID attribute.
+ Does not HTML-escape the contents. If contents aren't given, uses the
+ field's HTML-escaped verbose_name.
+ """
+ contents = contents or escape(self.verbose_name)
+ widget = self.field.widget
+ id_ = widget.attrs.get('id') or self.auto_id
+ if id_:
+ contents = '<label for="%s">%s</label>' % (widget.id_for_label(id_), contents)
+ return contents
+
+ def _is_hidden(self):
+ "Returns True if this BoundField's widget is hidden."
+ return self.field.widget.is_hidden
+ is_hidden = property(_is_hidden)
+
+ def _auto_id(self):
+ """
+ Calculates and returns the ID attribute for this BoundField, if the
+ associated Form has specified auto_id. Returns an empty string otherwise.
+ """
+ auto_id = self.form.auto_id
+ if auto_id and '%s' in str(auto_id):
+ return str(auto_id) % self.name
+ elif auto_id:
+ return self.name
+ return ''
+ auto_id = property(_auto_id)
diff --git a/django/newforms/models.py b/django/newforms/models.py
new file mode 100644
index 0000000000..d99619c44a
--- /dev/null
+++ b/django/newforms/models.py
@@ -0,0 +1,13 @@
+"""
+Helper functions for creating Forms from Django models and database field objects.
+"""
+
+__all__ = ('form_for_model', 'form_for_fields')
+
+def form_for_model(model):
+ "Returns a Form instance for the given Django model class."
+ raise NotImplementedError
+
+def form_for_fields(field_list):
+ "Returns a Form instance for the given list of Django database field instances."
+ raise NotImplementedError
diff --git a/django/newforms/util.py b/django/newforms/util.py
index 3887010c85..a78623a17b 100644
--- a/django/newforms/util.py
+++ b/django/newforms/util.py
@@ -1,11 +1,22 @@
-# Default encoding for input byte strings.
-DEFAULT_ENCODING = 'utf-8' # TODO: First look at django.conf.settings, then fall back to this.
+from django.conf import settings
def smart_unicode(s):
- if not isinstance(s, unicode):
- s = unicode(s, DEFAULT_ENCODING)
+ if not isinstance(s, basestring):
+ s = unicode(str(s))
+ elif not isinstance(s, unicode):
+ s = unicode(s, settings.DEFAULT_CHARSET)
return s
+class StrAndUnicode(object):
+ """
+ A class whose __str__ returns its __unicode__ as a bytestring
+ according to settings.DEFAULT_CHARSET.
+
+ Useful as a mix-in.
+ """
+ def __str__(self):
+ return self.__unicode__().encode(settings.DEFAULT_CHARSET)
+
class ErrorDict(dict):
"""
A collection of errors that knows how to display itself in various formats.
diff --git a/django/newforms/widgets.py b/django/newforms/widgets.py
index 5ec27653cf..1a2d7d67c9 100644
--- a/django/newforms/widgets.py
+++ b/django/newforms/widgets.py
@@ -5,9 +5,10 @@ HTML Widget classes
__all__ = (
'Widget', 'TextInput', 'PasswordInput', 'HiddenInput', 'FileInput',
'Textarea', 'CheckboxInput',
- 'Select', 'SelectMultiple',
+ 'Select', 'SelectMultiple', 'RadioSelect', 'CheckboxSelectMultiple',
)
+from util import StrAndUnicode, smart_unicode
from django.utils.html import escape
from itertools import chain
@@ -16,28 +17,58 @@ try:
except NameError:
from sets import Set as set # Python 2.3 fallback
-# Converts a dictionary to a single string with key="value", XML-style.
-# Assumes keys do not need to be XML-escaped.
-flatatt = lambda attrs: ' '.join(['%s="%s"' % (k, escape(v)) for k, v in attrs.items()])
+# Converts a dictionary to a single string with key="value", XML-style with
+# a leading space. Assumes keys do not need to be XML-escaped.
+flatatt = lambda attrs: u''.join([u' %s="%s"' % (k, escape(v)) for k, v in attrs.items()])
class Widget(object):
requires_data_list = False # Determines whether render()'s 'value' argument should be a list.
+ is_hidden = False # Determines whether this corresponds to an <input type="hidden">.
+
def __init__(self, attrs=None):
self.attrs = attrs or {}
def render(self, name, value):
raise NotImplementedError
+ def build_attrs(self, extra_attrs=None, **kwargs):
+ "Helper function for building an attribute dictionary."
+ attrs = dict(self.attrs, **kwargs)
+ if extra_attrs:
+ attrs.update(extra_attrs)
+ return attrs
+
+ def value_from_datadict(self, data, name):
+ """
+ Given a dictionary of data and this widget's name, returns the value
+ of this widget. Returns None if it's not provided.
+ """
+ return data.get(name, None)
+
+ def id_for_label(self, id_):
+ """
+ Returns the HTML ID attribute of this Widget for use by a <label>,
+ given the ID of the field. Returns None if no ID is available.
+
+ This hook is necessary because some widgets have multiple HTML
+ elements and, thus, multiple IDs. In that case, this method should
+ return an ID value that corresponds to the first ID in the widget's
+ tags.
+ """
+ return id_
+ id_for_label = classmethod(id_for_label)
+
class Input(Widget):
- "Base class for all <input> widgets (except type='checkbox', which is special)"
+ """
+ Base class for all <input> widgets (except type='checkbox' and
+ type='radio', which are special).
+ """
input_type = None # Subclasses must define this.
def render(self, name, value, attrs=None):
if value is None: value = ''
- final_attrs = dict(self.attrs, type=self.input_type, name=name)
- if attrs:
- final_attrs.update(attrs)
- if value != '': final_attrs['value'] = value # Only add the 'value' attribute if a value is non-empty.
- return u'<input %s />' % flatatt(final_attrs)
+ final_attrs = self.build_attrs(attrs, type=self.input_type, name=name)
+ if value != '': final_attrs['value'] = smart_unicode(value) # Only add the 'value' attribute if a value is non-empty.
+ return u'<input%s />' % flatatt(final_attrs)
class TextInput(Input):
input_type = 'text'
@@ -47,6 +78,7 @@ class PasswordInput(Input):
class HiddenInput(Input):
input_type = 'hidden'
+ is_hidden = True
class FileInput(Input):
input_type = 'file'
@@ -54,18 +86,28 @@ class FileInput(Input):
class Textarea(Widget):
def render(self, name, value, attrs=None):
if value is None: value = ''
- final_attrs = dict(self.attrs, name=name)
- if attrs:
- final_attrs.update(attrs)
- return u'<textarea %s>%s</textarea>' % (flatatt(final_attrs), escape(value))
+ value = smart_unicode(value)
+ final_attrs = self.build_attrs(attrs, name=name)
+ return u'<textarea%s>%s</textarea>' % (flatatt(final_attrs), escape(value))
class CheckboxInput(Widget):
+ def __init__(self, attrs=None, check_test=bool):
+ # check_test is a callable that takes a value and returns True
+ # if the checkbox should be checked for that value.
+ self.attrs = attrs or {}
+ self.check_test = check_test
+
def render(self, name, value, attrs=None):
- final_attrs = dict(self.attrs, type='checkbox', name=name)
- if attrs:
- final_attrs.update(attrs)
- if value: final_attrs['checked'] = 'checked'
- return u'<input %s />' % flatatt(final_attrs)
+ final_attrs = self.build_attrs(attrs, type='checkbox', name=name)
+ try:
+ result = self.check_test(value)
+ except: # Silently catch exceptions
+ result = False
+ if result:
+ final_attrs['checked'] = 'checked'
+ if value not in ('', True, False, None):
+ final_attrs['value'] = smart_unicode(value) # Only add the 'value' attribute if a value is non-empty.
+ return u'<input%s />' % flatatt(final_attrs)
class Select(Widget):
def __init__(self, attrs=None, choices=()):
@@ -75,14 +117,13 @@ class Select(Widget):
def render(self, name, value, attrs=None, choices=()):
if value is None: value = ''
- final_attrs = dict(self.attrs, name=name)
- if attrs:
- final_attrs.update(attrs)
- output = [u'<select %s>' % flatatt(final_attrs)]
- str_value = str(value) # Normalize to string.
+ final_attrs = self.build_attrs(attrs, name=name)
+ output = [u'<select%s>' % flatatt(final_attrs)]
+ str_value = smart_unicode(value) # Normalize to string.
for option_value, option_label in chain(self.choices, choices):
- selected_html = (str(option_value) == str_value) and ' selected="selected"' or ''
- output.append(u'<option value="%s"%s>%s</option>' % (escape(option_value), selected_html, escape(option_label)))
+ option_value = smart_unicode(option_value)
+ selected_html = (option_value == str_value) and u' selected="selected"' or ''
+ output.append(u'<option value="%s"%s>%s</option>' % (escape(option_value), selected_html, escape(smart_unicode(option_label))))
output.append(u'</select>')
return u'\n'.join(output)
@@ -95,19 +136,87 @@ class SelectMultiple(Widget):
def render(self, name, value, attrs=None, choices=()):
if value is None: value = []
- final_attrs = dict(self.attrs, name=name)
- if attrs:
- final_attrs.update(attrs)
- output = [u'<select multiple="multiple" %s>' % flatatt(final_attrs)]
- str_values = set([str(v) for v in value]) # Normalize to strings.
+ final_attrs = self.build_attrs(attrs, name=name)
+ output = [u'<select multiple="multiple"%s>' % flatatt(final_attrs)]
+ str_values = set([smart_unicode(v) for v in value]) # Normalize to strings.
for option_value, option_label in chain(self.choices, choices):
- selected_html = (str(option_value) in str_values) and ' selected="selected"' or ''
- output.append(u'<option value="%s"%s>%s</option>' % (escape(option_value), selected_html, escape(option_label)))
+ option_value = smart_unicode(option_value)
+ selected_html = (option_value in str_values) and ' selected="selected"' or ''
+ output.append(u'<option value="%s"%s>%s</option>' % (escape(option_value), selected_html, escape(smart_unicode(option_label))))
output.append(u'</select>')
return u'\n'.join(output)
-class RadioSelect(Widget):
- pass
+class RadioInput(StrAndUnicode):
+ "An object used by RadioFieldRenderer that represents a single <input type='radio'>."
+ def __init__(self, name, value, attrs, choice, index):
+ self.name, self.value = name, value
+ self.attrs = attrs
+ self.choice_value, self.choice_label = choice
+ self.index = index
+
+ def __unicode__(self):
+ return u'<label>%s %s</label>' % (self.tag(), self.choice_label)
+
+ def is_checked(self):
+ return self.value == smart_unicode(self.choice_value)
+
+ def tag(self):
+ if self.attrs.has_key('id'):
+ self.attrs['id'] = '%s_%s' % (self.attrs['id'], self.index)
+ final_attrs = dict(self.attrs, type='radio', name=self.name, value=self.choice_value)
+ if self.is_checked():
+ final_attrs['checked'] = 'checked'
+ return u'<input%s />' % flatatt(final_attrs)
+
+class RadioFieldRenderer(StrAndUnicode):
+ "An object used by RadioSelect to enable customization of radio widgets."
+ def __init__(self, name, value, attrs, choices):
+ self.name, self.value, self.attrs = name, value, attrs
+ self.choices = choices
+
+ def __iter__(self):
+ for i, choice in enumerate(self.choices):
+ yield RadioInput(self.name, self.value, self.attrs.copy(), choice, i)
+
+ def __unicode__(self):
+ "Outputs a <ul> for this set of radio fields."
+ return u'<ul>\n%s\n</ul>' % u'\n'.join([u'<li>%s</li>' % w for w in self])
+
+class RadioSelect(Select):
+ def render(self, name, value, attrs=None, choices=()):
+ "Returns a RadioFieldRenderer instance rather than a Unicode string."
+ if value is None: value = ''
+ str_value = smart_unicode(value) # Normalize to string.
+ attrs = attrs or {}
+ return RadioFieldRenderer(name, str_value, attrs, list(chain(self.choices, choices)))
+
+ def id_for_label(self, id_):
+ # RadioSelect is represented by multiple <input type="radio"> fields,
+ # each of which has a distinct ID. The IDs are made distinct by a "_X"
+ # suffix, where X is the zero-based index of the radio field. Thus,
+ # the label for a RadioSelect should reference the first one ('_0').
+ if id_:
+ id_ += '_0'
+ return id_
+ id_for_label = classmethod(id_for_label)
+
+class CheckboxSelectMultiple(SelectMultiple):
+ def render(self, name, value, attrs=None, choices=()):
+ if value is None: value = []
+ final_attrs = self.build_attrs(attrs, name=name)
+ output = [u'<ul>']
+ str_values = set([smart_unicode(v) for v in value]) # Normalize to strings.
+ cb = CheckboxInput(final_attrs, check_test=lambda value: value in str_values)
+ for option_value, option_label in chain(self.choices, choices):
+ option_value = smart_unicode(option_value)
+ rendered_cb = cb.render(name, option_value)
+ output.append(u'<li><label>%s %s</label></li>' % (rendered_cb, escape(smart_unicode(option_label))))
+ output.append(u'</ul>')
+ return u'\n'.join(output)
-class CheckboxSelectMultiple(Widget):
- pass
+ def id_for_label(self, id_):
+ # See the comment for RadioSelect.id_for_label()
+ if id_:
+ id_ += '_0'
+ return id_
+ id_for_label = classmethod(id_for_label)
diff --git a/django/template/__init__.py b/django/template/__init__.py
index 5affafeba9..7718801684 100644
--- a/django/template/__init__.py
+++ b/django/template/__init__.py
@@ -742,7 +742,11 @@ class VariableNode(Node):
def encode_output(self, output):
# Check type so that we don't run str() on a Unicode object
if not isinstance(output, basestring):
- return str(output)
+ try:
+ return str(output)
+ except UnicodeEncodeError:
+ # If __str__() returns a Unicode object, convert it to bytestring.
+ return unicode(output).encode(settings.DEFAULT_CHARSET)
elif isinstance(output, unicode):
return output.encode(settings.DEFAULT_CHARSET)
else:
diff --git a/django/views/generic/list_detail.py b/django/views/generic/list_detail.py
index bd0f17c56a..1836ce4a9f 100644
--- a/django/views/generic/list_detail.py
+++ b/django/views/generic/list_detail.py
@@ -84,7 +84,7 @@ def object_detail(request, queryset, object_id=None, slug=None,
context_processors=None, template_object_name='object',
mimetype=None):
"""
- Generic list of objects.
+ Generic detail of an object.
Templates: ``<app_label>/<model_name>_detail.html``
Context:
diff --git a/docs/add_ons.txt b/docs/add_ons.txt
index a0377700d7..58c01c4fc0 100644
--- a/docs/add_ons.txt
+++ b/docs/add_ons.txt
@@ -48,6 +48,23 @@ See the `csrf documentation`_.
.. _csrf documentation: http://www.djangoproject.com/documentation/csrf/
+formtools
+=========
+
+**New in Django development version**
+
+A set of high-level abstractions for Django forms (django.newforms).
+
+django.contrib.formtools.preview
+--------------------------------
+
+An abstraction of the following workflow:
+
+"Display an HTML form, force a preview, then do something with the submission."
+
+Full documentation for this feature does not yet exist, but you can read the
+code and docstrings in ``django/contrib/formtools/preview.py`` for a start.
+
humanize
========
diff --git a/docs/newforms.txt b/docs/newforms.txt
new file mode 100644
index 0000000000..9bfbc75ee7
--- /dev/null
+++ b/docs/newforms.txt
@@ -0,0 +1,79 @@
+====================
+The newforms library
+====================
+
+``django.newforms`` is a new replacement for ``django.forms``, the old Django
+form/manipulator/validation framework. This document explains how to use this
+new form library.
+
+Migration plan
+==============
+
+``django.newforms`` currently is only available in the Django development version
+-- i.e., it's not available in the Django 0.95 release. For the next Django
+release, our plan is to do the following:
+
+ * Move the current ``django.forms`` to ``django.oldforms``. This will allow
+ for an eased migration of form code. You'll just have to change your
+ import statements::
+
+ from django import forms # old
+ from django import oldforms as forms # new
+
+ * Move the current ``django.newforms`` to ``django.forms``.
+
+ * We will remove ``django.oldforms`` in the release *after* the next Django
+ release -- the release that comes after the release in which we're
+ creating ``django.oldforms``.
+
+With this in mind, we recommend you use the following import statement when
+using ``django.newforms``::
+
+ from django import newforms as forms
+
+This way, your code can refer to the ``forms`` module, and when
+``django.newforms`` is renamed to ``django.forms``, you'll only have to change
+your ``import`` statements.
+
+If you prefer "``import *``" syntax, you can do the following::
+
+ from django.newforms import *
+
+This will import all fields, widgets, form classes and other various utilities
+into your local namespace. Some people find this convenient; others find it
+too messy. The choice is yours.
+
+Overview
+========
+
+As the ``django.forms`` system before it, ``django.newforms`` is intended to
+handle HTML form display, validation and redisplay. It's what you use if you
+want to perform server-side validation for an HTML form.
+
+The library deals with these concepts:
+
+ * **Widget** -- A class that corresponds to an HTML form widget, e.g.
+ ``<input type="text">`` or ``<textarea>``. This handles rendering of the
+ widget as HTML.
+
+ * **Field** -- A class that is responsible for doing validation, e.g.
+ an ``EmailField`` that makes sure its data is a valid e-mail address.
+
+ * **Form** -- A collection of fields that knows how to validate itself and
+ display itself as HTML.
+
+
+
+Using forms with templates
+==========================
+
+Using forms in views
+====================
+
+More coming soon
+================
+
+That's all the documentation for now. For more, see the file
+http://code.djangoproject.com/browser/django/trunk/tests/regressiontests/forms/tests.py
+-- the unit tests for ``django.newforms``. This can give you a good idea of
+what's possible.
diff --git a/docs/sessions.txt b/docs/sessions.txt
index d39f42c3bf..dd4a581d91 100644
--- a/docs/sessions.txt
+++ b/docs/sessions.txt
@@ -141,7 +141,7 @@ Do this after you've verified that the test cookie worked.
Here's a typical usage example::
def login(request):
- if request.POST:
+ if request.method == 'POST':
if request.session.test_cookie_worked():
request.session.delete_test_cookie()
return HttpResponse("You're logged in.")
diff --git a/docs/settings.txt b/docs/settings.txt
index ff1e2eeca2..00672060db 100644
--- a/docs/settings.txt
+++ b/docs/settings.txt
@@ -830,7 +830,7 @@ Default: ``Django/<version> (http://www.djangoproject.com/)``
The string to use as the ``User-Agent`` header when checking to see if URLs
exist (see the ``verify_exists`` option on URLField_).
-.. URLField: ../model_api/#urlfield
+.. _URLField: ../model_api/#urlfield
USE_ETAGS
---------
diff --git a/docs/sitemaps.txt b/docs/sitemaps.txt
index fec65572f2..7414567b16 100644
--- a/docs/sitemaps.txt
+++ b/docs/sitemaps.txt
@@ -5,9 +5,9 @@ The sitemap framework
**New in Django development version**.
Django comes with a high-level sitemap-generating framework that makes
-creating `Google Sitemap`_ XML files easy.
+creating sitemap_ XML files easy.
-.. _Google Sitemap: http://www.google.com/webmasters/sitemaps/docs/en/protocol.html
+.. _sitemap: http://www.sitemaps.org/
Overview
========
@@ -55,11 +55,12 @@ URLconf_:
This tells Django to build a sitemap when a client accesses ``/sitemap.xml``.
-The name of the sitemap file is not important, but the location is. Google will
-only index links in your sitemap for the current URL level and below. For
-instance, if ``sitemap.xml`` lives in your root directory, it may reference any
-URL in your site. However, if your sitemap lives at ``/content/sitemap.xml``,
-it may only reference URLs that begin with ``/content/``.
+The name of the sitemap file is not important, but the location is. Search
+engines will only index links in your sitemap for the current URL level and
+below. For instance, if ``sitemap.xml`` lives in your root directory, it may
+reference any URL in your site. However, if your sitemap lives at
+``/content/sitemap.xml``, it may only reference URLs that begin with
+``/content/``.
The sitemap view takes an extra, required argument: ``{'sitemaps': sitemaps}``.
``sitemaps`` should be a dictionary that maps a short section label (e.g.,
@@ -199,9 +200,9 @@ If it's an attribute, its value should be either a string or float representing
the priority of *every* object returned by ``items()``.
Example values for ``priority``: ``0.4``, ``1.0``. The default priority of a
-page is ``0.5``. See Google's documentation for more documentation.
+page is ``0.5``. See the `sitemaps.org documentation`_ for more.
-.. _Google's documentation: http://www.google.com/webmasters/sitemaps/docs/en/protocol.html
+.. _sitemaps.org documentation: http://www.sitemaps.org/protocol.html#prioritydef
Shortcuts
=========
diff --git a/docs/templates.txt b/docs/templates.txt
index 7a580bd0db..b4cc47b9f3 100644
--- a/docs/templates.txt
+++ b/docs/templates.txt
@@ -571,7 +571,7 @@ The arguments can be hard-coded strings, so the following is valid::
It is only possible to compare an argument to template variables or strings.
You cannot check for equality with Python objects such as ``True`` or
``False``. If you need to test if something is true or false, use the ``if``
-and ``ifnot`` tags instead.
+tag instead.
ifnotequal
~~~~~~~~~~
diff --git a/docs/templates_python.txt b/docs/templates_python.txt
index ae2582d7b8..7aeed935b9 100644
--- a/docs/templates_python.txt
+++ b/docs/templates_python.txt
@@ -321,7 +321,7 @@ Note::
def some_view(request):
# ...
- return render_to_response('my_template'html',
+ return render_to_response('my_template.html',
my_data_dictionary,
context_instance=RequestContext(request))
diff --git a/docs/testing.txt b/docs/testing.txt
index 19eef9f071..a0b8a8a187 100644
--- a/docs/testing.txt
+++ b/docs/testing.txt
@@ -10,7 +10,7 @@ used to validate that code behaves as expected. When refactoring or
modifying code, tests serve as a guide to ensure that behavior hasn't
changed unexpectedly as a result of the refactor.
-Testing an web application is a complex task, as there are many
+Testing a web application is a complex task, as there are many
components of a web application that must be validated and tested. To
help you test your application, Django provides a test execution
framework, and range of utilities that can be used to stimulate and
@@ -133,7 +133,7 @@ together, picking the test system to match the type of tests you need to
write.
For developers new to testing, however, this choice can seem
-confusing, so here are a few key differences to help you decide weather
+confusing, so here are a few key differences to help you decide whether
doctests or unit tests are right for you.
If you've been using Python for a while, ``doctest`` will probably feel more
diff --git a/setup.py b/setup.py
index 9dd5fd9144..9c189ebf75 100644
--- a/setup.py
+++ b/setup.py
@@ -11,13 +11,17 @@ for scheme in INSTALL_SCHEMES.values():
# Compile the list of packages available, because distutils doesn't have
# an easy way to do this.
packages, data_files = [], []
-root_dir = os.path.join(os.path.dirname(__file__), 'django')
-for dirpath, dirnames, filenames in os.walk(root_dir):
+root_dir = os.path.dirname(__file__)
+len_root_dir = len(root_dir)
+django_dir = os.path.join(root_dir, 'django')
+
+for dirpath, dirnames, filenames in os.walk(django_dir):
# Ignore dirnames that start with '.'
for i, dirname in enumerate(dirnames):
if dirname.startswith('.'): del dirnames[i]
if '__init__.py' in filenames:
- packages.append(dirpath.replace('/', '.'))
+ package = dirpath[len_root_dir:].lstrip('/').replace('/', '.')
+ packages.append(package)
else:
data_files.append((dirpath, [os.path.join(dirpath, f) for f in filenames]))
diff --git a/tests/regressiontests/forms/tests.py b/tests/regressiontests/forms/tests.py
index 5402f654e9..779db97528 100644
--- a/tests/regressiontests/forms/tests.py
+++ b/tests/regressiontests/forms/tests.py
@@ -1,8 +1,17 @@
-"""
+# -*- coding: utf-8 -*-
+r"""
>>> from django.newforms import *
>>> import datetime
>>> import re
+###########
+# Widgets #
+###########
+
+Each Widget class corresponds to an HTML form widget. A Widget knows how to
+render itself, given a field name and some data. Widgets don't perform
+validation.
+
# TextInput Widget ############################################################
>>> w = TextInput()
@@ -17,6 +26,11 @@ u'<input type="text" name="email" value="some &quot;quoted&quot; &amp; ampersand
>>> w.render('email', 'test@example.com', attrs={'class': 'fun'})
u'<input type="text" name="email" value="test@example.com" class="fun" />'
+# Note that doctest in Python 2.4 (and maybe 2.5?) doesn't support non-ascii
+# characters in output, so we're displaying the repr() here.
+>>> w.render('email', 'Å ÄĆŽćžšđ', attrs={'class': 'fun'})
+u'<input type="text" name="email" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" class="fun" />'
+
You can also pass 'attrs' to the constructor:
>>> w = TextInput(attrs={'class': 'fun'})
>>> w.render('email', '')
@@ -55,6 +69,9 @@ u'<input type="password" class="fun" value="foo@example.com" name="email" />'
>>> w.render('email', '', attrs={'class': 'special'})
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" />'
+
# HiddenInput Widget ############################################################
>>> w = HiddenInput()
@@ -81,6 +98,14 @@ u'<input type="hidden" class="fun" value="foo@example.com" name="email" />'
>>> w.render('email', '', attrs={'class': 'special'})
u'<input type="hidden" class="special" 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 = HiddenInput(attrs={'class': 'pretty'})
+>>> w.render('email', '', attrs={'class': 'special'})
+u'<input type="hidden" class="special" name="email" />'
+
# FileInput Widget ############################################################
>>> w = FileInput()
@@ -102,10 +127,8 @@ u'<input type="file" class="fun" name="email" />'
>>> w.render('email', 'foo@example.com')
u'<input type="file" class="fun" value="foo@example.com" name="email" />'
-'attrs' passed to render() get precedence over those passed to the constructor:
->>> w = HiddenInput(attrs={'class': 'pretty'})
->>> w.render('email', '', attrs={'class': 'special'})
-u'<input type="hidden" class="special" name="email" />'
+>>> w.render('email', 'Å ÄĆŽćžšđ', attrs={'class': 'fun'})
+u'<input type="file" class="fun" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" name="email" />'
# Textarea Widget #############################################################
@@ -133,15 +156,26 @@ u'<textarea class="pretty" name="msg">example</textarea>'
>>> w.render('msg', '', attrs={'class': 'special'})
u'<textarea class="special" name="msg"></textarea>'
+>>> w.render('msg', 'Å ÄĆŽćžšđ', attrs={'class': 'fun'})
+u'<textarea class="fun" name="msg">\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111</textarea>'
+
# CheckboxInput Widget ########################################################
>>> w = CheckboxInput()
>>> w.render('is_cool', '')
u'<input type="checkbox" name="is_cool" />'
+>>> w.render('is_cool', None)
+u'<input type="checkbox" name="is_cool" />'
>>> w.render('is_cool', False)
u'<input type="checkbox" name="is_cool" />'
>>> w.render('is_cool', True)
u'<input checked="checked" type="checkbox" name="is_cool" />'
+
+Using any value that's not in ('', None, False, True) will check the checkbox
+and set the 'value' attribute.
+>>> w.render('is_cool', 'foo')
+u'<input checked="checked" type="checkbox" name="is_cool" value="foo" />'
+
>>> w.render('is_cool', False, attrs={'class': 'pretty'})
u'<input type="checkbox" name="is_cool" class="pretty" />'
@@ -155,6 +189,29 @@ u'<input type="checkbox" class="pretty" name="is_cool" />'
>>> w.render('is_cool', '', attrs={'class': 'special'})
u'<input type="checkbox" class="special" name="is_cool" />'
+You can pass 'check_test' to the constructor. This is a callable that takes the
+value and returns True if the box should be checked.
+>>> w = CheckboxInput(check_test=lambda value: value.startswith('hello'))
+>>> w.render('greeting', '')
+u'<input type="checkbox" name="greeting" />'
+>>> w.render('greeting', 'hello')
+u'<input checked="checked" type="checkbox" name="greeting" value="hello" />'
+>>> w.render('greeting', 'hello there')
+u'<input checked="checked" type="checkbox" name="greeting" value="hello there" />'
+>>> w.render('greeting', 'hello & goodbye')
+u'<input checked="checked" type="checkbox" name="greeting" value="hello &amp; goodbye" />'
+
+A subtlety: If the 'check_test' argument cannot handle a value and raises any
+exception during its __call__, then the exception will be swallowed and the box
+will not be checked. In this example, the 'check_test' assumes the value has a
+startswith() method, which fails for the values True, False and None.
+>>> w.render('greeting', True)
+u'<input type="checkbox" name="greeting" />'
+>>> w.render('greeting', False)
+u'<input type="checkbox" name="greeting" />'
+>>> w.render('greeting', None)
+u'<input type="checkbox" name="greeting" />'
+
# Select Widget ###############################################################
>>> w = Select()
@@ -236,6 +293,9 @@ If 'choices' is passed to both the constructor and render(), then they'll both b
<option value="5">5</option>
</select>
+>>> 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>'
+
# SelectMultiple Widget #######################################################
>>> w = SelectMultiple()
@@ -340,8 +400,264 @@ If 'choices' is passed to both the constructor and render(), then they'll both b
<option value="5">5</option>
</select>
+>>> w.render('nums', ['Å ÄĆŽćžšđ'], choices=[('Å ÄĆŽćžšđ', 'Å ÄabcĆŽćžšđ'), ('ćžšđ', 'abcćžšđ')])
+u'<select multiple="multiple" name="nums">\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>'
+
+# RadioSelect Widget ##########################################################
+
+>>> w = RadioSelect()
+>>> print w.render('beatle', 'J', choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo')))
+<ul>
+<li><label><input checked="checked" type="radio" name="beatle" value="J" /> John</label></li>
+<li><label><input type="radio" name="beatle" value="P" /> Paul</label></li>
+<li><label><input type="radio" name="beatle" value="G" /> George</label></li>
+<li><label><input type="radio" name="beatle" value="R" /> Ringo</label></li>
+</ul>
+
+If the value is None, none of the options are checked:
+>>> print w.render('beatle', None, choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo')))
+<ul>
+<li><label><input type="radio" name="beatle" value="J" /> John</label></li>
+<li><label><input type="radio" name="beatle" value="P" /> Paul</label></li>
+<li><label><input type="radio" name="beatle" value="G" /> George</label></li>
+<li><label><input type="radio" name="beatle" value="R" /> Ringo</label></li>
+</ul>
+
+If the value corresponds to a label (but not to an option value), none of the options are checked:
+>>> print w.render('beatle', 'John', choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo')))
+<ul>
+<li><label><input type="radio" name="beatle" value="J" /> John</label></li>
+<li><label><input type="radio" name="beatle" value="P" /> Paul</label></li>
+<li><label><input type="radio" name="beatle" value="G" /> George</label></li>
+<li><label><input type="radio" name="beatle" value="R" /> Ringo</label></li>
+</ul>
+
+The value is compared to its str():
+>>> print w.render('num', 2, choices=[('1', '1'), ('2', '2'), ('3', '3')])
+<ul>
+<li><label><input type="radio" name="num" value="1" /> 1</label></li>
+<li><label><input checked="checked" type="radio" name="num" value="2" /> 2</label></li>
+<li><label><input type="radio" name="num" value="3" /> 3</label></li>
+</ul>
+>>> print w.render('num', '2', choices=[(1, 1), (2, 2), (3, 3)])
+<ul>
+<li><label><input type="radio" name="num" value="1" /> 1</label></li>
+<li><label><input checked="checked" type="radio" name="num" value="2" /> 2</label></li>
+<li><label><input type="radio" name="num" value="3" /> 3</label></li>
+</ul>
+>>> print w.render('num', 2, choices=[(1, 1), (2, 2), (3, 3)])
+<ul>
+<li><label><input type="radio" name="num" value="1" /> 1</label></li>
+<li><label><input checked="checked" type="radio" name="num" value="2" /> 2</label></li>
+<li><label><input type="radio" name="num" value="3" /> 3</label></li>
+</ul>
+
+The 'choices' argument can be any iterable:
+>>> def get_choices():
+... for i in range(5):
+... yield (i, i)
+>>> print w.render('num', 2, choices=get_choices())
+<ul>
+<li><label><input type="radio" name="num" value="0" /> 0</label></li>
+<li><label><input type="radio" name="num" value="1" /> 1</label></li>
+<li><label><input checked="checked" type="radio" name="num" value="2" /> 2</label></li>
+<li><label><input type="radio" name="num" value="3" /> 3</label></li>
+<li><label><input type="radio" name="num" value="4" /> 4</label></li>
+</ul>
+
+You can also pass 'choices' to the constructor:
+>>> w = RadioSelect(choices=[(1, 1), (2, 2), (3, 3)])
+>>> print w.render('num', 2)
+<ul>
+<li><label><input type="radio" name="num" value="1" /> 1</label></li>
+<li><label><input checked="checked" type="radio" name="num" value="2" /> 2</label></li>
+<li><label><input type="radio" name="num" value="3" /> 3</label></li>
+</ul>
+
+If 'choices' is passed to both the constructor and render(), then they'll both be in the output:
+>>> print w.render('num', 2, choices=[(4, 4), (5, 5)])
+<ul>
+<li><label><input type="radio" name="num" value="1" /> 1</label></li>
+<li><label><input checked="checked" type="radio" name="num" value="2" /> 2</label></li>
+<li><label><input type="radio" name="num" value="3" /> 3</label></li>
+<li><label><input type="radio" name="num" value="4" /> 4</label></li>
+<li><label><input type="radio" name="num" value="5" /> 5</label></li>
+</ul>
+
+The render() method returns a RadioFieldRenderer object, whose str() is a <ul>.
+You can manipulate that object directly to customize the way the RadioSelect
+is rendered.
+>>> w = RadioSelect()
+>>> r = w.render('beatle', 'J', choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo')))
+>>> for inp in r:
+... print inp
+<label><input checked="checked" type="radio" name="beatle" value="J" /> John</label>
+<label><input type="radio" name="beatle" value="P" /> Paul</label>
+<label><input type="radio" name="beatle" value="G" /> George</label>
+<label><input type="radio" name="beatle" value="R" /> Ringo</label>
+>>> for inp in r:
+... print '%s<br />' % inp
+<label><input checked="checked" type="radio" name="beatle" value="J" /> John</label><br />
+<label><input type="radio" name="beatle" value="P" /> Paul</label><br />
+<label><input type="radio" name="beatle" value="G" /> George</label><br />
+<label><input type="radio" name="beatle" value="R" /> Ringo</label><br />
+>>> for inp in r:
+... print '<p>%s %s</p>' % (inp.tag(), inp.choice_label)
+<p><input checked="checked" type="radio" name="beatle" value="J" /> John</p>
+<p><input type="radio" name="beatle" value="P" /> Paul</p>
+<p><input type="radio" name="beatle" value="G" /> George</p>
+<p><input type="radio" name="beatle" value="R" /> Ringo</p>
+>>> for inp in r:
+... print '%s %s %s %s %s' % (inp.name, inp.value, inp.choice_value, inp.choice_label, inp.is_checked())
+beatle J J John True
+beatle J P Paul False
+beatle J G George False
+beatle J R Ringo False
+
+# CheckboxSelectMultiple Widget ###############################################
+
+>>> w = CheckboxSelectMultiple()
+>>> print w.render('beatles', ['J'], choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo')))
+<ul>
+<li><label><input checked="checked" type="checkbox" name="beatles" value="J" /> John</label></li>
+<li><label><input type="checkbox" name="beatles" value="P" /> Paul</label></li>
+<li><label><input type="checkbox" name="beatles" value="G" /> George</label></li>
+<li><label><input type="checkbox" name="beatles" value="R" /> Ringo</label></li>
+</ul>
+>>> print w.render('beatles', ['J', 'P'], choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo')))
+<ul>
+<li><label><input checked="checked" type="checkbox" name="beatles" value="J" /> John</label></li>
+<li><label><input checked="checked" type="checkbox" name="beatles" value="P" /> Paul</label></li>
+<li><label><input type="checkbox" name="beatles" value="G" /> George</label></li>
+<li><label><input type="checkbox" name="beatles" value="R" /> Ringo</label></li>
+</ul>
+>>> print w.render('beatles', ['J', 'P', 'R'], choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo')))
+<ul>
+<li><label><input checked="checked" type="checkbox" name="beatles" value="J" /> John</label></li>
+<li><label><input checked="checked" type="checkbox" name="beatles" value="P" /> Paul</label></li>
+<li><label><input type="checkbox" name="beatles" value="G" /> George</label></li>
+<li><label><input checked="checked" type="checkbox" name="beatles" value="R" /> Ringo</label></li>
+</ul>
+
+If the value is None, none of the options are selected:
+>>> print w.render('beatles', None, choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo')))
+<ul>
+<li><label><input type="checkbox" name="beatles" value="J" /> John</label></li>
+<li><label><input type="checkbox" name="beatles" value="P" /> Paul</label></li>
+<li><label><input type="checkbox" name="beatles" value="G" /> George</label></li>
+<li><label><input type="checkbox" name="beatles" value="R" /> Ringo</label></li>
+</ul>
+
+If the value corresponds to a label (but not to an option value), none of the options are selected:
+>>> print w.render('beatles', ['John'], choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo')))
+<ul>
+<li><label><input type="checkbox" name="beatles" value="J" /> John</label></li>
+<li><label><input type="checkbox" name="beatles" value="P" /> Paul</label></li>
+<li><label><input type="checkbox" name="beatles" value="G" /> George</label></li>
+<li><label><input type="checkbox" name="beatles" value="R" /> Ringo</label></li>
+</ul>
+
+If multiple values are given, but some of them are not valid, the valid ones are selected:
+>>> print w.render('beatles', ['J', 'G', 'foo'], choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo')))
+<ul>
+<li><label><input checked="checked" type="checkbox" name="beatles" value="J" /> John</label></li>
+<li><label><input type="checkbox" name="beatles" value="P" /> Paul</label></li>
+<li><label><input checked="checked" type="checkbox" name="beatles" value="G" /> George</label></li>
+<li><label><input type="checkbox" name="beatles" value="R" /> Ringo</label></li>
+</ul>
+
+The value is compared to its str():
+>>> print w.render('nums', [2], choices=[('1', '1'), ('2', '2'), ('3', '3')])
+<ul>
+<li><label><input type="checkbox" name="nums" value="1" /> 1</label></li>
+<li><label><input checked="checked" type="checkbox" name="nums" value="2" /> 2</label></li>
+<li><label><input type="checkbox" name="nums" value="3" /> 3</label></li>
+</ul>
+>>> print w.render('nums', ['2'], choices=[(1, 1), (2, 2), (3, 3)])
+<ul>
+<li><label><input type="checkbox" name="nums" value="1" /> 1</label></li>
+<li><label><input checked="checked" type="checkbox" name="nums" value="2" /> 2</label></li>
+<li><label><input type="checkbox" name="nums" value="3" /> 3</label></li>
+</ul>
+>>> print w.render('nums', [2], choices=[(1, 1), (2, 2), (3, 3)])
+<ul>
+<li><label><input type="checkbox" name="nums" value="1" /> 1</label></li>
+<li><label><input checked="checked" type="checkbox" name="nums" value="2" /> 2</label></li>
+<li><label><input type="checkbox" name="nums" value="3" /> 3</label></li>
+</ul>
+
+The 'choices' argument can be any iterable:
+>>> def get_choices():
+... for i in range(5):
+... yield (i, i)
+>>> print w.render('nums', [2], choices=get_choices())
+<ul>
+<li><label><input type="checkbox" name="nums" value="0" /> 0</label></li>
+<li><label><input type="checkbox" name="nums" value="1" /> 1</label></li>
+<li><label><input checked="checked" type="checkbox" name="nums" value="2" /> 2</label></li>
+<li><label><input type="checkbox" name="nums" value="3" /> 3</label></li>
+<li><label><input type="checkbox" name="nums" value="4" /> 4</label></li>
+</ul>
+
+You can also pass 'choices' to the constructor:
+>>> w = CheckboxSelectMultiple(choices=[(1, 1), (2, 2), (3, 3)])
+>>> print w.render('nums', [2])
+<ul>
+<li><label><input type="checkbox" name="nums" value="1" /> 1</label></li>
+<li><label><input checked="checked" type="checkbox" name="nums" value="2" /> 2</label></li>
+<li><label><input type="checkbox" name="nums" value="3" /> 3</label></li>
+</ul>
+
+If 'choices' is passed to both the constructor and render(), then they'll both be in the output:
+>>> print w.render('nums', [2], choices=[(4, 4), (5, 5)])
+<ul>
+<li><label><input type="checkbox" name="nums" value="1" /> 1</label></li>
+<li><label><input checked="checked" type="checkbox" name="nums" value="2" /> 2</label></li>
+<li><label><input type="checkbox" name="nums" value="3" /> 3</label></li>
+<li><label><input type="checkbox" name="nums" value="4" /> 4</label></li>
+<li><label><input type="checkbox" name="nums" value="5" /> 5</label></li>
+</ul>
+
+>>> 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>'
+
+##########
+# Fields #
+##########
+
+Each Field class does some sort of validation. Each Field has a clean() method,
+which either raises django.newforms.ValidationError or returns the "clean"
+data -- usually a Unicode object, but, in some rare cases, a list.
+
+Each Field's __init__() takes at least these parameters:
+ required -- Boolean that specifies whether the field is required.
+ True by default.
+ widget -- A Widget class, or instance of a Widget class, that should be
+ used for this Field when displaying it. Each Field has a default
+ Widget that it'll use if you don't specify this. In most cases,
+ the default widget is TextInput.
+
+Other than that, the Field subclasses have class-specific options for
+__init__(). For example, CharField has a max_length option.
+
# CharField ###################################################################
+>>> f = CharField()
+>>> f.clean(1)
+u'1'
+>>> f.clean('hello')
+u'hello'
+>>> f.clean(None)
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+>>> f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+>>> f.clean([1, 2, 3])
+u'[1, 2, 3]'
+
>>> f = CharField(required=False)
>>> f.clean(1)
u'1'
@@ -349,13 +665,13 @@ u'1'
u'hello'
>>> f.clean(None)
u''
+>>> f.clean('')
+u''
>>> f.clean([1, 2, 3])
u'[1, 2, 3]'
CharField accepts an optional max_length parameter:
>>> f = CharField(max_length=10, required=False)
->>> f.clean('')
-u''
>>> f.clean('12345')
u'12345'
>>> f.clean('1234567890')
@@ -383,6 +699,40 @@ u'1234567890a'
# IntegerField ################################################################
>>> f = IntegerField()
+>>> f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+>>> f.clean(None)
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+>>> f.clean('1')
+1
+>>> isinstance(f.clean('1'), int)
+True
+>>> f.clean('23')
+23
+>>> f.clean('a')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a whole number.']
+>>> f.clean('1 ')
+1
+>>> f.clean(' 1')
+1
+>>> f.clean(' 1 ')
+1
+>>> f.clean('1a')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a whole number.']
+
+>>> f = IntegerField(required=False)
+>>> f.clean('')
+u''
+>>> f.clean(None)
+u''
>>> f.clean('1')
1
>>> isinstance(f.clean('1'), int)
@@ -546,6 +896,14 @@ Traceback (most recent call last):
...
ValidationError: [u'Enter a valid date/time.']
+>>> f = DateTimeField(required=False)
+>>> f.clean(None)
+>>> repr(f.clean(None))
+'None'
+>>> f.clean('')
+>>> repr(f.clean(''))
+'None'
+
# RegexField ##################################################################
>>> f = RegexField('^\d[A-F]\d$')
@@ -565,6 +923,22 @@ ValidationError: [u'Enter a valid value.']
Traceback (most recent call last):
...
ValidationError: [u'Enter a valid value.']
+>>> f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+
+>>> f = RegexField('^\d[A-F]\d$', required=False)
+>>> f.clean('2A2')
+u'2A2'
+>>> f.clean('3F3')
+u'3F3'
+>>> f.clean('3G3')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid value.']
+>>> f.clean('')
+u''
Alternatively, RegexField can take a compiled regular expression:
>>> f = RegexField(re.compile('^\d[A-F]\d$'))
@@ -601,6 +975,34 @@ ValidationError: [u'Enter a four-digit number.']
# EmailField ##################################################################
>>> f = EmailField()
+>>> f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+>>> f.clean(None)
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+>>> f.clean('person@example.com')
+u'person@example.com'
+>>> f.clean('foo')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid e-mail address.']
+>>> f.clean('foo@')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid e-mail address.']
+>>> f.clean('foo@bar')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid e-mail address.']
+
+>>> f = EmailField(required=False)
+>>> f.clean('')
+u''
+>>> f.clean(None)
+u''
>>> f.clean('person@example.com')
u'person@example.com'
>>> f.clean('foo')
@@ -619,6 +1021,48 @@ ValidationError: [u'Enter a valid e-mail address.']
# URLField ##################################################################
>>> f = URLField()
+>>> f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+>>> f.clean(None)
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+>>> f.clean('http://example.com')
+u'http://example.com'
+>>> f.clean('http://www.example.com')
+u'http://www.example.com'
+>>> f.clean('foo')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid URL.']
+>>> f.clean('example.com')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid URL.']
+>>> f.clean('http://')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid URL.']
+>>> f.clean('http://example')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid URL.']
+>>> f.clean('http://example.')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid URL.']
+>>> f.clean('http://.com')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid URL.']
+
+>>> f = URLField(required=False)
+>>> f.clean('')
+u''
+>>> f.clean(None)
+u''
>>> f.clean('http://example.com')
u'http://example.com'
>>> f.clean('http://www.example.com')
@@ -669,6 +1113,30 @@ ValidationError: [u'This URL appears to be a broken link.']
# BooleanField ################################################################
>>> f = BooleanField()
+>>> f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+>>> f.clean(None)
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+>>> f.clean(True)
+True
+>>> f.clean(False)
+False
+>>> f.clean(1)
+True
+>>> f.clean(0)
+False
+>>> f.clean('Django rocks')
+True
+
+>>> f = BooleanField(required=False)
+>>> f.clean('')
+False
+>>> f.clean(None)
+False
>>> f.clean(True)
True
>>> f.clean(False)
@@ -683,18 +1151,32 @@ True
# ChoiceField #################################################################
>>> f = ChoiceField(choices=[('1', '1'), ('2', '2')])
+>>> f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+>>> f.clean(None)
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
>>> f.clean(1)
u'1'
>>> f.clean('1')
u'1'
->>> f.clean(None)
+>>> f.clean('3')
Traceback (most recent call last):
...
-ValidationError: [u'This field is required.']
+ValidationError: [u'Select a valid choice. 3 is not one of the available choices.']
+
+>>> f = ChoiceField(choices=[('1', '1'), ('2', '2')], required=False)
>>> f.clean('')
-Traceback (most recent call last):
-...
-ValidationError: [u'This field is required.']
+u''
+>>> f.clean(None)
+u''
+>>> f.clean(1)
+u'1'
+>>> f.clean('1')
+u'1'
>>> f.clean('3')
Traceback (most recent call last):
...
@@ -711,6 +1193,14 @@ ValidationError: [u'Select a valid choice. John is not one of the available choi
# MultipleChoiceField #########################################################
>>> f = MultipleChoiceField(choices=[('1', '1'), ('2', '2')])
+>>> f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+>>> f.clean(None)
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
>>> f.clean([1])
[u'1']
>>> f.clean(['1'])
@@ -738,10 +1228,38 @@ Traceback (most recent call last):
...
ValidationError: [u'Select a valid choice. 3 is not one of the available choices.']
+>>> f = MultipleChoiceField(choices=[('1', '1'), ('2', '2')], required=False)
+>>> f.clean('')
+[]
+>>> f.clean(None)
+[]
+>>> f.clean([1])
+[u'1']
+>>> f.clean(['1'])
+[u'1']
+>>> f.clean(['1', '2'])
+[u'1', u'2']
+>>> f.clean([1, '2'])
+[u'1', u'2']
+>>> f.clean((1, '2'))
+[u'1', u'2']
+>>> f.clean('hello')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a list of values.']
+>>> f.clean([])
+[]
+>>> f.clean(())
+[]
+>>> f.clean(['3'])
+Traceback (most recent call last):
+...
+ValidationError: [u'Select a valid choice. 3 is not one of the available choices.']
+
# ComboField ##################################################################
ComboField takes a list of fields that should be used to validate a value,
-in that order:
+in that order.
>>> f = ComboField(fields=[CharField(max_length=20), EmailField()])
>>> f.clean('test@example.com')
u'test@example.com'
@@ -762,57 +1280,48 @@ Traceback (most recent call last):
...
ValidationError: [u'This field is required.']
+>>> f = ComboField(fields=[CharField(max_length=20), EmailField()], required=False)
+>>> f.clean('test@example.com')
+u'test@example.com'
+>>> f.clean('longemailaddress@example.com')
+Traceback (most recent call last):
+...
+ValidationError: [u'Ensure this value has at most 20 characters.']
+>>> f.clean('not an e-mail')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid e-mail address.']
+>>> f.clean('')
+u''
+>>> f.clean(None)
+u''
+
+#########
+# Forms #
+#########
+
+A Form is a collection of Fields. It knows how to validate a set of data and it
+knows how to render itself in a couple of default ways (e.g., an HTML table).
+You can pass it data in __init__(), as a dictionary.
+
# Form ########################################################################
>>> class Person(Form):
... first_name = CharField()
... last_name = CharField()
... birthday = DateField()
->>> p = Person()
->>> print p
-<table>
-<tr><td>First name:</td><td><input type="text" name="first_name" /></td></tr>
-<tr><td>Last name:</td><td><input type="text" name="last_name" /></td></tr>
-<tr><td>Birthday:</td><td><input type="text" name="birthday" /></td></tr>
-</table>
->>> print p.as_table()
-<table>
-<tr><td>First name:</td><td><input type="text" name="first_name" /></td></tr>
-<tr><td>Last name:</td><td><input type="text" name="last_name" /></td></tr>
-<tr><td>Birthday:</td><td><input type="text" name="birthday" /></td></tr>
-</table>
->>> print p.as_ul()
-<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>
-</ul>
->>> print p.as_table_with_errors()
-<table>
-<tr><td colspan="2"><ul><li>This field is required.</li></ul></td></tr>
-<tr><td>First name:</td><td><input type="text" name="first_name" /></td></tr>
-<tr><td colspan="2"><ul><li>This field is required.</li></ul></td></tr>
-<tr><td>Last name:</td><td><input type="text" name="last_name" /></td></tr>
-<tr><td colspan="2"><ul><li>This field is required.</li></ul></td></tr>
-<tr><td>Birthday:</td><td><input type="text" name="birthday" /></td></tr>
-</table>
->>> print p.as_ul_with_errors()
-<ul>
-<li><ul><li>This field is required.</li></ul>First name: <input type="text" name="first_name" /></li>
-<li><ul><li>This field is required.</li></ul>Last name: <input type="text" name="last_name" /></li>
-<li><ul><li>This field is required.</li></ul>Birthday: <input type="text" name="birthday" /></li>
-</ul>
+Pass a dictionary to a Form's __init__().
>>> p = Person({'first_name': u'John', 'last_name': u'Lennon', 'birthday': u'1940-10-9'})
->>> p.errors()
+>>> p.errors
{}
>>> p.is_valid()
True
->>> p.errors().as_ul()
+>>> p.errors.as_ul()
u''
->>> p.errors().as_text()
+>>> p.errors.as_text()
u''
->>> p.clean()
+>>> p.clean_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" />
@@ -825,27 +1334,96 @@ u''
<input type="text" name="first_name" value="John" />
<input type="text" name="last_name" value="Lennon" />
<input type="text" name="birthday" value="1940-10-9" />
+>>> for boundfield in p:
+... print boundfield.verbose_name, boundfield.data
+First name John
+Last name Lennon
+Birthday 1940-10-9
>>> print p
-<table>
<tr><td>First name:</td><td><input type="text" name="first_name" value="John" /></td></tr>
<tr><td>Last name:</td><td><input type="text" name="last_name" value="Lennon" /></td></tr>
<tr><td>Birthday:</td><td><input type="text" name="birthday" value="1940-10-9" /></td></tr>
-</table>
+
+Empty dictionaries are valid, too.
+>>> p = Person({})
+>>> p.errors
+{'first_name': [u'This field is required.'], 'last_name': [u'This field is required.'], 'birthday': [u'This field is required.']}
+>>> p.is_valid()
+False
+>>> print p
+<tr><td colspan="2"><ul class="errorlist"><li>This field is required.</li></ul></td></tr>
+<tr><td>First name:</td><td><input type="text" name="first_name" /></td></tr>
+<tr><td colspan="2"><ul class="errorlist"><li>This field is required.</li></ul></td></tr>
+<tr><td>Last name:</td><td><input type="text" name="last_name" /></td></tr>
+<tr><td colspan="2"><ul class="errorlist"><li>This field is required.</li></ul></td></tr>
+<tr><td>Birthday:</td><td><input type="text" name="birthday" /></td></tr>
+>>> print p.as_table()
+<tr><td colspan="2"><ul class="errorlist"><li>This field is required.</li></ul></td></tr>
+<tr><td>First name:</td><td><input type="text" name="first_name" /></td></tr>
+<tr><td colspan="2"><ul class="errorlist"><li>This field is required.</li></ul></td></tr>
+<tr><td>Last name:</td><td><input type="text" name="last_name" /></td></tr>
+<tr><td colspan="2"><ul class="errorlist"><li>This field is required.</li></ul></td></tr>
+<tr><td>Birthday:</td><td><input type="text" name="birthday" /></td></tr>
+>>> print p.as_ul()
+<li><ul class="errorlist"><li>This field is required.</li></ul>First name: <input type="text" name="first_name" /></li>
+<li><ul class="errorlist"><li>This field is required.</li></ul>Last name: <input type="text" name="last_name" /></li>
+<li><ul class="errorlist"><li>This field is required.</li></ul>Birthday: <input type="text" name="birthday" /></li>
+>>> print p.as_p()
+<p><ul class="errorlist"><li>This field is required.</li></ul></p>
+<p>First name: <input type="text" name="first_name" /></p>
+<p><ul class="errorlist"><li>This field is required.</li></ul></p>
+<p>Last name: <input type="text" name="last_name" /></p>
+<p><ul class="errorlist"><li>This field is required.</li></ul></p>
+<p>Birthday: <input type="text" name="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.
+>>> p = Person()
+>>> p.errors
+{}
+>>> p.is_valid()
+False
+>>> print p
+<tr><td>First name:</td><td><input type="text" name="first_name" /></td></tr>
+<tr><td>Last name:</td><td><input type="text" name="last_name" /></td></tr>
+<tr><td>Birthday:</td><td><input type="text" name="birthday" /></td></tr>
+>>> print p.as_table()
+<tr><td>First name:</td><td><input type="text" name="first_name" /></td></tr>
+<tr><td>Last name:</td><td><input type="text" name="last_name" /></td></tr>
+<tr><td>Birthday:</td><td><input type="text" name="birthday" /></td></tr>
+>>> 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>
+>>> print p.as_p()
+<p>First name: <input type="text" name="first_name" /></p>
+<p>Last name: <input type="text" name="last_name" /></p>
+<p>Birthday: <input type="text" name="birthday" /></p>
+
+Unicode values are handled properly.
+>>> p = Person({'first_name': u'John', 'last_name': u'\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111', 'birthday': '1940-10-9'})
+>>> p.as_table()
+u'<tr><td>First name:</td><td><input type="text" name="first_name" value="John" /></td></tr>\n<tr><td>Last name:</td><td><input type="text" name="last_name" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" /></td></tr>\n<tr><td>Birthday:</td><td><input type="text" name="birthday" value="1940-10-9" /></td></tr>'
+>>> p.as_ul()
+u'<li>First name: <input type="text" name="first_name" value="John" /></li>\n<li>Last name: <input type="text" name="last_name" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" /></li>\n<li>Birthday: <input type="text" name="birthday" value="1940-10-9" /></li>'
+>>> p.as_p()
+u'<p>First name: <input type="text" name="first_name" value="John" /></p>\n<p>Last name: <input type="text" name="last_name" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" /></p>\n<p>Birthday: <input type="text" name="birthday" value="1940-10-9" /></p>'
>>> p = Person({'last_name': u'Lennon'})
->>> p.errors()
+>>> p.errors
{'first_name': [u'This field is required.'], 'birthday': [u'This field is required.']}
>>> p.is_valid()
False
->>> p.errors().as_ul()
+>>> p.errors.as_ul()
u'<ul class="errorlist"><li>first_name<ul class="errorlist"><li>This field is required.</li></ul></li><li>birthday<ul class="errorlist"><li>This field is required.</li></ul></li></ul>'
->>> print p.errors().as_text()
+>>> print p.errors.as_text()
* first_name
* This field is required.
* birthday
* This field is required.
->>> p.clean()
->>> repr(p.clean())
+>>> p.clean_data
+>>> repr(p.clean_data)
'None'
>>> p['first_name'].errors
[u'This field is required.']
@@ -862,6 +1440,60 @@ u'* This field is required.'
>>> print p['birthday']
<input type="text" name="birthday" />
+"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
+the human-readable labels for a field.
+>>> p = Person(auto_id='id_%s')
+>>> print p.as_table()
+<tr><td><label for="id_first_name">First name:</label></td><td><input type="text" name="first_name" id="id_first_name" /></td></tr>
+<tr><td><label for="id_last_name">Last name:</label></td><td><input type="text" name="last_name" id="id_last_name" /></td></tr>
+<tr><td><label for="id_birthday">Birthday:</label></td><td><input type="text" name="birthday" id="id_birthday" /></td></tr>
+>>> print p.as_ul()
+<li><label for="id_first_name">First name:</label> <input type="text" name="first_name" id="id_first_name" /></li>
+<li><label for="id_last_name">Last name:</label> <input type="text" name="last_name" id="id_last_name" /></li>
+<li><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" /></li>
+>>> print p.as_p()
+<p><label for="id_first_name">First name:</label> <input type="text" name="first_name" id="id_first_name" /></p>
+<p><label for="id_last_name">Last name:</label> <input type="text" name="last_name" id="id_last_name" /></p>
+<p><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" /></p>
+
+If auto_id is any True value whose str() does not contain '%s', the "id"
+attribute will be the name of the field.
+>>> p = Person(auto_id=True)
+>>> print p.as_ul()
+<li><label for="first_name">First name:</label> <input type="text" name="first_name" id="first_name" /></li>
+<li><label for="last_name">Last name:</label> <input type="text" name="last_name" id="last_name" /></li>
+<li><label for="birthday">Birthday:</label> <input type="text" name="birthday" id="birthday" /></li>
+
+If auto_id is any False value, an "id" attribute won't be output unless it
+was manually entered.
+>>> 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>
+
+In this example, auto_id is False, but the "id" attribute for the "first_name"
+field is given. Also note that field gets a <label>, while the others don't.
+>>> class PersonNew(Form):
+... first_name = CharField(widget=TextInput(attrs={'id': 'first_name_id'}))
+... last_name = CharField()
+... birthday = DateField()
+>>> p = PersonNew(auto_id=False)
+>>> print p.as_ul()
+<li><label for="first_name_id">First name:</label> <input type="text" id="first_name_id" name="first_name" /></li>
+<li>Last name: <input type="text" name="last_name" /></li>
+<li>Birthday: <input type="text" name="birthday" /></li>
+
+If the "id" attribute is specified in the Form and auto_id is True, the "id"
+attribute in the Form gets precedence.
+>>> p = PersonNew(auto_id=True)
+>>> print p.as_ul()
+<li><label for="first_name_id">First name:</label> <input type="text" id="first_name_id" name="first_name" /></li>
+<li><label for="last_name">Last name:</label> <input type="text" name="last_name" id="last_name" /></li>
+<li><label for="birthday">Birthday:</label> <input type="text" name="birthday" id="birthday" /></li>
+
>>> class SignupForm(Form):
... email = EmailField()
... get_spam = BooleanField()
@@ -887,11 +1519,14 @@ Any Field can have a Widget class passed to its constructor:
>>> print f['message']
<textarea name="message"></textarea>
-as_textarea() and as_text() are shortcuts for changing the output widget type:
+as_textarea(), as_text() and as_hidden() are shortcuts for changing the output
+widget type:
>>> f['subject'].as_textarea()
u'<textarea name="subject"></textarea>'
>>> f['message'].as_text()
u'<input type="text" name="message" />'
+>>> f['message'].as_hidden()
+u'<input type="hidden" name="message" />'
The 'widget' parameter to a Field can also be an instance:
>>> class ContactForm(Form):
@@ -901,7 +1536,8 @@ The 'widget' parameter to a Field can also be an instance:
>>> print f['message']
<textarea rows="80" cols="20" name="message"></textarea>
-Instance-level attrs are *not* carried over to as_textarea() and as_text():
+Instance-level attrs are *not* carried over to as_textarea(), as_text() and
+as_hidden():
>>> f['message'].as_text()
u'<input type="text" name="message" />'
>>> f = ContactForm({'subject': 'Hello', 'message': 'I love you.'})
@@ -909,6 +1545,8 @@ u'<input type="text" name="message" />'
u'<textarea name="subject">Hello</textarea>'
>>> f['message'].as_text()
u'<input type="text" name="message" value="I love you." />'
+>>> f['message'].as_hidden()
+u'<input type="hidden" name="message" value="I love you." />'
For a form with a <select>, use ChoiceField:
>>> class FrameworkForm(Form):
@@ -927,6 +1565,61 @@ For a form with a <select>, use ChoiceField:
<option value="J">Java</option>
</select>
+Add widget=RadioSelect to use that widget with a ChoiceField.
+>>> class FrameworkForm(Form):
+... name = CharField()
+... language = ChoiceField(choices=[('P', 'Python'), ('J', 'Java')], widget=RadioSelect)
+>>> f = FrameworkForm()
+>>> print f['language']
+<ul>
+<li><label><input type="radio" name="language" value="P" /> Python</label></li>
+<li><label><input type="radio" name="language" value="J" /> Java</label></li>
+</ul>
+>>> print f
+<tr><td>Name:</td><td><input type="text" name="name" /></td></tr>
+<tr><td>Language:</td><td><ul>
+<li><label><input type="radio" name="language" value="P" /> Python</label></li>
+<li><label><input type="radio" name="language" value="J" /> Java</label></li>
+</ul></td></tr>
+>>> print f.as_ul()
+<li>Name: <input type="text" name="name" /></li>
+<li>Language: <ul>
+<li><label><input type="radio" name="language" value="P" /> Python</label></li>
+<li><label><input type="radio" name="language" value="J" /> Java</label></li>
+</ul></li>
+
+Regarding auto_id and <label>, RadioSelect is a special case. Each radio button
+gets a distinct ID, formed by appending an underscore plus the button's
+zero-based index.
+>>> f = FrameworkForm(auto_id='id_%s')
+>>> print f['language']
+<ul>
+<li><label><input type="radio" id="id_language_0" value="P" name="language" /> Python</label></li>
+<li><label><input type="radio" id="id_language_1" value="J" name="language" /> Java</label></li>
+</ul>
+
+When RadioSelect is used with auto_id, and the whole form is printed using
+either as_table() or as_ul(), the label for the RadioSelect will point to the
+ID of the *first* radio button.
+>>> print f
+<tr><td><label for="id_name">Name:</label></td><td><input type="text" name="name" id="id_name" /></td></tr>
+<tr><td><label for="id_language_0">Language:</label></td><td><ul>
+<li><label><input type="radio" id="id_language_0" value="P" name="language" /> Python</label></li>
+<li><label><input type="radio" id="id_language_1" value="J" name="language" /> Java</label></li>
+</ul></td></tr>
+>>> print f.as_ul()
+<li><label for="id_name">Name:</label> <input type="text" name="name" id="id_name" /></li>
+<li><label for="id_language_0">Language:</label> <ul>
+<li><label><input type="radio" id="id_language_0" value="P" name="language" /> Python</label></li>
+<li><label><input type="radio" id="id_language_1" value="J" name="language" /> Java</label></li>
+</ul></li>
+>>> print f.as_p()
+<p><label for="id_name">Name:</label> <input type="text" name="name" id="id_name" /></p>
+<p><label for="id_language_0">Language:</label> <ul>
+<li><label><input type="radio" id="id_language_0" value="P" name="language" /> Python</label></li>
+<li><label><input type="radio" id="id_language_1" value="J" name="language" /> Java</label></li>
+</ul></p>
+
MultipleChoiceField is a special case, as its data is required to be a list:
>>> class SongForm(Form):
... name = CharField()
@@ -953,10 +1646,52 @@ MultipleChoiceField is a special case, as its data is required to be a list:
<option value="P" selected="selected">Paul McCartney</option>
</select>
+MultipleChoiceField can also be used with the CheckboxSelectMultiple widget.
+>>> class SongForm(Form):
+... name = CharField()
+... composers = MultipleChoiceField(choices=[('J', 'John Lennon'), ('P', 'Paul McCartney')], widget=CheckboxSelectMultiple)
+>>> f = SongForm()
+>>> print f['composers']
+<ul>
+<li><label><input type="checkbox" name="composers" value="J" /> John Lennon</label></li>
+<li><label><input type="checkbox" name="composers" value="P" /> Paul McCartney</label></li>
+</ul>
+>>> f = SongForm({'composers': ['J']})
+>>> print f['composers']
+<ul>
+<li><label><input checked="checked" type="checkbox" name="composers" value="J" /> John Lennon</label></li>
+<li><label><input type="checkbox" name="composers" value="P" /> Paul McCartney</label></li>
+</ul>
+>>> f = SongForm({'composers': ['J', 'P']})
+>>> print f['composers']
+<ul>
+<li><label><input checked="checked" type="checkbox" name="composers" value="J" /> John Lennon</label></li>
+<li><label><input checked="checked" type="checkbox" name="composers" value="P" /> Paul McCartney</label></li>
+</ul>
+
+When using CheckboxSelectMultiple, the framework expects a list of input and
+returns a list of input.
+>>> f = SongForm({'name': 'Yesterday'})
+>>> f.errors
+{'composers': [u'This field is required.']}
+>>> f = SongForm({'name': 'Yesterday', 'composers': ['J']})
+>>> f.errors
+{}
+>>> f.clean_data
+{'composers': [u'J'], 'name': u'Yesterday'}
+>>> f = SongForm({'name': 'Yesterday', 'composers': ['J', 'P']})
+>>> f.errors
+{}
+>>> f.clean_data
+{'composers': [u'J', u'P'], 'name': u'Yesterday'}
+
There are a couple of ways to do multiple-field validation. If you want the
validation message to be associated with a particular field, implement the
clean_XXX() method on the Form, where XXX is the field name. As in
-Field.clean(), the clean_XXX() method should return the cleaned value:
+Field.clean(), the clean_XXX() method should return the cleaned value. In the
+clean_XXX() method, you have access to self.clean_data, which is a dictionary
+of all the data that has been cleaned *so far*, in order by the fields,
+including the current field (e.g., the field XXX if you're in clean_XXX()).
>>> class UserRegistration(Form):
... username = CharField(max_length=10)
... password1 = CharField(widget=PasswordInput)
@@ -966,22 +1701,27 @@ Field.clean(), the clean_XXX() method should return the cleaned value:
... raise ValidationError(u'Please make sure your passwords match.')
... return self.clean_data['password2']
>>> f = UserRegistration()
->>> f.errors()
+>>> f.errors
+{}
+>>> f = UserRegistration({})
+>>> f.errors
{'username': [u'This field is required.'], 'password1': [u'This field is required.'], 'password2': [u'This field is required.']}
>>> f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'bar'})
->>> f.errors()
+>>> f.errors
{'password2': [u'Please make sure your passwords match.']}
>>> f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'foo'})
->>> f.errors()
+>>> f.errors
{}
->>> f.clean()
+>>> f.clean_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
-Form.clean() still needs to return a dictionary of all clean data:
+special-case association with the field named '__all__'.
+Note that in Form.clean(), you have access to self.clean_data, a dictionary of
+all the fields/values that have *not* raised a ValidationError. Also note
+Form.clean() is required to return a dictionary of all clean data.
>>> class UserRegistration(Form):
... username = CharField(max_length=10)
... password1 = CharField(widget=PasswordInput)
@@ -991,45 +1731,315 @@ Form.clean() still needs to return a dictionary of all clean data:
... raise ValidationError(u'Please make sure your passwords match.')
... return self.clean_data
>>> f = UserRegistration()
+>>> f.errors
+{}
+>>> f = UserRegistration({})
>>> print f.as_table()
-<table>
+<tr><td colspan="2"><ul class="errorlist"><li>This field is required.</li></ul></td></tr>
<tr><td>Username:</td><td><input type="text" name="username" /></td></tr>
+<tr><td colspan="2"><ul class="errorlist"><li>This field is required.</li></ul></td></tr>
<tr><td>Password1:</td><td><input type="password" name="password1" /></td></tr>
+<tr><td colspan="2"><ul class="errorlist"><li>This field is required.</li></ul></td></tr>
<tr><td>Password2:</td><td><input type="password" name="password2" /></td></tr>
-</table>
->>> f.errors()
+>>> f.errors
{'username': [u'This field is required.'], 'password1': [u'This field is required.'], 'password2': [u'This field is required.']}
>>> f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'bar'})
->>> f.errors()
+>>> f.errors
{'__all__': [u'Please make sure your passwords match.']}
>>> print f.as_table()
-<table>
+<tr><td colspan="2"><ul class="errorlist"><li>Please make sure your passwords match.</li></ul></td></tr>
<tr><td>Username:</td><td><input type="text" name="username" value="adrian" /></td></tr>
<tr><td>Password1:</td><td><input type="password" name="password1" value="foo" /></td></tr>
<tr><td>Password2:</td><td><input type="password" name="password2" value="bar" /></td></tr>
-</table>
->>> print f.as_table_with_errors()
-<table>
-<tr><td colspan="2"><ul><li>Please make sure your passwords match.</li></ul></td></tr>
-<tr><td>Username:</td><td><input type="text" name="username" value="adrian" /></td></tr>
-<tr><td>Password1:</td><td><input type="password" name="password1" value="foo" /></td></tr>
-<tr><td>Password2:</td><td><input type="password" name="password2" value="bar" /></td></tr>
-</table>
->>> print f.as_ul_with_errors()
-<ul>
-<li><ul><li>Please make sure your passwords match.</li></ul></li>
+>>> print f.as_ul()
+<li><ul class="errorlist"><li>Please make sure your passwords match.</li></ul></li>
<li>Username: <input type="text" name="username" value="adrian" /></li>
<li>Password1: <input type="password" name="password1" value="foo" /></li>
<li>Password2: <input type="password" name="password2" value="bar" /></li>
-</ul>
>>> f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'foo'})
->>> f.errors()
+>>> f.errors
{}
->>> f.clean()
+>>> f.clean_data
{'username': u'adrian', 'password1': u'foo', 'password2': u'foo'}
+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__().
+>>> class Person(Form):
+... first_name = CharField()
+... last_name = CharField()
+... def __init__(self):
+... super(Person, self).__init__()
+... self.fields['birthday'] = DateField()
+>>> p = Person()
+>>> print p
+<tr><td>First name:</td><td><input type="text" name="first_name" /></td></tr>
+<tr><td>Last name:</td><td><input type="text" name="last_name" /></td></tr>
+<tr><td>Birthday:</td><td><input type="text" name="birthday" /></td></tr>
+
+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
+form, directly after that row's form element.
+>>> class Person(Form):
+... first_name = CharField()
+... last_name = CharField()
+... hidden_text = CharField(widget=HiddenInput)
+... birthday = DateField()
+>>> p = Person()
+>>> print p
+<tr><td>First name:</td><td><input type="text" name="first_name" /></td></tr>
+<tr><td>Last name:</td><td><input type="text" name="last_name" /></td></tr>
+<tr><td>Birthday:</td><td><input type="text" name="birthday" /><input type="hidden" name="hidden_text" /></td></tr>
+>>> 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" /><input type="hidden" name="hidden_text" /></li>
+>>> print p.as_p()
+<p>First name: <input type="text" name="first_name" /></p>
+<p>Last name: <input type="text" name="last_name" /></p>
+<p>Birthday: <input type="text" name="birthday" /><input type="hidden" name="hidden_text" /></p>
+
+With auto_id set, a HiddenInput still gets an ID, but it doesn't get a label.
+>>> p = Person(auto_id='id_%s')
+>>> print p
+<tr><td><label for="id_first_name">First name:</label></td><td><input type="text" name="first_name" id="id_first_name" /></td></tr>
+<tr><td><label for="id_last_name">Last name:</label></td><td><input type="text" name="last_name" id="id_last_name" /></td></tr>
+<tr><td><label for="id_birthday">Birthday:</label></td><td><input type="text" name="birthday" id="id_birthday" /><input type="hidden" name="hidden_text" id="id_hidden_text" /></td></tr>
+>>> print p.as_ul()
+<li><label for="id_first_name">First name:</label> <input type="text" name="first_name" id="id_first_name" /></li>
+<li><label for="id_last_name">Last name:</label> <input type="text" name="last_name" id="id_last_name" /></li>
+<li><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" /><input type="hidden" name="hidden_text" id="id_hidden_text" /></li>
+>>> print p.as_p()
+<p><label for="id_first_name">First name:</label> <input type="text" name="first_name" id="id_first_name" /></p>
+<p><label for="id_last_name">Last name:</label> <input type="text" name="last_name" id="id_last_name" /></p>
+<p><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" /><input type="hidden" name="hidden_text" id="id_hidden_text" /></p>
+
+If a field with a HiddenInput has errors, the as_table() and as_ul() output
+will include the error message(s) with the text "(Hidden field [fieldname]) "
+prepended. This message is displayed at the top of the output, regardless of
+its field's order in the form.
+>>> p = Person({'first_name': 'John', 'last_name': 'Lennon', 'birthday': '1940-10-9'})
+>>> print p
+<tr><td colspan="2"><ul class="errorlist"><li>(Hidden field hidden_text) This field is required.</li></ul></td></tr>
+<tr><td>First name:</td><td><input type="text" name="first_name" value="John" /></td></tr>
+<tr><td>Last name:</td><td><input type="text" name="last_name" value="Lennon" /></td></tr>
+<tr><td>Birthday:</td><td><input type="text" name="birthday" value="1940-10-9" /><input type="hidden" name="hidden_text" /></td></tr>
+>>> print p.as_ul()
+<li><ul class="errorlist"><li>(Hidden field hidden_text) This field is required.</li></ul></li>
+<li>First name: <input type="text" name="first_name" value="John" /></li>
+<li>Last name: <input type="text" name="last_name" value="Lennon" /></li>
+<li>Birthday: <input type="text" name="birthday" value="1940-10-9" /><input type="hidden" name="hidden_text" /></li>
+>>> print p.as_p()
+<p><ul class="errorlist"><li>(Hidden field hidden_text) This field is required.</li></ul></p>
+<p>First name: <input type="text" name="first_name" value="John" /></p>
+<p>Last name: <input type="text" name="last_name" value="Lennon" /></p>
+<p>Birthday: <input type="text" name="birthday" value="1940-10-9" /><input type="hidden" name="hidden_text" /></p>
+
+A corner case: It's possible for a form to have only HiddenInputs.
+>>> class TestForm(Form):
+... foo = CharField(widget=HiddenInput)
+... bar = CharField(widget=HiddenInput)
+>>> p = TestForm()
+>>> print p.as_table()
+<input type="hidden" name="foo" /><input type="hidden" name="bar" />
+>>> print p.as_ul()
+<input type="hidden" name="foo" /><input type="hidden" name="bar" />
+>>> print p.as_p()
+<input type="hidden" name="foo" /><input type="hidden" name="bar" />
+
+A Form's fields are displayed in the same order in which they were defined.
+>>> class TestForm(Form):
+... field1 = CharField()
+... field2 = CharField()
+... field3 = CharField()
+... field4 = CharField()
+... field5 = CharField()
+... field6 = CharField()
+... field7 = CharField()
+... field8 = CharField()
+... field9 = CharField()
+... field10 = CharField()
+... field11 = CharField()
+... field12 = CharField()
+... field13 = CharField()
+... field14 = CharField()
+>>> p = TestForm()
+>>> print p
+<tr><td>Field1:</td><td><input type="text" name="field1" /></td></tr>
+<tr><td>Field2:</td><td><input type="text" name="field2" /></td></tr>
+<tr><td>Field3:</td><td><input type="text" name="field3" /></td></tr>
+<tr><td>Field4:</td><td><input type="text" name="field4" /></td></tr>
+<tr><td>Field5:</td><td><input type="text" name="field5" /></td></tr>
+<tr><td>Field6:</td><td><input type="text" name="field6" /></td></tr>
+<tr><td>Field7:</td><td><input type="text" name="field7" /></td></tr>
+<tr><td>Field8:</td><td><input type="text" name="field8" /></td></tr>
+<tr><td>Field9:</td><td><input type="text" name="field9" /></td></tr>
+<tr><td>Field10:</td><td><input type="text" name="field10" /></td></tr>
+<tr><td>Field11:</td><td><input type="text" name="field11" /></td></tr>
+<tr><td>Field12:</td><td><input type="text" name="field12" /></td></tr>
+<tr><td>Field13:</td><td><input type="text" name="field13" /></td></tr>
+<tr><td>Field14:</td><td><input type="text" name="field14" /></td></tr>
+
+# Basic form processing in a view #############################################
+
+>>> from django.template import Template, Context
+>>> class UserRegistration(Form):
+... username = CharField(max_length=10)
+... password1 = CharField(widget=PasswordInput)
+... password2 = CharField(widget=PasswordInput)
+... def clean(self):
+... if self.clean_data.get('password1') and self.clean_data.get('password2') and self.clean_data['password1'] != self.clean_data['password2']:
+... raise ValidationError(u'Please make sure your passwords match.')
+... return self.clean_data
+>>> def my_function(method, post_data):
+... if method == 'POST':
+... form = UserRegistration(post_data)
+... else:
+... form = UserRegistration()
+... if form.is_valid():
+... return 'VALID: %r' % form.clean_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}))
+
+Case 1: GET (an empty form, with no errors).
+>>> print my_function('GET', {})
+<form action="" method="post">
+<table>
+<tr><td>Username:</td><td><input type="text" name="username" /></td></tr>
+<tr><td>Password1:</td><td><input type="password" name="password1" /></td></tr>
+<tr><td>Password2:</td><td><input type="password" name="password2" /></td></tr>
+</table>
+<input type="submit" />
+</form>
+
+Case 2: POST with erroneous data (a redisplayed form, with errors).
+>>> print my_function('POST', {'username': 'this-is-a-long-username', 'password1': 'foo', 'password2': 'bar'})
+<form action="" method="post">
+<table>
+<tr><td colspan="2"><ul class="errorlist"><li>Please make sure your passwords match.</li></ul></td></tr>
+<tr><td colspan="2"><ul class="errorlist"><li>Ensure this value has at most 10 characters.</li></ul></td></tr>
+<tr><td>Username:</td><td><input type="text" name="username" value="this-is-a-long-username" /></td></tr>
+<tr><td>Password1:</td><td><input type="password" name="password1" value="foo" /></td></tr>
+<tr><td>Password2:</td><td><input type="password" name="password2" value="bar" /></td></tr>
+</table>
+<input type="submit" />
+</form>
+
+Case 3: POST with valid data (the success message).
+>>> print my_function('POST', {'username': 'adrian', 'password1': 'secret', 'password2': 'secret'})
+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)
+... password1 = CharField(widget=PasswordInput)
+... password2 = CharField(widget=PasswordInput)
+... def clean(self):
+... if self.clean_data.get('password1') and self.clean_data.get('password2') and self.clean_data['password1'] != self.clean_data['password2']:
+... raise ValidationError(u'Please make sure your passwords match.')
+... return self.clean_data
+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
+fields. Note, however, that this flexibility comes with the responsibility of
+displaying all the errors, including any that might not be associated with a
+particular field.
+>>> t = Template('''<form action="">
+... {{ form.username.errors.as_ul }}<p><label>Your username: {{ form.username }}</label></p>
+... {{ form.password1.errors.as_ul }}<p><label>Password: {{ form.password1 }}</label></p>
+... {{ form.password2.errors.as_ul }}<p><label>Password (again): {{ form.password2 }}</label></p>
+... <input type="submit" />
+... </form>''')
+>>> print t.render(Context({'form': UserRegistration()}))
+<form action="">
+<p><label>Your username: <input type="text" name="username" /></label></p>
+<p><label>Password: <input type="password" name="password1" /></label></p>
+<p><label>Password (again): <input type="password" name="password2" /></label></p>
+<input type="submit" />
+</form>
+>>> print t.render(Context({'form': UserRegistration({'username': 'django'})}))
+<form action="">
+<p><label>Your username: <input type="text" name="username" value="django" /></label></p>
+<ul class="errorlist"><li>This field is required.</li></ul><p><label>Password: <input type="password" name="password1" /></label></p>
+<ul class="errorlist"><li>This field is required.</li></ul><p><label>Password (again): <input type="password" name="password2" /></label></p>
+<input type="submit" />
+</form>
+
+Use form.[field].verbose_name to output a field's "verbose name" -- its field
+name with underscores converted to spaces, and the initial letter capitalized.
+>>> t = Template('''<form action="">
+... <p><label>{{ form.username.verbose_name }}: {{ form.username }}</label></p>
+... <p><label>{{ form.password1.verbose_name }}: {{ form.password1 }}</label></p>
+... <p><label>{{ form.password2.verbose_name }}: {{ form.password2 }}</label></p>
+... <input type="submit" />
+... </form>''')
+>>> print t.render(Context({'form': UserRegistration()}))
+<form action="">
+<p><label>Username: <input type="text" name="username" /></label></p>
+<p><label>Password1: <input type="password" name="password1" /></label></p>
+<p><label>Password2: <input type="password" name="password2" /></label></p>
+<input type="submit" />
+</form>
+
+User form.[field].label_tag to output a field's verbose_name with a <label>
+tag wrapped around it, but *only* if the given field has an "id" attribute.
+Recall from above that passing the "auto_id" argument to a Form gives each
+field an "id" attribute.
+>>> t = Template('''<form action="">
+... <p>{{ form.username.label_tag }}: {{ form.username }}</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()}))
+<form action="">
+<p>Username: <input type="text" name="username" /></p>
+<p>Password1: <input type="password" name="password1" /></p>
+<p>Password2: <input type="password" name="password2" /></p>
+<input type="submit" />
+</form>
+>>> print t.render(Context({'form': UserRegistration(auto_id='id_%s')}))
+<form action="">
+<p><label for="id_username">Username</label>: <input type="text" name="username" id="id_username" /></p>
+<p><label for="id_password1">Password1</label>: <input type="password" name="password1" id="id_password1" /></p>
+<p><label for="id_password2">Password2</label>: <input type="password" name="password2" id="id_password2" /></p>
+<input type="submit" />
+</form>
+
+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
+the list of errors is empty). You can also use it in {% if %} statements.
+>>> t = Template('''<form action="">
+... {{ form.username.errors.as_ul }}<p><label>Your username: {{ form.username }}</label></p>
+... {{ form.password1.errors.as_ul }}<p><label>Password: {{ form.password1 }}</label></p>
+... {{ form.password2.errors.as_ul }}<p><label>Password (again): {{ form.password2 }}</label></p>
+... <input type="submit" />
+... </form>''')
+>>> print t.render(Context({'form': UserRegistration({'username': 'django', 'password1': 'foo', 'password2': 'bar'})}))
+<form action="">
+<p><label>Your username: <input type="text" name="username" value="django" /></label></p>
+<p><label>Password: <input type="password" name="password1" value="foo" /></label></p>
+<p><label>Password (again): <input type="password" name="password2" value="bar" /></label></p>
+<input type="submit" />
+</form>
+>>> t = Template('''<form action="">
+... {{ form.non_field_errors }}
+... {{ form.username.errors.as_ul }}<p><label>Your username: {{ form.username }}</label></p>
+... {{ form.password1.errors.as_ul }}<p><label>Password: {{ form.password1 }}</label></p>
+... {{ form.password2.errors.as_ul }}<p><label>Password (again): {{ form.password2 }}</label></p>
+... <input type="submit" />
+... </form>''')
+>>> print t.render(Context({'form': UserRegistration({'username': 'django', 'password1': 'foo', 'password2': 'bar'})}))
+<form action="">
+<ul class="errorlist"><li>Please make sure your passwords match.</li></ul>
+<p><label>Your username: <input type="text" name="username" value="django" /></label></p>
+<p><label>Password: <input type="password" name="password1" value="foo" /></label></p>
+<p><label>Password (again): <input type="password" name="password2" value="bar" /></label></p>
+<input type="submit" />
+</form>
"""
if __name__ == "__main__":
diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py
index 3c31bb0604..0a41f5b5b7 100644
--- a/tests/regressiontests/templates/tests.py
+++ b/tests/regressiontests/templates/tests.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
from django.conf import settings
if __name__ == '__main__':
@@ -62,6 +63,11 @@ class OtherClass:
def method(self):
return "OtherClass.method"
+class UnicodeInStrClass:
+ "Class whose __str__ returns a Unicode object."
+ def __str__(self):
+ return u'Å ÄĆŽćžšđ'
+
class Templates(unittest.TestCase):
def test_templates(self):
# NOW and NOW_tz are used by timesince tag tests.
@@ -173,6 +179,10 @@ class Templates(unittest.TestCase):
# Empty strings can be passed as arguments to filters
'basic-syntax36': (r'{{ var|join:"" }}', {'var': ['a', 'b', 'c']}, 'abc'),
+ # If a variable has a __str__() that returns a Unicode object, the value
+ # will be converted to a bytestring.
+ 'basic-syntax37': (r'{{ var }}', {'var': UnicodeInStrClass()}, '\xc5\xa0\xc4\x90\xc4\x86\xc5\xbd\xc4\x87\xc5\xbe\xc5\xa1\xc4\x91'),
+
### COMMENT SYNTAX ########################################################
'comment-syntax01': ("{# this is hidden #}hello", {}, "hello"),
'comment-syntax02': ("{# this is hidden #}hello{# foo #}", {}, "hello"),
@@ -328,18 +338,18 @@ class Templates(unittest.TestCase):
'ifchanged05': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% for x in numx %}{% ifchanged %}{{ x }}{% endifchanged %}{% endfor %}{% endfor %}', { 'num': (1, 1, 1), 'numx': (1, 2, 3)}, '1123123123'),
'ifchanged06': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% for x in numx %}{% ifchanged %}{{ x }}{% endifchanged %}{% endfor %}{% endfor %}', { 'num': (1, 1, 1), 'numx': (2, 2, 2)}, '1222'),
'ifchanged07': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% for x in numx %}{% ifchanged %}{{ x }}{% endifchanged %}{% for y in numy %}{% ifchanged %}{{ y }}{% endifchanged %}{% endfor %}{% endfor %}{% endfor %}', { 'num': (1, 1, 1), 'numx': (2, 2, 2), 'numy': (3, 3, 3)}, '1233323332333'),
-
+
# Test one parameter given to ifchanged.
'ifchanged-param01': ('{% for n in num %}{% ifchanged n %}..{% endifchanged %}{{ n }}{% endfor %}', { 'num': (1,2,3) }, '..1..2..3'),
'ifchanged-param02': ('{% for n in num %}{% for x in numx %}{% ifchanged n %}..{% endifchanged %}{{ x }}{% endfor %}{% endfor %}', { 'num': (1,2,3), 'numx': (5,6,7) }, '..567..567..567'),
-
+
# Test multiple parameters to ifchanged.
'ifchanged-param03': ('{% for n in num %}{{ n }}{% for x in numx %}{% ifchanged x n %}{{ x }}{% endifchanged %}{% endfor %}{% endfor %}', { 'num': (1,1,2), 'numx': (5,6,6) }, '156156256'),
-
+
# Test a date+hour like construct, where the hour of the last day
# is the same but the date had changed, so print the hour anyway.
'ifchanged-param04': ('{% for d in days %}{% ifchanged %}{{ d.day }}{% endifchanged %}{% for h in d.hours %}{% ifchanged d h %}{{ h }}{% endifchanged %}{% endfor %}{% endfor %}', {'days':[{'day':1, 'hours':[1,2,3]},{'day':2, 'hours':[3]},] }, '112323'),
-
+
# Logically the same as above, just written with explicit
# ifchanged for the day.
'ifchanged-param04': ('{% for d in days %}{% ifchanged d.day %}{{ d.day }}{% endifchanged %}{% for h in d.hours %}{% ifchanged d.day h %}{{ h }}{% endifchanged %}{% endfor %}{% endfor %}', {'days':[{'day':1, 'hours':[1,2,3]},{'day':2, 'hours':[3]},] }, '112323'),