diff options
52 files changed, 2408 insertions, 649 deletions
@@ -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 Binary files differindex aef68099d3..0dd2b26dff 100644 --- a/django/conf/locale/de/LC_MESSAGES/django.mo +++ b/django/conf/locale/de/LC_MESSAGES/django.mo 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 Binary files differindex 1c95d6b5d9..4a7d8e41f0 100644 --- a/django/conf/locale/el/LC_MESSAGES/django.mo +++ b/django/conf/locale/el/LC_MESSAGES/django.mo 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 " ÎÏα:" #: 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 Binary files differnew file mode 100644 index 0000000000..7d43b315fe --- /dev/null +++ b/django/conf/locale/el/LC_MESSAGES/djangojs.mo 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 Binary files differindex dd96bf99ce..a247bb2385 100644 --- a/django/conf/locale/es_AR/LC_MESSAGES/django.mo +++ b/django/conf/locale/es_AR/LC_MESSAGES/django.mo 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 Binary files differindex f24c9a12d0..3f89e3e33f 100644 --- a/django/conf/locale/es_AR/LC_MESSAGES/djangojs.mo +++ b/django/conf/locale/es_AR/LC_MESSAGES/djangojs.mo 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 @@ -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 "quoted" & 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 & 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'), |