From f66821deaef713a5db11a5b162a615945feb6f88 Mon Sep 17 00:00:00 2001 From: Justin Bronn Date: Wed, 14 Nov 2007 19:13:37 +0000 Subject: [PATCH] gis: Merged revisions 6614-6671 via svnmerge from trunk. git-svn-id: http://code.djangoproject.com/svn/django/branches/gis@6672 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- AUTHORS | 2 + django/conf/locale/ca/LC_MESSAGES/django.mo | Bin 61430 -> 61655 bytes django/conf/locale/ca/LC_MESSAGES/django.po | 766 ++-- django/conf/locale/es/LC_MESSAGES/django.mo | Bin 61344 -> 61344 bytes django/conf/locale/es/LC_MESSAGES/django.po | 2 +- django/conf/locale/he/LC_MESSAGES/django.mo | Bin 61545 -> 62506 bytes django/conf/locale/he/LC_MESSAGES/django.po | 1009 +++-- django/conf/locale/sv/LC_MESSAGES/django.mo | Bin 47968 -> 48025 bytes django/conf/locale/sv/LC_MESSAGES/django.po | 78 +- django/conf/locale/sv/LC_MESSAGES/djangojs.mo | Bin 1680 -> 1674 bytes django/conf/locale/sv/LC_MESSAGES/djangojs.po | 6 +- .../conf/locale/zh_TW/LC_MESSAGES/django.mo | Bin 28397 -> 41345 bytes .../conf/locale/zh_TW/LC_MESSAGES/django.po | 3376 ++++++++++------- django/contrib/admin/filterspecs.py | 6 +- django/contrib/admin/models.py | 3 +- .../admin/templates/admin/base_site.html | 2 +- .../admin/templates/admin/change_form.html | 4 +- .../admin/templates/admin/date_hierarchy.html | 4 +- .../templates/admin/delete_confirmation.html | 4 +- .../templates/admin/edit_inline_stacked.html | 2 +- .../templates/admin/edit_inline_tabular.html | 4 +- .../contrib/admin/templates/admin/index.html | 6 +- .../admin/templates/admin/invalid_setup.html | 2 +- .../admin/templates/admin/object_history.html | 6 +- .../admin/templates/admin/pagination.html | 2 +- .../templates/admin_doc/model_detail.html | 8 +- .../admin/templates/widget/foreign.html | 2 +- .../admin/templates/widget/one_to_one.html | 2 +- .../contrib/admin/templatetags/admin_list.py | 17 +- .../admin/templatetags/admin_modify.py | 17 +- .../admin/templatetags/adminapplist.py | 3 +- django/contrib/admin/utils.py | 3 +- django/contrib/admin/views/decorators.py | 3 +- django/contrib/admin/views/doc.py | 3 +- django/contrib/admin/views/main.py | 29 +- django/contrib/auth/decorators.py | 47 +- django/contrib/auth/forms.py | 2 +- django/contrib/csrf/middleware.py | 7 +- django/contrib/databrowse/datastructures.py | 13 +- .../contrib/databrowse/plugins/calendars.py | 12 +- .../databrowse/plugins/fieldchoices.py | 10 +- django/contrib/databrowse/sites.py | 3 +- django/contrib/flatpages/views.py | 8 + .../contrib/humanize/templatetags/humanize.py | 4 + django/contrib/markup/templatetags/markup.py | 10 +- django/contrib/markup/tests.py | 6 +- django/contrib/sessions/middleware.py | 20 +- django/contrib/sitemaps/templates/sitemap.xml | 3 +- .../sitemaps/templates/sitemap_index.xml | 3 +- django/core/handlers/base.py | 28 +- django/core/handlers/modpython.py | 1 + django/core/handlers/wsgi.py | 2 + django/core/management/__init__.py | 2 + django/core/management/commands/startapp.py | 21 +- django/core/management/sql.py | 63 +- django/core/servers/basehttp.py | 6 +- django/db/backends/__init__.py | 1 + django/db/backends/mysql/base.py | 1 + django/db/backends/mysql_old/base.py | 1 + django/db/models/__init__.py | 1 + django/db/models/fields/__init__.py | 2 + django/db/models/fields/subclassing.py | 53 + django/http/__init__.py | 1 + django/http/utils.py | 34 + django/middleware/http.py | 23 +- django/newforms/fields.py | 230 +- django/newforms/forms.py | 33 +- django/newforms/models.py | 99 +- django/newforms/util.py | 18 +- django/newforms/widgets.py | 40 +- django/oldforms/__init__.py | 43 +- django/template/__init__.py | 70 +- django/template/context.py | 5 +- django/template/defaultfilters.py | 340 +- django/template/defaulttags.py | 234 +- django/test/client.py | 2 +- django/test/testcases.py | 10 +- django/utils/cache.py | 30 +- django/utils/datastructures.py | 8 +- django/utils/dateformat.py | 2 +- django/utils/encoding.py | 32 +- django/utils/html.py | 33 +- django/utils/http.py | 28 + django/utils/safestring.py | 124 + django/views/debug.py | 30 +- django/views/static.py | 8 +- docs/cache.txt | 10 +- docs/custom_model_fields.txt | 567 +++ docs/email.txt | 2 +- docs/form_preview.txt | 2 +- docs/model-api.txt | 109 +- docs/modpython.txt | 2 +- docs/newforms.txt | 51 +- docs/serialization.txt | 27 +- docs/sitemaps.txt | 2 +- docs/templates.txt | 193 +- docs/templates_python.txt | 142 +- docs/testing.txt | 1 - .../modeltests/field_subclassing/__init__.py | 0 tests/modeltests/field_subclassing/models.py | 106 + tests/modeltests/model_forms/models.py | 36 +- tests/modeltests/test_client/models.py | 135 +- tests/modeltests/test_client/urls.py | 3 + tests/modeltests/test_client/views.py | 34 +- tests/regressiontests/defaultfilters/tests.py | 8 +- tests/regressiontests/forms/error_messages.py | 315 ++ tests/regressiontests/forms/extra.py | 3 +- tests/regressiontests/forms/forms.py | 2 +- tests/regressiontests/forms/regressions.py | 1 - tests/regressiontests/forms/tests.py | 4 +- tests/regressiontests/forms/util.py | 7 + tests/regressiontests/humanize/tests.py | 3 +- tests/regressiontests/templates/filters.py | 220 ++ tests/regressiontests/templates/tests.py | 217 +- tests/regressiontests/templates/urls.py | 2 +- .../test_client_regress/models.py | 52 +- tests/regressiontests/text/tests.py | 8 + 117 files changed, 6564 insertions(+), 2878 deletions(-) create mode 100644 django/db/models/fields/subclassing.py create mode 100644 django/http/utils.py create mode 100644 django/utils/safestring.py create mode 100644 docs/custom_model_fields.txt create mode 100644 tests/modeltests/field_subclassing/__init__.py create mode 100644 tests/modeltests/field_subclassing/models.py create mode 100644 tests/regressiontests/forms/error_messages.py create mode 100644 tests/regressiontests/templates/filters.py diff --git a/AUTHORS b/AUTHORS index 1f85553a05..1f8e40edbe 100644 --- a/AUTHORS +++ b/AUTHORS @@ -120,6 +120,7 @@ answer newbie questions, and generally made Django that much better: Marc Fargas Szilveszter Farkas favo@exoweb.net + Dmitri Fedortchenko Bill Fenner Stefane Fermgier Afonso Fernández Nogueira @@ -298,6 +299,7 @@ answer newbie questions, and generally made Django that much better: Tyler Tarabula Tyson Tate Frank Tegtmeyer + terryh.tp@gmail.com thebjorn Zach Thompson Michael Thornhill diff --git a/django/conf/locale/ca/LC_MESSAGES/django.mo b/django/conf/locale/ca/LC_MESSAGES/django.mo index 8f10d00c4e132d88f576fad482b740f49b15bdec..fdba04abd26960b8ed635342546275a0337242bb 100644 GIT binary patch delta 17705 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

i9?=0rV`aMVPKn59qyR6u=R)W8tzZ1sat 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

vSih2}hz*d6Kjf^ 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 diff --git a/django/conf/locale/ca/LC_MESSAGES/django.po b/django/conf/locale/ca/LC_MESSAGES/django.po index 26225d4269..9f561acc5b 100644 --- a/django/conf/locale/ca/LC_MESSAGES/django.po +++ b/django/conf/locale/ca/LC_MESSAGES/django.po @@ -5,8 +5,8 @@ msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2007-10-20 14:55+0200\n" -"PO-Revision-Date: 2007-10-20 15:22+0100\n" +"POT-Creation-Date: 2007-11-05 17:33+0100\n" +"PO-Revision-Date: 2007-11-05 17:34+0100\n" "Last-Translator: Marc Garcia \n" "Language-Team: \n" "MIME-Version: 1.0\n" @@ -203,10 +203,8 @@ msgstr "" "

Per %s:

\n" "
    \n" -#: contrib/admin/filterspecs.py:72 -#: contrib/admin/filterspecs.py:90 -#: contrib/admin/filterspecs.py:145 -#: contrib/admin/filterspecs.py:171 +#: contrib/admin/filterspecs.py:72 contrib/admin/filterspecs.py:90 +#: contrib/admin/filterspecs.py:145 contrib/admin/filterspecs.py:171 msgid "All" msgstr "Tots" @@ -230,20 +228,17 @@ msgstr "Aquest mes" msgid "This year" msgstr "Aquest any" -#: contrib/admin/filterspecs.py:145 -#: newforms/widgets.py:205 +#: contrib/admin/filterspecs.py:145 newforms/widgets.py:221 #: oldforms/__init__.py:591 msgid "Yes" msgstr "Si" -#: contrib/admin/filterspecs.py:145 -#: newforms/widgets.py:205 +#: contrib/admin/filterspecs.py:145 newforms/widgets.py:221 #: oldforms/__init__.py:591 msgid "No" msgstr "No" -#: contrib/admin/filterspecs.py:152 -#: newforms/widgets.py:205 +#: contrib/admin/filterspecs.py:152 newforms/widgets.py:221 #: oldforms/__init__.py:591 msgid "Unknown" msgstr "Desconegut" @@ -315,8 +310,12 @@ msgid "Server Error (500)" msgstr "Error del servidor (500)" #: contrib/admin/templates/admin/500.html:10 -msgid "There's been an error. It's been reported to the site administrators via e-mail and should be fixed shortly. Thanks for your patience." -msgstr "Hi ha hagut un error. S'ha informat als administradors del lloc per correu electrònic y hauria d'arreglar-se en breu. Gràcies per la seva paciència." +msgid "" +"There's been an error. It's been reported to the site administrators via e-" +"mail and should be fixed shortly. Thanks for your patience." +msgstr "" +"Hi ha hagut un error. S'ha informat als administradors del lloc per correu " +"electrònic y hauria d'arreglar-se en breu. Gràcies per la seva paciència." #: contrib/admin/templates/admin/base.html:26 msgid "Welcome," @@ -387,13 +386,23 @@ msgstr "Eliminar" #: contrib/admin/templates/admin/delete_confirmation.html:13 #, python-format -msgid "Deleting the %(object_name)s '%(escaped_object)s' would result in deleting related objects, but your account doesn't have permission to delete the following types of objects:" -msgstr "Eliminar el/la %(object_name)s '%(escaped_object)s' provocaria l'eliminació d'objectes relacionats, però el seu compte no te permisos per a esborrar els tipus d'objecte següents:" +msgid "" +"Deleting the %(object_name)s '%(escaped_object)s' would result in deleting " +"related objects, but your account doesn't have permission to delete the " +"following types of objects:" +msgstr "" +"Eliminar el/la %(object_name)s '%(escaped_object)s' provocaria l'eliminació " +"d'objectes relacionats, però el seu compte no te permisos per a esborrar els " +"tipus d'objecte següents:" #: contrib/admin/templates/admin/delete_confirmation.html:20 #, python-format -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 "Està segur de voler esborrar els/les %(object_name)s \"%(escaped_object)s\"? S'esborraran els següents elements relacionats:" +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 "" +"Està segur de voler esborrar els/les %(object_name)s \"%(escaped_object)s\"? " +"S'esborraran els següents elements relacionats:" #: contrib/admin/templates/admin/delete_confirmation.html:25 msgid "Yes, I'm sure" @@ -439,8 +448,14 @@ msgid "None available" msgstr "Cap disponible" #: contrib/admin/templates/admin/invalid_setup.html:8 -msgid "Something's wrong with your database installation. Make sure the appropriate database tables have been created, and make sure the database is readable by the appropriate user." -msgstr "Alguna cosa està malament en la instal·lació de la teva base de dades. Assegurat de que s'han creat les taules, i de que la base de dades és llegible per l'usuari apropiat." +msgid "" +"Something's wrong with your database installation. Make sure the appropriate " +"database tables have been created, and make sure the database is readable by " +"the appropriate user." +msgstr "" +"Alguna cosa està malament en la instal·lació de la teva base de dades. " +"Assegurat de que s'han creat les taules, i de que la base de dades és " +"llegible per l'usuari apropiat." #: contrib/admin/templates/admin/login.html:17 #: contrib/comments/templates/comments/form.html:6 @@ -475,8 +490,12 @@ msgid "DATE_WITH_TIME_FULL" msgstr "F j, Y, H:i " #: contrib/admin/templates/admin/object_history.html:35 -msgid "This object doesn't have a change history. It probably wasn't added via this admin site." -msgstr "Aquest objecte no te historial de canvis. Probablement no va ser afegit utilitzant aquest lloc administratiu." +msgid "" +"This object doesn't have a change history. It probably wasn't added via this " +"admin site." +msgstr "" +"Aquest objecte no te historial de canvis. Probablement no va ser afegit " +"utilitzant aquest lloc administratiu." #: contrib/admin/templates/admin/pagination.html:10 msgid "Show all" @@ -515,8 +534,12 @@ msgid "Save" msgstr "Desar" #: contrib/admin/templates/admin/auth/user/add_form.html:6 -msgid "First, enter a username and password. Then, you'll be able to edit more user options." -msgstr "Primer, entri un usuari i una contrasenya. Després podrà editar més opcions del usuari." +msgid "" +"First, enter a username and password. Then, you'll be able to edit more user " +"options." +msgstr "" +"Primer, entri un usuari i una contrasenya. Després podrà editar més opcions " +"del usuari." #: contrib/admin/templates/admin/auth/user/add_form.html:12 msgid "Username" @@ -561,28 +584,38 @@ msgid "" "your computer is \"internal\").

    \n" msgstr "" "\n" -"

    Per a instalar 'bookmarklets', arrosegui l'enllaç a la seva barra de\n" -"marcadors, o faci click amb el botò dret en l'enllaç i afegeixi'l als marcadors.\n" +"

    Per a instalar 'bookmarklets', arrosegui l'enllaç a la " +"seva barra de\n" +"marcadors, o faci click amb el botò dret en l'enllaç i afegeixi'l als " +"marcadors.\n" "Ara pot escollir el 'bookmarklet' des de qualsevol pàgina del lloc.\n" "Observi que alguns d'aquests 'bookmarklets' precisen que estigui veient\n" "el lloc des de un ordinador senyalat com a \"intern\" (parli\n" -"amb el seu administrador de sistemes si no està segur de la condició del seu).

    \n" +"amb el seu administrador de sistemes si no està segur de la condició del " +"seu).

    \n" #: contrib/admin/templates/admin_doc/bookmarklets.html:18 msgid "Documentation for this page" msgstr "Documentació d'aquesta pàgina" #: contrib/admin/templates/admin_doc/bookmarklets.html:19 -msgid "Jumps you from any page to the documentation for the view that generates that page." -msgstr "El porta des de qualsevol pàgina de la documentació a la vista que la genera." +msgid "" +"Jumps you from any page to the documentation for the view that generates " +"that page." +msgstr "" +"El porta des de qualsevol pàgina de la documentació a la vista que la genera." #: contrib/admin/templates/admin_doc/bookmarklets.html:21 msgid "Show object ID" msgstr "Mostra el ID de l'objecte" #: contrib/admin/templates/admin_doc/bookmarklets.html:22 -msgid "Shows the content-type and unique ID for pages that represent a single object." -msgstr "Mostra el 'content-type' (tipus de contingut) i el ID inequívoc de les pàgines que representen un únic objecte." +msgid "" +"Shows the content-type and unique ID for pages that represent a single " +"object." +msgstr "" +"Mostra el 'content-type' (tipus de contingut) i el ID inequívoc de les " +"pàgines que representen un únic objecte." #: contrib/admin/templates/admin_doc/bookmarklets.html:24 msgid "Edit this object (current window)" @@ -590,7 +623,9 @@ msgstr "Editar aquest objecte (finestra actual)" #: contrib/admin/templates/admin_doc/bookmarklets.html:25 msgid "Jumps to the admin page for pages that represent a single object." -msgstr "El porta a la pàgina d'administració de pàgines que representen un únic objecte." +msgstr "" +"El porta a la pàgina d'administració de pàgines que representen un únic " +"objecte." #: contrib/admin/templates/admin_doc/bookmarklets.html:27 msgid "Edit this object (new window)" @@ -625,8 +660,13 @@ msgid "Your password was changed." msgstr "La seva clau ha estat canviada." #: contrib/admin/templates/registration/password_change_form.html:11 -msgid "Please enter your old password, for security's sake, and then enter your new password twice so we can verify you typed it in correctly." -msgstr "Si us plau, introdueixi la seva contrasenya antiga, per seguretat, i tot seguit introdueixi la seva nova contrasenya dues vegades per verificar que l'ha escrit correctament." +msgid "" +"Please enter your old password, for security's sake, and then enter your new " +"password twice so we can verify you typed it in correctly." +msgstr "" +"Si us plau, introdueixi la seva contrasenya antiga, per seguretat, i tot " +"seguit introdueixi la seva nova contrasenya dues vegades per verificar que " +"l'ha escrit correctament." #: contrib/admin/templates/registration/password_change_form.html:16 msgid "Old password:" @@ -657,12 +697,18 @@ msgid "Password reset successful" msgstr "Contrasenya restaber-ta amb èxit" #: contrib/admin/templates/registration/password_reset_done.html:12 -msgid "We've e-mailed a new password to the e-mail address you submitted. You should be receiving it shortly." -msgstr "Li hem enviat una contrasenya nova a l'adreça de correu electrònic que ens ha indicat. L'hauria de rebre en breu." +msgid "" +"We've e-mailed a new password to the e-mail address you submitted. You " +"should be receiving it shortly." +msgstr "" +"Li hem enviat una contrasenya nova a l'adreça de correu electrònic que ens " +"ha indicat. L'hauria de rebre en breu." #: contrib/admin/templates/registration/password_reset_email.html:2 msgid "You're receiving this e-mail because you requested a password reset" -msgstr "Està rebent aquest missatge degut a que va solicitar un restabliment de contrasenya." +msgstr "" +"Està rebent aquest missatge degut a que va solicitar un restabliment de " +"contrasenya." #: contrib/admin/templates/registration/password_reset_email.html:3 #, python-format @@ -692,8 +738,12 @@ msgid "The %(site_name)s team" msgstr "L'equip de %(site_name)s" #: contrib/admin/templates/registration/password_reset_form.html:12 -msgid "Forgotten your password? Enter your e-mail address below, and we'll reset your password and e-mail the new one to you." -msgstr "Ha oblidat la seva contrasenya? Introdueixi la seva adreça de correu electrònic i crearem una nova que li enviarem per correu." +msgid "" +"Forgotten your password? Enter your e-mail address below, and we'll reset " +"your password and e-mail the new one to you." +msgstr "" +"Ha oblidat la seva contrasenya? Introdueixi la seva adreça de correu " +"electrònic i crearem una nova que li enviarem per correu." #: contrib/admin/templates/registration/password_reset_form.html:16 msgid "E-mail address:" @@ -723,14 +773,12 @@ msgstr "Modificar:" msgid "All dates" msgstr "Totes les dates" -#: contrib/admin/views/auth.py:20 -#: contrib/admin/views/main.py:264 +#: contrib/admin/views/auth.py:20 contrib/admin/views/main.py:264 #, python-format msgid "The %(name)s \"%(obj)s\" was added successfully." msgstr "El/la %(name)s \"%(obj)s\".ha estat agregat/da amb èxit." -#: contrib/admin/views/auth.py:25 -#: contrib/admin/views/main.py:268 +#: contrib/admin/views/auth.py:25 contrib/admin/views/main.py:268 #: contrib/admin/views/main.py:354 msgid "You may edit it again below." msgstr "Pot editar-lo de nou abaix." @@ -748,18 +796,30 @@ msgstr "Canvi de clau exitós" msgid "Change password: %s" msgstr "Canviar clau: %s" -#: contrib/admin/views/decorators.py:10 -#: contrib/auth/forms.py:60 -msgid "Please enter a correct username and password. Note that both fields are case-sensitive." -msgstr "Si us plau, introdueixi un nom d'usuari i contrasenya vàlids. Tingui en compte que tots dos camps son sensibles a majúscules i minúscules." +#: contrib/admin/views/decorators.py:10 contrib/auth/forms.py:60 +msgid "" +"Please enter a correct username and password. Note that both fields are case-" +"sensitive." +msgstr "" +"Si us plau, introdueixi un nom d'usuari i contrasenya vàlids. Tingui en " +"compte que tots dos camps son sensibles a majúscules i minúscules." #: contrib/admin/views/decorators.py:62 -msgid "Please log in again, because your session has expired. Don't worry: Your submission has been saved." -msgstr "Si us plau, identifiquis de nou doncs la seva sessió ha expirat. No es preocupi, el seu enviament està emmagatzemat." +msgid "" +"Please log in again, because your session has expired. Don't worry: Your " +"submission has been saved." +msgstr "" +"Si us plau, identifiquis de nou doncs la seva sessió ha expirat. No es " +"preocupi, el seu enviament està emmagatzemat." #: contrib/admin/views/decorators.py:69 -msgid "Looks like your browser isn't configured to accept cookies. Please enable cookies, reload this page, and try again." -msgstr "Sembla ser que el seu navegador no està configurat per acceptar 'cookies' (galetes). Si us plau, habiliti les 'cookies', recarregui aquesta pàgina i provi-ho de nou. " +msgid "" +"Looks like your browser isn't configured to accept cookies. Please enable " +"cookies, reload this page, and try again." +msgstr "" +"Sembla ser que el seu navegador no està configurat per acceptar " +"'cookies' (galetes). Si us plau, habiliti les 'cookies', recarregui aquesta " +"pàgina i provi-ho de nou. " #: contrib/admin/views/decorators.py:83 msgid "Usernames cannot contain the '@' character." @@ -768,22 +828,20 @@ msgstr "Els noms d'usuari no poden contenir el caracter '@'." #: contrib/admin/views/decorators.py:85 #, python-format msgid "Your e-mail address is not your username. Try '%s' instead." -msgstr "La seva adreça de correu no és el seu nom d'usuari. Provi '%s' en tot cas." +msgstr "" +"La seva adreça de correu no és el seu nom d'usuari. Provi '%s' en tot cas." -#: contrib/admin/views/doc.py:47 -#: contrib/admin/views/doc.py:49 +#: contrib/admin/views/doc.py:47 contrib/admin/views/doc.py:49 #: contrib/admin/views/doc.py:51 msgid "tag:" msgstr "etiqueta:" -#: contrib/admin/views/doc.py:78 -#: contrib/admin/views/doc.py:80 +#: contrib/admin/views/doc.py:78 contrib/admin/views/doc.py:80 #: contrib/admin/views/doc.py:82 msgid "filter:" msgstr "filtre:" -#: contrib/admin/views/doc.py:136 -#: contrib/admin/views/doc.py:138 +#: contrib/admin/views/doc.py:136 contrib/admin/views/doc.py:138 #: contrib/admin/views/doc.py:140 msgid "view:" msgstr "vista:" @@ -803,10 +861,8 @@ msgstr "El model %(name)r no s'ha trobat en la aplicació %(label)r" msgid "the related `%(label)s.%(type)s` object" msgstr "el objecte relacionat `%(label)s.%(type)s`" -#: contrib/admin/views/doc.py:184 -#: contrib/admin/views/doc.py:206 -#: contrib/admin/views/doc.py:220 -#: contrib/admin/views/doc.py:225 +#: contrib/admin/views/doc.py:184 contrib/admin/views/doc.py:206 +#: contrib/admin/views/doc.py:220 contrib/admin/views/doc.py:225 msgid "model:" msgstr "model:" @@ -830,12 +886,9 @@ msgstr "nombre de %s" msgid "Fields on %s objects" msgstr "Camps en objectes %s" -#: contrib/admin/views/doc.py:292 -#: contrib/admin/views/doc.py:303 -#: contrib/admin/views/doc.py:305 -#: contrib/admin/views/doc.py:311 -#: contrib/admin/views/doc.py:312 -#: contrib/admin/views/doc.py:314 +#: contrib/admin/views/doc.py:292 contrib/admin/views/doc.py:303 +#: contrib/admin/views/doc.py:305 contrib/admin/views/doc.py:311 +#: contrib/admin/views/doc.py:312 contrib/admin/views/doc.py:314 msgid "Integer" msgstr "Enter" @@ -843,8 +896,7 @@ msgstr "Enter" msgid "Boolean (Either True or False)" msgstr "Booleà (Verdader o Fals)" -#: contrib/admin/views/doc.py:294 -#: contrib/admin/views/doc.py:313 +#: contrib/admin/views/doc.py:294 contrib/admin/views/doc.py:313 #, python-format msgid "String (up to %(max_length)s)" msgstr "Cadena (de fins a %(max_length)s)" @@ -869,8 +921,7 @@ msgstr "Número decimal" msgid "E-mail address" msgstr "Adreça de correu electrònic" -#: contrib/admin/views/doc.py:300 -#: contrib/admin/views/doc.py:301 +#: contrib/admin/views/doc.py:300 contrib/admin/views/doc.py:301 #: contrib/admin/views/doc.py:304 msgid "File path" msgstr "Ruta del fitxer" @@ -879,8 +930,7 @@ msgstr "Ruta del fitxer" msgid "Floating point number" msgstr "Número amb punt de coma flotant" -#: contrib/admin/views/doc.py:306 -#: contrib/comments/models.py:85 +#: contrib/admin/views/doc.py:306 contrib/comments/models.py:85 msgid "IP address" msgstr "Adreça IP" @@ -904,8 +954,7 @@ msgstr "Texte" msgid "Time" msgstr "Hora" -#: contrib/admin/views/doc.py:317 -#: contrib/flatpages/models.py:7 +#: contrib/admin/views/doc.py:317 contrib/flatpages/models.py:7 msgid "URL" msgstr "URL" @@ -926,8 +975,7 @@ msgstr "%s no sembla ser un objecte 'urlpattern'" msgid "Site administration" msgstr "Lloc administratiu" -#: contrib/admin/views/main.py:278 -#: contrib/admin/views/main.py:363 +#: contrib/admin/views/main.py:278 contrib/admin/views/main.py:363 #, python-format msgid "You may add another %s below." msgstr "Pot afegir un altre %s a baix." @@ -942,10 +990,8 @@ msgstr "Afegir %s" msgid "Added %s." msgstr "Agregat %s." -#: contrib/admin/views/main.py:342 -#: contrib/admin/views/main.py:344 -#: contrib/admin/views/main.py:346 -#: core/validators.py:283 +#: contrib/admin/views/main.py:342 contrib/admin/views/main.py:344 +#: contrib/admin/views/main.py:346 core/validators.py:283 #: db/models/manipulators.py:309 msgid "and" msgstr "i" @@ -971,8 +1017,10 @@ msgstr "S'ha modificat amb èxist el/la %(name)s \"%(obj)s." #: contrib/admin/views/main.py:360 #, python-format -msgid "The %(name)s \"%(obj)s\" was added successfully. You may edit it again below." -msgstr "S'ha agregat amb èxit el/la %(name)s \"%(obj)s\". Pot editar-lo de nou abaix." +msgid "" +"The %(name)s \"%(obj)s\" was added successfully. You may edit it again below." +msgstr "" +"S'ha agregat amb èxit el/la %(name)s \"%(obj)s\". Pot editar-lo de nou abaix." #: contrib/admin/views/main.py:398 #, python-format @@ -1017,8 +1065,7 @@ msgstr "Seleccioni %s per modificar" msgid "Database error" msgstr "Error de/en la base de dades" -#: contrib/auth/forms.py:17 -#: contrib/auth/forms.py:138 +#: contrib/auth/forms.py:17 contrib/auth/forms.py:138 msgid "The two password fields didn't match." msgstr "Els dos camps de contrasenya no coincideixen." @@ -1027,16 +1074,29 @@ msgid "A user with that username already exists." msgstr "Ja existeix un usuari amb aquest nom." #: contrib/auth/forms.py:53 -msgid "Your Web browser doesn't appear to have cookies enabled. Cookies are required for logging in." -msgstr "El seu navegador no sembla tenir les 'cookies' (galetes) activades. Aquestes són necessàries per iniciar la sessió." +msgid "" +"Your Web browser doesn't appear to have cookies enabled. Cookies are " +"required for logging in." +msgstr "" +"El seu navegador no sembla tenir les 'cookies' (galetes) activades. Aquestes " +"són necessàries per iniciar la sessió." #: contrib/auth/forms.py:62 msgid "This account is inactive." msgstr "Aquest compte està inactiu" #: contrib/auth/forms.py:84 -msgid "That e-mail address doesn't have an associated user account. Are you sure you've registered?" -msgstr "Aquesta adreça de correu no té associada cap compte d'usuari. Està segur de que s'ha registrat?" +msgid "" +"That e-mail address doesn't have an associated user account. Are you sure " +"you've registered?" +msgstr "" +"Aquesta adreça de correu no té associada cap compte d'usuari. Està segur de " +"que s'ha registrat?" + +#: contrib/auth/forms.py:107 +#, python-format +msgid "Password reset on %s" +msgstr "Restablir contrasenya en %s" #: contrib/auth/forms.py:117 msgid "The two 'new password' fields didn't match." @@ -1044,10 +1104,10 @@ msgstr "Els dos camps de nova contrasenya no coincideixen." #: contrib/auth/forms.py:124 msgid "Your old password was entered incorrectly. Please enter it again." -msgstr "La seva antiga contrasenya no és correcte. Si el plau, introdueixi-la de nou." +msgstr "" +"La seva antiga contrasenya no és correcte. Si el plau, introdueixi-la de nou." -#: contrib/auth/models.py:73 -#: contrib/auth/models.py:93 +#: contrib/auth/models.py:73 contrib/auth/models.py:93 msgid "name" msgstr "nom" @@ -1059,8 +1119,7 @@ msgstr "nom en clau" msgid "permission" msgstr "permís" -#: contrib/auth/models.py:79 -#: contrib/auth/models.py:94 +#: contrib/auth/models.py:79 contrib/auth/models.py:94 msgid "permissions" msgstr "permissos" @@ -1068,8 +1127,7 @@ msgstr "permissos" msgid "group" msgstr "grup" -#: contrib/auth/models.py:98 -#: contrib/auth/models.py:141 +#: contrib/auth/models.py:98 contrib/auth/models.py:141 msgid "groups" msgstr "grups" @@ -1078,8 +1136,12 @@ msgid "username" msgstr "nom d'usuari" #: contrib/auth/models.py:131 -msgid "Required. 30 characters or fewer. Alphanumeric characters only (letters, digits and underscores)." -msgstr "Requerit. 30 o menys caracters. Només caracters alfanumèrics (lletres, dígits i guions baixos)." +msgid "" +"Required. 30 characters or fewer. Alphanumeric characters only (letters, " +"digits and underscores)." +msgstr "" +"Requerit. 30 o menys caracters. Només caracters alfanumèrics (lletres, " +"dígits i guions baixos)." #: contrib/auth/models.py:132 msgid "first name" @@ -1098,8 +1160,12 @@ msgid "password" msgstr "contrasenya" #: contrib/auth/models.py:135 -msgid "Use '[algo]$[salt]$[hexdigest]' or use the change password form." -msgstr "Utilitzi '[algo]$[salt]$[hexdigest]' o el formulari de canvi de contrasenya." +msgid "" +"Use '[algo]$[salt]$[hexdigest]' or use the change " +"password form." +msgstr "" +"Utilitzi '[algo]$[salt]$[hexdigest]' o el formulari de " +"canvi de contrasenya." #: contrib/auth/models.py:136 msgid "staff status" @@ -1114,16 +1180,24 @@ msgid "active" msgstr "actiu" #: contrib/auth/models.py:137 -msgid "Designates whether this user can log into the Django admin. Unselect this instead of deleting accounts." -msgstr "Designa si aquest usuari pot iniciar sessió a la interfície administrativa Djano. Deselecciona-ho enlloc de esborrar comptes d'usuari." +msgid "" +"Designates whether this user can log into the Django admin. Unselect this " +"instead of deleting accounts." +msgstr "" +"Designa si aquest usuari pot iniciar sessió a la interfície administrativa " +"Djano. Deselecciona-ho enlloc de esborrar comptes d'usuari." #: contrib/auth/models.py:138 msgid "superuser status" msgstr "estat de superusuari" #: contrib/auth/models.py:138 -msgid "Designates that this user has all permissions without explicitly assigning them." -msgstr "Designa que aquest usuari té tots els permisos sense assignar-los explícitament." +msgid "" +"Designates that this user has all permissions without explicitly assigning " +"them." +msgstr "" +"Designa que aquest usuari té tots els permisos sense assignar-los " +"explícitament." #: contrib/auth/models.py:139 msgid "last login" @@ -1134,8 +1208,12 @@ msgid "date joined" msgstr "data de creació" #: contrib/auth/models.py:142 -msgid "In addition to the permissions manually assigned, this user will also get all permissions granted to each group he/she is in." -msgstr "Junt amb els permissos asignats manualment, aquest usuari tindrà, també, els permissos dels grups dels que sigui membre." +msgid "" +"In addition to the permissions manually assigned, this user will also get " +"all permissions granted to each group he/she is in." +msgstr "" +"Junt amb els permissos asignats manualment, aquest usuari tindrà, també, els " +"permissos dels grups dels que sigui membre." #: contrib/auth/models.py:143 msgid "user permissions" @@ -1173,8 +1251,7 @@ msgstr "missatge" msgid "Logged out" msgstr "Sessió finalitzada" -#: contrib/comments/models.py:67 -#: contrib/comments/models.py:169 +#: contrib/comments/models.py:67 contrib/comments/models.py:169 msgid "object ID" msgstr "ID de l'objecte" @@ -1182,8 +1259,7 @@ msgstr "ID de l'objecte" msgid "headline" msgstr "encapçalament" -#: contrib/comments/models.py:69 -#: contrib/comments/models.py:90 +#: contrib/comments/models.py:69 contrib/comments/models.py:90 #: contrib/comments/models.py:170 msgid "comment" msgstr "comentari" @@ -1224,13 +1300,11 @@ msgstr "qualificació #8" msgid "is valid rating" msgstr "és qualificació vàlida" -#: contrib/comments/models.py:83 -#: contrib/comments/models.py:172 +#: contrib/comments/models.py:83 contrib/comments/models.py:172 msgid "date/time submitted" msgstr "data/hora d'enviament" -#: contrib/comments/models.py:84 -#: contrib/comments/models.py:173 +#: contrib/comments/models.py:84 contrib/comments/models.py:173 msgid "is public" msgstr "és públic" @@ -1239,15 +1313,18 @@ msgid "is removed" msgstr "està eliminat" #: contrib/comments/models.py:86 -msgid "Check this box if the comment is inappropriate. A \"This comment has been removed\" message will be displayed instead." -msgstr "Marqui aquesta caixa si el comentari no és apropiat. En lloc seu es mostrarà \"Aquest comentari ha estat eliminat\" " +msgid "" +"Check this box if the comment is inappropriate. A \"This comment has been " +"removed\" message will be displayed instead." +msgstr "" +"Marqui aquesta caixa si el comentari no és apropiat. En lloc seu es mostrarà " +"\"Aquest comentari ha estat eliminat\" " #: contrib/comments/models.py:91 msgid "comments" msgstr "comentaris" -#: contrib/comments/models.py:134 -#: contrib/comments/models.py:213 +#: contrib/comments/models.py:134 contrib/comments/models.py:213 msgid "Content object" msgstr "Objecte Contingut" @@ -1389,25 +1466,30 @@ msgid "Your name:" msgstr "El seu nom:" #: contrib/comments/views/comments.py:28 -msgid "This rating is required because you've entered at least one other rating." +msgid "" +"This rating is required because you've entered at least one other rating." msgstr "Es precisa aquesta puntuació perquè has introduït almenys un altre." #: contrib/comments/views/comments.py:112 #, python-format msgid "" -"This comment was posted by a user who has posted fewer than %(count)s comment:\n" +"This comment was posted by a user who has posted fewer than %(count)s " +"comment:\n" "\n" "%(text)s" msgid_plural "" -"This comment was posted by a user who has posted fewer than %(count)s comments:\n" +"This comment was posted by a user who has posted fewer than %(count)s " +"comments:\n" "\n" "%(text)s" msgstr[0] "" -"Aquest comentari ha estat publicat per un usuari que ha publicat menys de %(count)s comentari:\n" +"Aquest comentari ha estat publicat per un usuari que ha publicat menys de %" +"(count)s comentari:\n" "\n" "%(text)s" msgstr[1] "" -"Aquest comentari ha estat publicat per un usuari que ha publicat menys de %(count)s comentaris:\n" +"Aquest comentari ha estat publicat per un usuari que ha publicat menys de %" +"(count)s comentaris:\n" "\n" "%(text)s" @@ -1435,17 +1517,23 @@ msgstr "Un o més dels caps requerits no ha estat sotmès" #: contrib/comments/views/comments.py:198 #: contrib/comments/views/comments.py:289 msgid "Somebody tampered with the comment form (security violation)" -msgstr "Algú està jugant amb el formulari de comentaris (violació de seguretat)" +msgstr "" +"Algú està jugant amb el formulari de comentaris (violació de seguretat)" #: contrib/comments/views/comments.py:208 #: contrib/comments/views/comments.py:295 -msgid "The comment form had an invalid 'target' parameter -- the object ID was invalid" -msgstr "El formulari de comentaris tenia un paràmetre 'target' invàlid -- el ID del objecte era invàlid" +msgid "" +"The comment form had an invalid 'target' parameter -- the object ID was " +"invalid" +msgstr "" +"El formulari de comentaris tenia un paràmetre 'target' invàlid -- el ID del " +"objecte era invàlid" #: contrib/comments/views/comments.py:259 #: contrib/comments/views/comments.py:324 msgid "The comment form didn't provide either 'preview' or 'post'" -msgstr "El formulari del comentari no ha proveït ni 'previsualitzar' ni 'enviar'" +msgstr "" +"El formulari del comentari no ha proveït ni 'previsualitzar' ni 'enviar'" #: contrib/comments/views/karma.py:21 msgid "Anonymous users cannot vote" @@ -1472,8 +1560,11 @@ msgid "content types" msgstr "tipus de continguts" #: contrib/flatpages/models.py:8 -msgid "Example: '/about/contact/'. Make sure to have leading and trailing slashes." -msgstr "Exemple: '/about/contact/'. Asseguri's de posar les barres al principi i al final." +msgid "" +"Example: '/about/contact/'. Make sure to have leading and trailing slashes." +msgstr "" +"Exemple: '/about/contact/'. Asseguri's de posar les barres al principi i al " +"final." #: contrib/flatpages/models.py:9 msgid "title" @@ -1492,8 +1583,12 @@ msgid "template name" msgstr "nom de la plantilla" #: contrib/flatpages/models.py:13 -msgid "Example: 'flatpages/contact_page.html'. If this isn't provided, the system will use 'flatpages/default.html'." -msgstr "Exemple: 'flatpages/contact_page.html'. Si no el proporciona, el sistema utilitzarà 'flatpages/defaula.htmlt'." +msgid "" +"Example: 'flatpages/contact_page.html'. If this isn't provided, the system " +"will use 'flatpages/default.html'." +msgstr "" +"Exemple: 'flatpages/contact_page.html'. Si no el proporciona, el sistema " +"utilitzarà 'flatpages/defaula.htmlt'." #: contrib/flatpages/models.py:14 msgid "registration required" @@ -1596,15 +1691,12 @@ msgstr "demà" msgid "yesterday" msgstr "ahir" -#: contrib/localflavor/ar/forms.py:30 -#: contrib/localflavor/ar/forms.py:38 +#: contrib/localflavor/ar/forms.py:30 contrib/localflavor/ar/forms.py:38 msgid "Enter a postal code in the format NNNN or ANNNNAAA." msgstr "Introdueixi un codi postal en el format NNNN or ANNNNAAA." -#: contrib/localflavor/ar/forms.py:61 -#: contrib/localflavor/br/forms.py:103 -#: contrib/localflavor/pe/forms.py:34 -#: contrib/localflavor/pe/forms.py:57 +#: contrib/localflavor/ar/forms.py:61 contrib/localflavor/br/forms.py:103 +#: contrib/localflavor/pe/forms.py:34 contrib/localflavor/pe/forms.py:57 msgid "This field requires only numbers." msgstr "Aquest camps requereix només números." @@ -1614,7 +1706,8 @@ msgstr "Aquest camp requereix 7 o 8 dígits." #: contrib/localflavor/ar/forms.py:75 msgid "Enter a valid CUIT in XX-XXXXXXXX-X or XXXXXXXXXXXX format." -msgstr "Introdueixi un número CUIT vàlid en el format XX-XXXXXXXX-X o XXXXXXXXXXXX." +msgstr "" +"Introdueixi un número CUIT vàlid en el format XX-XXXXXXXX-X o XXXXXXXXXXXX." #: contrib/localflavor/ar/forms.py:88 msgid "Invalid CUIT." @@ -1633,8 +1726,12 @@ msgid "Phone numbers must be in XX-XXXX-XXXX format." msgstr "El número de telèfon ha de ser en el format XX-XXXX-XXXX." #: contrib/localflavor/br/forms.py:68 -msgid "Select a valid brazilian state. That state is not one of the available states." -msgstr "Seleccioni un estat brasiler vàlid. Aquest estat no és un dels estats disponibles." +msgid "" +"Select a valid brazilian state. That state is not one of the available " +"states." +msgstr "" +"Seleccioni un estat brasiler vàlid. Aquest estat no és un dels estats " +"disponibles." #: contrib/localflavor/br/forms.py:105 msgid "This field requires at most 11 digits or 14 characters." @@ -1658,7 +1755,9 @@ msgstr "Introdueixi un codi postal en el format XXX XXX." #: contrib/localflavor/ca/forms.py:81 msgid "Enter a valid Canadian Social Insurance number in XXX-XXX-XXXX format." -msgstr "Introdueixi un número vàlid de la Seguretat Social de Canada en el format XXX-XXX-XXXX." +msgstr "" +"Introdueixi un número vàlid de la Seguretat Social de Canada en el format " +"XXX-XXX-XXXX." #: contrib/localflavor/ch/ch_states.py:5 msgid "Aargau" @@ -1764,14 +1863,17 @@ msgstr "Zug" msgid "Zurich" msgstr "Zurich" -#: contrib/localflavor/ch/forms.py:18 -#: contrib/localflavor/no/forms.py:14 +#: contrib/localflavor/ch/forms.py:18 contrib/localflavor/no/forms.py:14 msgid "Enter a zip code in the format XXXX." msgstr "Introdueixi un codi zip en el format XXXX." #: contrib/localflavor/ch/forms.py:90 -msgid "Enter a valid Swiss identity or passport card number in X1234567<0 or 1234567890 format." -msgstr "Introdueixi un número de identificació o de passaport Suïssos en els formats 1234567890 o X1234567<0." +msgid "" +"Enter a valid Swiss identity or passport card number in X1234567<0 or " +"1234567890 format." +msgstr "" +"Introdueixi un número de identificació o de passaport Suïssos en els formats " +"1234567890 o X1234567<0." #: contrib/localflavor/cl/forms.py:32 msgid "Enter valid a Chilean RUT. The format is XX.XXX.XXX-X." @@ -1845,15 +1947,18 @@ msgstr "Schleswig-Holstein" msgid "Thuringia" msgstr "Thuringia" -#: contrib/localflavor/de/forms.py:16 -#: contrib/localflavor/fi/forms.py:14 +#: contrib/localflavor/de/forms.py:16 contrib/localflavor/fi/forms.py:14 #: contrib/localflavor/fr/forms.py:17 msgid "Enter a zip code in the format XXXXX." msgstr "Introdueixi un codi zip en el format XXXXX." #: contrib/localflavor/de/forms.py:60 -msgid "Enter a valid German identity card number in XXXXXXXXXXX-XXXXXXX-XXXXXXX-X format." -msgstr "Introdueixi un número de tarjeta d'identificació alemany vàlid en el format XXXXXXXXXXX-XXXXXXX-XXXXXXX-X." +msgid "" +"Enter a valid German identity card number in XXXXXXXXXXX-XXXXXXX-XXXXXXX-X " +"format." +msgstr "" +"Introdueixi un número de tarjeta d'identificació alemany vàlid en el format " +"XXXXXXXXXXX-XXXXXXX-XXXXXXX-X." #: contrib/localflavor/es/es_provinces.py:5 msgid "Arava" @@ -2127,11 +2232,14 @@ msgid "Enter a valid postal code in the range and format 01XXX - 52XXX." msgstr "Introdueixi un codi postal en rang i format 01XXX - 52XXX." #: contrib/localflavor/es/forms.py:39 -msgid "Enter a valid phone number in one of the formats 6XXXXXXXX, 8XXXXXXXX or 9XXXXXXXX." -msgstr "Introdueixi un número de telèfon vàlid en un dels formats 6XXXXXXXX, 8XXXXXXXX o 9XXXXXXXX." +msgid "" +"Enter a valid phone number in one of the formats 6XXXXXXXX, 8XXXXXXXX or " +"9XXXXXXXX." +msgstr "" +"Introdueixi un número de telèfon vàlid en un dels formats 6XXXXXXXX, " +"8XXXXXXXX o 9XXXXXXXX." -#: contrib/localflavor/es/forms.py:73 -#: contrib/localflavor/es/forms.py:108 +#: contrib/localflavor/es/forms.py:73 contrib/localflavor/es/forms.py:108 #: db/models/fields/related.py:55 #, python-format msgid "Please enter a valid %s." @@ -2150,15 +2258,17 @@ msgid "Invalid checksum for CIF." msgstr "Validació invàlida del CIF." #: contrib/localflavor/es/forms.py:136 -msgid "Please enter a valid bank account number in format XXXX-XXXX-XX-XXXXXXXXXX." -msgstr "Introdueixi un número de compte bancari vàlid en el format XXXX-XXXX-XX-XXXXXXXXXX." +msgid "" +"Please enter a valid bank account number in format XXXX-XXXX-XX-XXXXXXXXXX." +msgstr "" +"Introdueixi un número de compte bancari vàlid en el format XXXX-XXXX-XX-" +"XXXXXXXXXX." #: contrib/localflavor/es/forms.py:150 msgid "Invalid checksum for bank account number." msgstr "Validació invàlida del número de compte bancari." -#: contrib/localflavor/fi/forms.py:40 -#: contrib/localflavor/fi/forms.py:45 +#: contrib/localflavor/fi/forms.py:40 contrib/localflavor/fi/forms.py:45 msgid "Enter a valid Finnish social security number." msgstr "Introdueixi un número vàlid de la seguretat social finlandesa." @@ -2167,8 +2277,10 @@ msgid "Enter a zip code in the format XXXXXXX." msgstr "Introdueixi un codi zip en el format XXXXXXX." #: contrib/localflavor/is_/forms.py:17 -msgid "Enter a valid Icelandic identification number. The format is XXXXXX-XXXX." -msgstr "Introdueixi un número de identificació d'Islàndia. El format és XXXXXX-XXXX." +msgid "" +"Enter a valid Icelandic identification number. The format is XXXXXX-XXXX." +msgstr "" +"Introdueixi un número de identificació d'Islàndia. El format és XXXXXX-XXXX." #: contrib/localflavor/is_/forms.py:31 msgid "The Icelandic identification number is not valid." @@ -2459,7 +2571,8 @@ msgid "Wrong checksum for the National Identification Number." msgstr "Validació invàlida del número d'identificació nacional." #: contrib/localflavor/pl/forms.py:72 -msgid "Enter a tax number field (NIP) in the format XXX-XXX-XX-XX or XX-XX-XXX-XXX." +msgid "" +"Enter a tax number field (NIP) in the format XXX-XXX-XX-XX or XX-XX-XXX-XXX." msgstr "Introdueixi un número NIP en el format XXX-XXX-XX-XX o XX-XX-XXX-XXX." #: contrib/localflavor/pl/forms.py:78 @@ -2468,7 +2581,8 @@ msgstr "Validació invàlida del número tributari (NIP)." #: contrib/localflavor/pl/forms.py:107 msgid "National Business Register Number (REGON) consists of 7 or 9 digits." -msgstr "El número nacional de registre de negocis (REGON) consisteix en 7 o 9 dígits." +msgstr "" +"El número nacional de registre de negocis (REGON) consisteix en 7 o 9 dígits." #: contrib/localflavor/pl/forms.py:113 msgid "Wrong checksum for the National Business Register Number (REGON)." @@ -2896,7 +3010,9 @@ msgstr "Regió de Zilina" #: contrib/localflavor/uk/forms.py:18 msgid "Enter a postcode. A space is required between the two postcode parts." -msgstr "Introdueixi un codi postal. És necessari un espai entre les dues parts del codi postal." +msgstr "" +"Introdueixi un codi postal. És necessari un espai entre les dues parts del " +"codi postal." #: contrib/localflavor/us/forms.py:18 msgid "Enter a zip code in the format XXXXX or XXXXX-XXXX." @@ -2904,23 +3020,33 @@ msgstr "Introdueixi un codi zip en el format XXXXX o XXXXX-XXXX." #: contrib/localflavor/us/forms.py:51 msgid "Enter a valid U.S. Social Security number in XXX-XX-XXXX format." -msgstr "Introdueixi un número vàlid de la Seguretat Social dels E.U.A. en el format XXX-XX-XXXX." +msgstr "" +"Introdueixi un número vàlid de la Seguretat Social dels E.U.A. en el format " +"XXX-XX-XXXX." #: contrib/redirects/models.py:7 msgid "redirect from" msgstr "redreçar des de" #: contrib/redirects/models.py:8 -msgid "This should be an absolute path, excluding the domain name. Example: '/events/search/'." -msgstr "Aquesta ruta hauria de ser el camí absolut, excloent-ne el nom del domini. Exemple '/events/search/'." +msgid "" +"This should be an absolute path, excluding the domain name. Example: '/" +"events/search/'." +msgstr "" +"Aquesta ruta hauria de ser el camí absolut, excloent-ne el nom del domini. " +"Exemple '/events/search/'." #: contrib/redirects/models.py:9 msgid "redirect to" msgstr "redreçar a" #: contrib/redirects/models.py:10 -msgid "This can be either an absolute path (as above) or a full URL starting with 'http://'." -msgstr "Això pot ser bé una ruta absoluta (com abans) o una URL completa que comenci per http:// ." +msgid "" +"This can be either an absolute path (as above) or a full URL starting with " +"'http://'." +msgstr "" +"Això pot ser bé una ruta absoluta (com abans) o una URL completa que comenci " +"per http:// ." #: contrib/redirects/models.py:13 msgid "redirect" @@ -2971,12 +3097,17 @@ msgid "This value must contain only letters, numbers and underscores." msgstr "Aquest valor ha de contenir només números, guions, i guions baixos." #: core/validators.py:76 -msgid "This value must contain only letters, numbers, underscores, dashes or slashes." -msgstr "Aquest valor ha de contenir només lletres, números, guions, guions baixos, i barres (/)." +msgid "" +"This value must contain only letters, numbers, underscores, dashes or " +"slashes." +msgstr "" +"Aquest valor ha de contenir només lletres, números, guions, guions baixos, i " +"barres (/)." #: core/validators.py:80 msgid "This value must contain only letters, numbers, underscores or hyphens." -msgstr "Aquest valor ha de contenir només lletres, números, guions o guions baixos" +msgstr "" +"Aquest valor ha de contenir només lletres, números, guions o guions baixos" #: core/validators.py:84 msgid "Uppercase letters are not allowed here." @@ -3010,8 +3141,7 @@ msgstr "No s'admeten caracters no numèrics." msgid "This value can't be comprised solely of digits." msgstr "Aquest valor no pot contenir només dígits." -#: core/validators.py:128 -#: newforms/fields.py:149 +#: core/validators.py:128 newforms/fields.py:157 msgid "Enter a whole number." msgstr "Introdueixi un número sencer." @@ -3028,8 +3158,7 @@ msgstr "L'any ha de ser posterior al 1900" msgid "Invalid date: %s" msgstr "Data invàlida: %s" -#: core/validators.py:156 -#: db/models/fields/__init__.py:505 +#: core/validators.py:156 db/models/fields/__init__.py:509 msgid "Enter a valid date in YYYY-MM-DD format." msgstr "Introdueixi una data vàlida en el forma AAAA-MM-DD." @@ -3037,27 +3166,27 @@ msgstr "Introdueixi una data vàlida en el forma AAAA-MM-DD." msgid "Enter a valid time in HH:MM format." msgstr "Introdueixi una hora vàlida en el format HH:MM." -#: core/validators.py:165 -#: db/models/fields/__init__.py:579 +#: core/validators.py:165 db/models/fields/__init__.py:583 msgid "Enter a valid date/time in YYYY-MM-DD HH:MM format." msgstr "Introdueixi un data/hora vàlida en format YYYY-MM-DD HH:MM." -#: core/validators.py:170 -#: newforms/fields.py:343 +#: core/validators.py:170 newforms/fields.py:408 msgid "Enter a valid e-mail address." msgstr "Introdueixi una adreça de correu vàlida." -#: core/validators.py:182 -#: core/validators.py:474 -#: newforms/fields.py:377 +#: core/validators.py:182 core/validators.py:474 newforms/fields.py:438 #: oldforms/__init__.py:686 msgid "No file was submitted. Check the encoding type on the form." -msgstr "No s'ha enviat cap fitxer. Comprovi el tipus de codificació del formulari." +msgstr "" +"No s'ha enviat cap fitxer. Comprovi el tipus de codificació del formulari." -#: core/validators.py:193 -#: newforms/fields.py:405 -msgid "Upload a valid image. The file you uploaded was either not an image or a corrupted image." -msgstr "Envii una imatge vàilda. El fitxer que ha enviat no era una imatge o estaba corrupte." +#: core/validators.py:193 newforms/fields.py:462 +msgid "" +"Upload a valid image. The file you uploaded was either not an image or a " +"corrupted image." +msgstr "" +"Envii una imatge vàilda. El fitxer que ha enviat no era una imatge o estaba " +"corrupte." #: core/validators.py:200 #, python-format @@ -3067,7 +3196,9 @@ msgstr "La URL %s no apunta una imatge vàlida." #: core/validators.py:204 #, python-format msgid "Phone numbers must be in XXX-XXX-XXXX format. \"%s\" is invalid." -msgstr "El números de telèfon han de guardar-se en el format XXX-XXX-XXXX. \"%s\" no és vàlid." +msgstr "" +"El números de telèfon han de guardar-se en el format XXX-XXX-XXXX. \"%s\" no " +"és vàlid." #: core/validators.py:212 #, python-format @@ -3097,8 +3228,7 @@ msgstr "XML incorrectament formatejat: %s" msgid "Invalid URL: %s" msgstr "URL invalida: %s" -#: core/validators.py:259 -#: core/validators.py:261 +#: core/validators.py:259 core/validators.py:261 #, python-format msgid "The URL %s is a broken link." msgstr "La URL %sés un enllaç trencat." @@ -3123,8 +3253,7 @@ msgstr "Aquest camp ha de concordar amb el camp '%s'." msgid "Please enter something for at least one field." msgstr "Si us plau, introdueixi alguna cosa alemnys en un camp." -#: core/validators.py:316 -#: core/validators.py:327 +#: core/validators.py:316 core/validators.py:327 msgid "Please enter both fields or leave them both empty." msgstr "Si us plau, ompli els dos camps o deixi'ls tots dos en blanc." @@ -3169,23 +3298,37 @@ msgstr "Si us plau, introdueixi un número decimal vàlid." #: core/validators.py:444 #, python-format msgid "Please enter a valid decimal number with at most %s total digit." -msgid_plural "Please enter a valid decimal number with at most %s total digits." -msgstr[0] "Si us plau, introdueixi un número decimal vàlid de com a màxim %s digit." -msgstr[1] "Si us plau, introdueixi un número decimal vàlid de com a màxim %s digits." +msgid_plural "" +"Please enter a valid decimal number with at most %s total digits." +msgstr[0] "" +"Si us plau, introdueixi un número decimal vàlid de com a màxim %s digit." +msgstr[1] "" +"Si us plau, introdueixi un número decimal vàlid de com a màxim %s digits." #: core/validators.py:447 #, python-format -msgid "Please enter a valid decimal number with a whole part of at most %s digit." -msgid_plural "Please enter a valid decimal number with a whole part of at most %s digits." -msgstr[0] "Si us plau, introdueixi un número decimal vàlid que la seva part sencera sigui de com a màxim %s digit." -msgstr[1] "Si us plau, introdueixi un número decimal vàlid que la seva part sencera sigui de com a màxim %s digits." +msgid "" +"Please enter a valid decimal number with a whole part of at most %s digit." +msgid_plural "" +"Please enter a valid decimal number with a whole part of at most %s digits." +msgstr[0] "" +"Si us plau, introdueixi un número decimal vàlid que la seva part sencera " +"sigui de com a màxim %s digit." +msgstr[1] "" +"Si us plau, introdueixi un número decimal vàlid que la seva part sencera " +"sigui de com a màxim %s digits." #: core/validators.py:450 #, python-format msgid "Please enter a valid decimal number with at most %s decimal place." -msgid_plural "Please enter a valid decimal number with at most %s decimal places." -msgstr[0] "Si us plau, introdueixi un número decimal vàlid de com a màxim %s posició decimal." -msgstr[1] "Si us plau, introdueixi un número decimal vàlid de com a màxim %s posicions decimals." +msgid_plural "" +"Please enter a valid decimal number with at most %s decimal places." +msgstr[0] "" +"Si us plau, introdueixi un número decimal vàlid de com a màxim %s posició " +"decimal." +msgstr[1] "" +"Si us plau, introdueixi un número decimal vàlid de com a màxim %s posicions " +"decimals." #: core/validators.py:458 msgid "Please enter a valid floating point number." @@ -3216,38 +3359,65 @@ msgstr "No s'ha pogut obtenir res de %s." #: core/validators.py:539 #, python-format -msgid "The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'." -msgstr "La URL %(url)s ha va tornar la capcelera Content-Type '%(contenttype)s', que no és vàlida." +msgid "" +"The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'." +msgstr "" +"La URL %(url)s ha va tornar la capcelera Content-Type '%(contenttype)s', que " +"no és vàlida." #: core/validators.py:572 #, python-format -msgid "Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with \"%(start)s\".)" -msgstr "Si us plau, tanqui l'etiqueta %(tag)s des de la línia %(line)s. (La línia comença amb \"%(start)s\".)" +msgid "" +"Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with " +"\"%(start)s\".)" +msgstr "" +"Si us plau, tanqui l'etiqueta %(tag)s des de la línia %(line)s. (La línia " +"comença amb \"%(start)s\".)" #: core/validators.py:576 #, python-format -msgid "Some text starting on line %(line)s is not allowed in that context. (Line starts with \"%(start)s\".)" -msgstr "Part del text que comença en la línia %(line)s no està permès en aquest context. (La línia comença per \"%(start)s\".)" +msgid "" +"Some text starting on line %(line)s is not allowed in that context. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"Part del text que comença en la línia %(line)s no està permès en aquest " +"context. (La línia comença per \"%(start)s\".)" #: core/validators.py:581 #, python-format -msgid "\"%(attr)s\" on line %(line)s is an invalid attribute. (Line starts with \"%(start)s\".)" -msgstr "El \"%(attr)s\" de la línia %(line)s no és un atribut vàlid. (La línia comença per \"%(start)s\".)" +msgid "" +"\"%(attr)s\" on line %(line)s is an invalid attribute. (Line starts with \"%" +"(start)s\".)" +msgstr "" +"El \"%(attr)s\" de la línia %(line)s no és un atribut vàlid. (La línia " +"comença per \"%(start)s\".)" #: core/validators.py:586 #, python-format -msgid "\"<%(tag)s>\" on line %(line)s is an invalid tag. (Line starts with \"%(start)s\".)" -msgstr "La \"<%(tag)s>\" de la línia %(line)s no és una etiqueta vàlida. (La línia comença per \"%(start)s\".)" +msgid "" +"\"<%(tag)s>\" on line %(line)s is an invalid tag. (Line starts with \"%" +"(start)s\".)" +msgstr "" +"La \"<%(tag)s>\" de la línia %(line)s no és una etiqueta vàlida. (La línia " +"comença per \"%(start)s\".)" #: core/validators.py:590 #, python-format -msgid "A tag on line %(line)s is missing one or more required attributes. (Line starts with \"%(start)s\".)" -msgstr "Una etiqueta de la línia %(line)s li falta un o més atributs requerits.(La línia comença per \"%(start)s\".)" +msgid "" +"A tag on line %(line)s is missing one or more required attributes. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"Una etiqueta de la línia %(line)s li falta un o més atributs requerits.(La " +"línia comença per \"%(start)s\".)" #: core/validators.py:595 #, python-format -msgid "The \"%(attr)s\" attribute on line %(line)s has an invalid value. (Line starts with \"%(start)s\".)" -msgstr "L'atribut \"%(attr)s\" de la línia %(line)s té un valor que no és vàlid. (La línia comença per \"%(start)s\".)" +msgid "" +"The \"%(attr)s\" attribute on line %(line)s has an invalid value. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"L'atribut \"%(attr)s\" de la línia %(line)s té un valor que no és vàlid. (La " +"línia comença per \"%(start)s\".)" #: db/models/manipulators.py:308 #, python-format @@ -3259,40 +3429,33 @@ msgstr "Ja existeix un %(object)s del tipus %(type)s amb aquest %(field)s." msgid "%(optname)s with this %(fieldname)s already exists." msgstr "Ja existeix %(optname)s amb auqest %(fieldname)s." -#: db/models/fields/__init__.py:159 -#: db/models/fields/__init__.py:316 -#: db/models/fields/__init__.py:731 -#: db/models/fields/__init__.py:742 -#: newforms/fields.py:93 -#: newforms/fields.py:513 -#: newforms/fields.py:589 -#: newforms/fields.py:600 -#: newforms/models.py:193 -#: oldforms/__init__.py:373 +#: db/models/fields/__init__.py:161 db/models/fields/__init__.py:318 +#: db/models/fields/__init__.py:735 db/models/fields/__init__.py:746 +#: newforms/fields.py:45 newforms/models.py:211 oldforms/__init__.py:373 msgid "This field is required." msgstr "Aquest camp és obligatori." -#: db/models/fields/__init__.py:414 +#: db/models/fields/__init__.py:418 msgid "This value must be an integer." msgstr "Aquest valor ha de ser un enter." -#: db/models/fields/__init__.py:450 +#: db/models/fields/__init__.py:454 msgid "This value must be either True or False." msgstr "Aquest valor ha de ser True (Veritat) o False (Fals)" -#: db/models/fields/__init__.py:471 +#: db/models/fields/__init__.py:475 msgid "This field cannot be null." msgstr "Aquest camp no pot ser null (estar buit)." -#: db/models/fields/__init__.py:640 +#: db/models/fields/__init__.py:644 msgid "This value must be a decimal number." msgstr "Aquest valor ha de ser un número decimal." -#: db/models/fields/__init__.py:751 +#: db/models/fields/__init__.py:755 msgid "Enter a valid filename." msgstr "Introdueixi un nom de fitxer vàlid." -#: db/models/fields/__init__.py:900 +#: db/models/fields/__init__.py:904 msgid "This value must be either None, True or False." msgstr "Aquest valor ha de ser None (Cap), True (Veritat) o False (Fals)" @@ -3301,117 +3464,120 @@ msgid "Separate multiple IDs with commas." msgstr "Separi múltiples IDs amb comes." #: db/models/fields/related.py:660 -msgid "Hold down \"Control\", or \"Command\" on a Mac, to select more than one." +msgid "" +"Hold down \"Control\", or \"Command\" on a Mac, to select more than one." msgstr "Premi \"Control\" o \"Command\" en un Mac per escollir més d'un." #: db/models/fields/related.py:707 #, python-format msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid." -msgid_plural "Please enter valid %(self)s IDs. The values %(value)r are invalid." -msgstr[0] "Si us plau, introdueixi els IDs de %(self)s vàlids. El valor %(value)r és invàlid." -msgstr[1] "Si us plau, introdueixi IDs de %(self)s vàlids. Els valors %(value)r són invàlids." +msgid_plural "" +"Please enter valid %(self)s IDs. The values %(value)r are invalid." +msgstr[0] "" +"Si us plau, introdueixi els IDs de %(self)s vàlids. El valor %(value)r és " +"invàlid." +msgstr[1] "" +"Si us plau, introdueixi IDs de %(self)s vàlids. Els valors %(value)r són " +"invàlids." -#: newforms/fields.py:123 +#: newforms/fields.py:46 +msgid "Enter a valid value." +msgstr "Introdueixi un valor vàlid." + +#: newforms/fields.py:129 #, python-format msgid "Ensure this value has at most %(max)d characters (it has %(length)d)." -msgstr "Asseguris de que el valor té com a màxim %(max)d caràcters (en té %(length)d)." +msgstr "" +"Asseguris de que el valor té com a màxim %(max)d caràcters (en té %(length)" +"d)." -#: newforms/fields.py:125 +#: newforms/fields.py:130 #, python-format msgid "Ensure this value has at least %(min)d characters (it has %(length)d)." -msgstr "Asseguris de que el valor té com a mínim %(min)d caràcters (en té %(length)d)." +msgstr "" +"Asseguris de que el valor té com a mínim %(min)d caràcters (en té %(length)" +"d)." -#: newforms/fields.py:151 -#: newforms/fields.py:174 -#: newforms/fields.py:204 +#: newforms/fields.py:158 newforms/fields.py:187 newforms/fields.py:216 #, python-format msgid "Ensure this value is less than or equal to %s." msgstr "Aquest valor ha de ser menor o igual a %s." -#: newforms/fields.py:153 -#: newforms/fields.py:176 -#: newforms/fields.py:206 +#: newforms/fields.py:159 newforms/fields.py:188 newforms/fields.py:217 #, python-format msgid "Ensure this value is greater than or equal to %s." msgstr "Asseguris de que aquest valor sigui superior o igual a %s." -#: newforms/fields.py:172 -#: newforms/fields.py:199 +#: newforms/fields.py:186 newforms/fields.py:215 msgid "Enter a number." msgstr "Introdueixi un número." -#: newforms/fields.py:208 +#: newforms/fields.py:218 #, python-format msgid "Ensure that there are no more than %s digits in total." msgstr "Asseguris de que no hi ha més de %s dígits en total." -#: newforms/fields.py:210 +#: newforms/fields.py:219 #, python-format msgid "Ensure that there are no more than %s decimal places." msgstr "Asseguris de que no hi ha més de %s decimals." -#: newforms/fields.py:212 +#: newforms/fields.py:220 #, python-format msgid "Ensure that there are no more than %s digits before the decimal point." msgstr "Asseguris de que no hia ha més de %s dígits decimals." -#: newforms/fields.py:245 -#: newforms/fields.py:633 +#: newforms/fields.py:268 newforms/fields.py:724 msgid "Enter a valid date." msgstr "Introdueixi una data vàlida." -#: newforms/fields.py:272 -#: newforms/fields.py:635 +#: newforms/fields.py:301 newforms/fields.py:725 msgid "Enter a valid time." msgstr "Introdueixi una hora vàlida." -#: newforms/fields.py:308 +#: newforms/fields.py:340 msgid "Enter a valid date/time." msgstr "Introdueixi una data/hora vàlides." -#: newforms/fields.py:321 -msgid "Enter a valid value." -msgstr "Introdueixi un valor vàlid." - -#: newforms/fields.py:379 +#: newforms/fields.py:439 msgid "No file was submitted." msgstr "No s'ha enviat cap fitxer." -#: newforms/fields.py:381 -#: oldforms/__init__.py:688 +#: newforms/fields.py:440 oldforms/__init__.py:688 msgid "The submitted file is empty." msgstr "El fitxer enviat està buit." -#: newforms/fields.py:419 -#: newforms/fields.py:444 +#: newforms/fields.py:498 msgid "Enter a valid URL." msgstr "Introdueixi una URL vàlida." -#: newforms/fields.py:446 +#: newforms/fields.py:499 msgid "This URL appears to be a broken link." msgstr "Aquesta URL sembla ser un enllaç trencat." -#: newforms/fields.py:501 -#: newforms/models.py:180 +#: newforms/fields.py:560 newforms/models.py:194 msgid "Select a valid choice. That choice is not one of the available choices." -msgstr "Esculli una opció vàlida; Aquesta opció no és una de les opcions disponibles." +msgstr "" +"Esculli una opció vàlida; Aquesta opció no és una de les opcions disponibles." -#: newforms/fields.py:517 -#: newforms/fields.py:593 -#: newforms/models.py:197 +#: newforms/fields.py:599 +#, python-format +msgid "Select a valid choice. %(value)s is not one of the available choices." +msgstr "Esculli una opció vàlida. %(value)s no és una de les opcions vàlides." + +#: newforms/fields.py:600 newforms/fields.py:662 newforms/models.py:215 msgid "Enter a list of values." msgstr "Introdueixi una llista de valors." -#: newforms/fields.py:523 -#: newforms/models.py:203 +#: newforms/fields.py:753 +msgid "Enter a valid IPv4 address." +msgstr "Introdueixi una adreça IPv4 vàlida." + +#: newforms/models.py:221 #, python-format msgid "Select a valid choice. %s is not one of the available choices." msgstr "Esculli una opció vàlida; %s' no és una de les opcions vàlides." -#: newforms/fields.py:644 -msgid "Enter a valid IPv4 address." -msgstr "Introdueixi una adreça IPv4 vàlida." - #: oldforms/__init__.py:408 #, python-format msgid "Ensure your text is less than %s character." @@ -3423,9 +3589,7 @@ msgstr[1] "Asseguris de que el seu texte té menys de %s caracters." msgid "Line breaks are not allowed here." msgstr "No es permeten salts de línia." -#: oldforms/__init__.py:511 -#: oldforms/__init__.py:585 -#: oldforms/__init__.py:624 +#: oldforms/__init__.py:511 oldforms/__init__.py:585 oldforms/__init__.py:624 #, python-format msgid "Select a valid choice; '%(data)s' is not in %(choices)s." msgstr "Esculli una opció vàlida; %(data)s' no està dintre de %(choices)s." @@ -3442,28 +3606,28 @@ msgstr "Introdueixi un número positiu." msgid "Enter a whole number between 0 and 32,767." msgstr "Introdueixi un número entre 0 i 32,767." -#: template/defaultfilters.py:541 +#: template/defaultfilters.py:555 msgid "yes,no,maybe" msgstr "si,no,potser" -#: template/defaultfilters.py:570 +#: template/defaultfilters.py:585 #, python-format msgid "%(size)d byte" msgid_plural "%(size)d bytes" msgstr[0] "%(size)d byte" msgstr[1] "%(size)d bytes" -#: template/defaultfilters.py:572 +#: template/defaultfilters.py:587 #, python-format msgid "%.1f KB" msgstr "%.1f KB" -#: template/defaultfilters.py:574 +#: template/defaultfilters.py:589 #, python-format msgid "%.1f MB" msgstr "%.1f MB" -#: template/defaultfilters.py:575 +#: template/defaultfilters.py:590 #, python-format msgid "%.1f GB" msgstr "%.1f GB" @@ -3556,28 +3720,23 @@ msgstr "Gener" msgid "February" msgstr "Febrer" -#: utils/dates.py:18 -#: utils/dates.py:31 +#: utils/dates.py:18 utils/dates.py:31 msgid "March" msgstr "Març" -#: utils/dates.py:18 -#: utils/dates.py:31 +#: utils/dates.py:18 utils/dates.py:31 msgid "April" msgstr "Abril" -#: utils/dates.py:18 -#: utils/dates.py:31 +#: utils/dates.py:18 utils/dates.py:31 msgid "May" msgstr "Maig" -#: utils/dates.py:18 -#: utils/dates.py:31 +#: utils/dates.py:18 utils/dates.py:31 msgid "June" msgstr "Juny" -#: utils/dates.py:19 -#: utils/dates.py:31 +#: utils/dates.py:19 utils/dates.py:31 msgid "July" msgstr "Juliol" @@ -3731,23 +3890,23 @@ msgstr "%(number)d %(type)s" msgid ", %(number)d %(type)s" msgstr ", %(number)d %(type)s" -#: utils/translation/trans_real.py:391 +#: utils/translation/trans_real.py:395 msgid "DATE_FORMAT" msgstr "F j, Y" -#: utils/translation/trans_real.py:392 +#: utils/translation/trans_real.py:396 msgid "DATETIME_FORMAT" msgstr "F j, Y, H:i" -#: utils/translation/trans_real.py:393 +#: utils/translation/trans_real.py:397 msgid "TIME_FORMAT" msgstr "H:i" -#: utils/translation/trans_real.py:409 +#: utils/translation/trans_real.py:413 msgid "YEAR_MONTH_FORMAT" msgstr "j de/d' F del Y" -#: utils/translation/trans_real.py:410 +#: utils/translation/trans_real.py:414 msgid "MONTH_DAY_FORMAT" msgstr "j de/d' F del Y" @@ -3782,8 +3941,10 @@ msgstr "El %(verbose_name)s s'ha eliminat." #~ "(count)s comentaris:\n" #~ "\n" #~ "%(text)s" + #~ msgid "AnonymousUser" #~ msgstr "AnonymousUser" + #~ msgid "" #~ "Please enter a valid decimal number with a whole part of at most %s digit." #~ "Please enter a valid decimal number with a whole part of at most %s " @@ -3792,6 +3953,7 @@ msgstr "El %(verbose_name)s s'ha eliminat." #~ "Si us plau, introdueixi un número decimal vàlid amb la part entera amb " #~ "com a màxim %s dígit.Si us plau, introdueixi un número decimal vàlid amb " #~ "la part entera amb com a màxim %s dígits." + #~ msgid "" #~ "Please enter a valid decimal number with at most %s decimal place.Please " #~ "enter a valid decimal number with at most %s decimal places." @@ -3799,6 +3961,6 @@ msgstr "El %(verbose_name)s s'ha eliminat." #~ "Si us plau, introdueixi un número decimal vàlid amb no més de %s dígit en " #~ "la part decimal.Si us plau, introdueixi un número decimal vàlid amb no " #~ "més de %s dígits en la part decimal." + #~ msgid "%d milliseconds" #~ msgstr "%d milisegons" - diff --git a/django/conf/locale/es/LC_MESSAGES/django.mo b/django/conf/locale/es/LC_MESSAGES/django.mo index 29b411467e4bb30a442b596b9129e233f487e92c..728c53a15bad6a3e60fd75780780ae902fec51dc 100644 GIT binary patch delta 17 ZcmZ2*pLxN3<_+A7nTql^^Ddt30{};q2Z#Uw delta 17 ZcmZ2*pLxN3<_+A7nevM^^Ddt30{};n2Z#Uw diff --git a/django/conf/locale/es/LC_MESSAGES/django.po b/django/conf/locale/es/LC_MESSAGES/django.po index 786889106e..509e01f7c0 100644 --- a/django/conf/locale/es/LC_MESSAGES/django.po +++ b/django/conf/locale/es/LC_MESSAGES/django.po @@ -1016,7 +1016,7 @@ msgstr "Escoja %s para modificar" #: contrib/admin/views/main.py:780 msgid "Database error" -msgstr "Erorr en la base de datos" +msgstr "Error en la base de datos" #: contrib/auth/forms.py:17 #: contrib/auth/forms.py:138 diff --git a/django/conf/locale/he/LC_MESSAGES/django.mo b/django/conf/locale/he/LC_MESSAGES/django.mo index 49ea931fef82de5e3d3f255d562d2ceec3244e0d..9f4e1245276daccae2f7855771f81bfebb1cfe7a 100644 GIT binary patch delta 17643 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 delta 17324 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 diff --git a/django/conf/locale/he/LC_MESSAGES/django.po b/django/conf/locale/he/LC_MESSAGES/django.po index c0646a5518..191a06cab7 100644 --- a/django/conf/locale/he/LC_MESSAGES/django.po +++ b/django/conf/locale/he/LC_MESSAGES/django.po @@ -8,13 +8,13 @@ msgid "" msgstr "" "Project-Id-Version: Django 0.95\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2007-10-05 18:10+0200\n" +"POT-Creation-Date: 2007-11-10 02:19+0200\n" "PO-Revision-Date: 2007-10-07 01:31+0200\n" "Last-Translator: Meir Kriheli \n" "Language-Team: Hebrew \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit" +"Content-Transfer-Encoding: 8bit\n" #: conf/global_settings.py:39 msgid "Arabic" @@ -205,10 +205,8 @@ msgstr "" "

    ע\"י %s:

    \n" "
      \n" -#: contrib/admin/filterspecs.py:72 -#: contrib/admin/filterspecs.py:90 -#: contrib/admin/filterspecs.py:145 -#: contrib/admin/filterspecs.py:171 +#: contrib/admin/filterspecs.py:72 contrib/admin/filterspecs.py:90 +#: contrib/admin/filterspecs.py:145 contrib/admin/filterspecs.py:171 msgid "All" msgstr "הכל" @@ -232,20 +230,17 @@ msgstr "החודש" msgid "This year" msgstr "השנה" -#: contrib/admin/filterspecs.py:145 -#: newforms/widgets.py:198 +#: contrib/admin/filterspecs.py:145 newforms/widgets.py:221 #: oldforms/__init__.py:591 msgid "Yes" msgstr "כן" -#: contrib/admin/filterspecs.py:145 -#: newforms/widgets.py:198 +#: contrib/admin/filterspecs.py:145 newforms/widgets.py:221 #: oldforms/__init__.py:591 msgid "No" msgstr "לא" -#: contrib/admin/filterspecs.py:152 -#: newforms/widgets.py:198 +#: contrib/admin/filterspecs.py:152 newforms/widgets.py:221 #: oldforms/__init__.py:591 msgid "Unknown" msgstr "לא ידוע" @@ -317,8 +312,11 @@ msgid "Server Error (500)" msgstr "שגיאת שרת (500)" #: contrib/admin/templates/admin/500.html:10 -msgid "There's been an error. It's been reported to the site administrators via e-mail and should be fixed shortly. Thanks for your patience." -msgstr "התרחשה שגיאה. היא דווחה למנהלי האתר בדוא\"ל ותתוקן בקרוב. תודה על סבלנותך." +msgid "" +"There's been an error. It's been reported to the site administrators via e-" +"mail and should be fixed shortly. Thanks for your patience." +msgstr "" +"התרחשה שגיאה. היא דווחה למנהלי האתר בדוא\"ל ותתוקן בקרוב. תודה על סבלנותך." #: contrib/admin/templates/admin/base.html:26 msgid "Welcome," @@ -365,7 +363,9 @@ msgstr "צפיה באתר" #: contrib/admin/templates/admin/change_form.html:31 #: contrib/admin/templates/admin/auth/user/change_password.html:23 msgid "Please correct the error below." -msgstr "נא לתקן את השגיאה המופיעה מתחת." +msgid_plural "Please correct the errors below." +msgstr[0] "נא לתקן את השגיאה המופיעה מתחת." +msgstr[1] "נא לתקן את השגיאות המופיעה מתחת." #: contrib/admin/templates/admin/change_form.html:49 msgid "Ordering" @@ -387,13 +387,22 @@ msgstr "מחיקה" #: contrib/admin/templates/admin/delete_confirmation.html:13 #, python-format -msgid "Deleting the %(object_name)s '%(escaped_object)s' would result in deleting related objects, but your account doesn't have permission to delete the following types of objects:" -msgstr "מחיקת %(object_name)s '%(escaped_object)s' מצריכה מחיקת אובייקטים מקושרים, אך לחשבון שלך אין הרשאות למחיקתסוגי האובייקטים הבאים:" +msgid "" +"Deleting the %(object_name)s '%(escaped_object)s' would result in deleting " +"related objects, but your account doesn't have permission to delete the " +"following types of objects:" +msgstr "" +"מחיקת %(object_name)s '%(escaped_object)s' מצריכה מחיקת אובייקטים מקושרים, " +"אך לחשבון שלך אין הרשאות למחיקתסוגי האובייקטים הבאים:" #: contrib/admin/templates/admin/delete_confirmation.html:20 #, python-format -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 "האם ברצונך למחוק את %(object_name)s \"%(escaped_object)s\"? כל הפריטים הקשורים הבאים יימחקו:" +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 "" +"האם ברצונך למחוק את %(object_name)s \"%(escaped_object)s\"? כל הפריטים " +"הקשורים הבאים יימחקו:" #: contrib/admin/templates/admin/delete_confirmation.html:25 msgid "Yes, I'm sure" @@ -439,8 +448,13 @@ msgid "None available" msgstr "לא נמצאו" #: contrib/admin/templates/admin/invalid_setup.html:8 -msgid "Something's wrong with your database installation. Make sure the appropriate database tables have been created, and make sure the database is readable by the appropriate user." -msgstr "משהו שגוי בהתקנת בסיס הנתונים שלך. נא לוודא שנוצרו טבלאות בסיס הנתונים המתאימות, ובסיס הנתונים ניתן לקריאה על ידי המשתמש המתאים." +msgid "" +"Something's wrong with your database installation. Make sure the appropriate " +"database tables have been created, and make sure the database is readable by " +"the appropriate user." +msgstr "" +"משהו שגוי בהתקנת בסיס הנתונים שלך. נא לוודא שנוצרו טבלאות בסיס הנתונים " +"המתאימות, ובסיס הנתונים ניתן לקריאה על ידי המשתמש המתאים." #: contrib/admin/templates/admin/login.html:17 #: contrib/comments/templates/comments/form.html:6 @@ -475,8 +489,11 @@ msgid "DATE_WITH_TIME_FULL" msgstr "l d.m.y H:i:s" #: contrib/admin/templates/admin/object_history.html:35 -msgid "This object doesn't have a change history. It probably wasn't added via this admin site." -msgstr "לאובייקט זה אין היסטוריית שינוי. כנראה לא השתמשו בממשק הניהול הזה להוספתו." +msgid "" +"This object doesn't have a change history. It probably wasn't added via this " +"admin site." +msgstr "" +"לאובייקט זה אין היסטוריית שינוי. כנראה לא השתמשו בממשק הניהול הזה להוספתו." #: contrib/admin/templates/admin/pagination.html:10 msgid "Show all" @@ -489,7 +506,9 @@ msgstr "בצע" #: contrib/admin/templates/admin/search_form.html:10 #, python-format msgid "1 result" -msgstr "תוצאה אחת" +msgid_plural "%(counter)s results" +msgstr[0] "תוצאה אחת" +msgstr[1] "%(counter)s תוצאות" #: contrib/admin/templates/admin/search_form.html:10 #, python-format @@ -513,8 +532,12 @@ msgid "Save" msgstr "שמירה" #: contrib/admin/templates/admin/auth/user/add_form.html:6 -msgid "First, enter a username and password. Then, you'll be able to edit more user options." -msgstr "ראשית יש להזין שם משתמש וסיסמה. לאחר מכן יהיה ביכולתך לערוך אפשרויותנוספות עבור המשתמש" +msgid "" +"First, enter a username and password. Then, you'll be able to edit more user " +"options." +msgstr "" +"ראשית יש להזין שם משתמש וסיסמה. לאחר מכן יהיה ביכולתך לערוך אפשרויותנוספות " +"עבור המשתמש" #: contrib/admin/templates/admin/auth/user/add_form.html:12 msgid "Username" @@ -571,7 +594,9 @@ msgid "Documentation for this page" msgstr "תיעוד לדף זה" #: contrib/admin/templates/admin_doc/bookmarklets.html:19 -msgid "Jumps you from any page to the documentation for the view that generates that page." +msgid "" +"Jumps you from any page to the documentation for the view that generates " +"that page." msgstr "מקפיץ אותך מכל עמוד לתיעוד התצוגה שייצרה אותו." #: contrib/admin/templates/admin_doc/bookmarklets.html:21 @@ -579,7 +604,9 @@ msgid "Show object ID" msgstr "הצג מזהה אובייקט" #: contrib/admin/templates/admin_doc/bookmarklets.html:22 -msgid "Shows the content-type and unique ID for pages that represent a single object." +msgid "" +"Shows the content-type and unique ID for pages that represent a single " +"object." msgstr "מציג את סוג התוכן והמזהה הייחודי לעמודים המייצגים אובייקט בודד." #: contrib/admin/templates/admin_doc/bookmarklets.html:24 @@ -623,8 +650,12 @@ msgid "Your password was changed." msgstr "סיסמתך שונתה." #: contrib/admin/templates/registration/password_change_form.html:11 -msgid "Please enter your old password, for security's sake, and then enter your new password twice so we can verify you typed it in correctly." -msgstr "נא להזין את סיסמתך הישנה, למען האבטחה, ולאחר מכן את סיסמתךהחדשה פעמיים כדי שנוכל לוודא שהקלדת אותה כראוי." +msgid "" +"Please enter your old password, for security's sake, and then enter your new " +"password twice so we can verify you typed it in correctly." +msgstr "" +"נא להזין את סיסמתך הישנה, למען האבטחה, ולאחר מכן את סיסמתךהחדשה פעמיים כדי " +"שנוכל לוודא שהקלדת אותה כראוי." #: contrib/admin/templates/registration/password_change_form.html:16 msgid "Old password:" @@ -655,8 +686,11 @@ msgid "Password reset successful" msgstr "הסיסמה אופסה בהצלחה" #: contrib/admin/templates/registration/password_reset_done.html:12 -msgid "We've e-mailed a new password to the e-mail address you submitted. You should be receiving it shortly." -msgstr "שלחנו את הסיסמה החדשה לכתובת הדוא\"ל שהזנת. היא אמורה להתקבל תוך זמן קצר." +msgid "" +"We've e-mailed a new password to the e-mail address you submitted. You " +"should be receiving it shortly." +msgstr "" +"שלחנו את הסיסמה החדשה לכתובת הדוא\"ל שהזנת. היא אמורה להתקבל תוך זמן קצר." #: contrib/admin/templates/registration/password_reset_email.html:2 msgid "You're receiving this e-mail because you requested a password reset" @@ -690,8 +724,12 @@ msgid "The %(site_name)s team" msgstr "צוות %(site_name)s" #: contrib/admin/templates/registration/password_reset_form.html:12 -msgid "Forgotten your password? Enter your e-mail address below, and we'll reset your password and e-mail the new one to you." -msgstr "שכחת את סיסמתך ? נא להזין את כתובת הדוא\"ל מתחת, אנו נאפסאת הסיסמה ונשלח את החדשה אליך." +msgid "" +"Forgotten your password? Enter your e-mail address below, and we'll reset " +"your password and e-mail the new one to you." +msgstr "" +"שכחת את סיסמתך ? נא להזין את כתובת הדוא\"ל מתחת, אנו נאפסאת הסיסמה ונשלח את " +"החדשה אליך." #: contrib/admin/templates/registration/password_reset_form.html:16 msgid "E-mail address:" @@ -721,14 +759,12 @@ msgstr "שינוי:" msgid "All dates" msgstr "כל התאריכים" -#: contrib/admin/views/auth.py:20 -#: contrib/admin/views/main.py:264 +#: contrib/admin/views/auth.py:20 contrib/admin/views/main.py:264 #, python-format msgid "The %(name)s \"%(obj)s\" was added successfully." msgstr "הוספת %(name)s \"%(obj)s\" בוצעה בהצלחה." -#: contrib/admin/views/auth.py:25 -#: contrib/admin/views/main.py:268 +#: contrib/admin/views/auth.py:25 contrib/admin/views/main.py:268 #: contrib/admin/views/main.py:354 msgid "You may edit it again below." msgstr "ניתן לערוך שוב מתחת" @@ -746,18 +782,27 @@ msgstr "הסיסמה שונתה בהצלחה." msgid "Change password: %s" msgstr "שינוי סיסמה: %s" -#: contrib/admin/views/decorators.py:10 -#: contrib/auth/forms.py:60 -msgid "Please enter a correct username and password. Note that both fields are case-sensitive." -msgstr "נא להזין שם משתמש וסיסמה נכונים. בשני השדות גודל האותיות האנגליות משנה." +#: contrib/admin/views/decorators.py:10 contrib/auth/forms.py:60 +msgid "" +"Please enter a correct username and password. Note that both fields are case-" +"sensitive." +msgstr "" +"נא להזין שם משתמש וסיסמה נכונים. בשני השדות גודל האותיות האנגליות משנה." #: contrib/admin/views/decorators.py:62 -msgid "Please log in again, because your session has expired. Don't worry: Your submission has been saved." -msgstr "נא להתחבר שוב, מאחר ופג תוקף ההתחברות הנוכחית. אל דאגה: המידע ששלחת נשמר." +msgid "" +"Please log in again, because your session has expired. Don't worry: Your " +"submission has been saved." +msgstr "" +"נא להתחבר שוב, מאחר ופג תוקף ההתחברות הנוכחית. אל דאגה: המידע ששלחת נשמר." #: contrib/admin/views/decorators.py:69 -msgid "Looks like your browser isn't configured to accept cookies. Please enable cookies, reload this page, and try again." -msgstr "נראה שהדפדפן שלך אינו מוגדר לקבל עוגיות. נא לאפשר עוגיות, לטעון מחדש את הדף ולנסות שוב." +msgid "" +"Looks like your browser isn't configured to accept cookies. Please enable " +"cookies, reload this page, and try again." +msgstr "" +"נראה שהדפדפן שלך אינו מוגדר לקבל עוגיות. נא לאפשר עוגיות, לטעון מחדש את הדף " +"ולנסות שוב." #: contrib/admin/views/decorators.py:83 msgid "Usernames cannot contain the '@' character." @@ -768,20 +813,17 @@ msgstr "שם משתמש אינו יכול להכיל את התו '@'." msgid "Your e-mail address is not your username. Try '%s' instead." msgstr "כתובת הדוא\"ל שלך אינה שם המשתמש שלך. נסה/י '%s' במקום." -#: contrib/admin/views/doc.py:47 -#: contrib/admin/views/doc.py:49 +#: contrib/admin/views/doc.py:47 contrib/admin/views/doc.py:49 #: contrib/admin/views/doc.py:51 msgid "tag:" msgstr "תג:" -#: contrib/admin/views/doc.py:78 -#: contrib/admin/views/doc.py:80 +#: contrib/admin/views/doc.py:78 contrib/admin/views/doc.py:80 #: contrib/admin/views/doc.py:82 msgid "filter:" msgstr "סינון:" -#: contrib/admin/views/doc.py:136 -#: contrib/admin/views/doc.py:138 +#: contrib/admin/views/doc.py:136 contrib/admin/views/doc.py:138 #: contrib/admin/views/doc.py:140 msgid "view:" msgstr "צפיה (view):" @@ -801,10 +843,8 @@ msgstr "מודל %(name)r לא נמצא ביישום %(label)r" msgid "the related `%(label)s.%(type)s` object" msgstr "האובייקט `%(label)s.%(type)s` הקשור" -#: contrib/admin/views/doc.py:184 -#: contrib/admin/views/doc.py:206 -#: contrib/admin/views/doc.py:220 -#: contrib/admin/views/doc.py:225 +#: contrib/admin/views/doc.py:184 contrib/admin/views/doc.py:206 +#: contrib/admin/views/doc.py:220 contrib/admin/views/doc.py:225 msgid "model:" msgstr "מודל:" @@ -828,12 +868,9 @@ msgstr "מספר %s" msgid "Fields on %s objects" msgstr "Fields on %s objects" -#: contrib/admin/views/doc.py:292 -#: contrib/admin/views/doc.py:303 -#: contrib/admin/views/doc.py:305 -#: contrib/admin/views/doc.py:311 -#: contrib/admin/views/doc.py:312 -#: contrib/admin/views/doc.py:314 +#: contrib/admin/views/doc.py:292 contrib/admin/views/doc.py:303 +#: contrib/admin/views/doc.py:305 contrib/admin/views/doc.py:311 +#: contrib/admin/views/doc.py:312 contrib/admin/views/doc.py:314 msgid "Integer" msgstr "מספר שלם" @@ -841,8 +878,7 @@ msgstr "מספר שלם" msgid "Boolean (Either True or False)" msgstr "בוליאני (אמת או שקר)" -#: contrib/admin/views/doc.py:294 -#: contrib/admin/views/doc.py:313 +#: contrib/admin/views/doc.py:294 contrib/admin/views/doc.py:313 #, python-format msgid "String (up to %(max_length)s)" msgstr "מחרוזת (עד %(max_length)s תווים)" @@ -867,8 +903,7 @@ msgstr "מספר עשרוני" msgid "E-mail address" msgstr "כתובת דוא\"ל" -#: contrib/admin/views/doc.py:300 -#: contrib/admin/views/doc.py:301 +#: contrib/admin/views/doc.py:300 contrib/admin/views/doc.py:301 #: contrib/admin/views/doc.py:304 msgid "File path" msgstr "נתיב קובץ" @@ -877,8 +912,7 @@ msgstr "נתיב קובץ" msgid "Floating point number" msgstr "מספר עשרוני" -#: contrib/admin/views/doc.py:306 -#: contrib/comments/models.py:85 +#: contrib/admin/views/doc.py:306 contrib/comments/models.py:85 msgid "IP address" msgstr "כתובת IP" @@ -902,8 +936,7 @@ msgstr "טקסט" msgid "Time" msgstr "זמן" -#: contrib/admin/views/doc.py:317 -#: contrib/flatpages/models.py:7 +#: contrib/admin/views/doc.py:317 contrib/flatpages/models.py:7 msgid "URL" msgstr "URL" @@ -924,8 +957,7 @@ msgstr "לא נראה ש-%s הוא אובייקט urlpattern" msgid "Site administration" msgstr "ניהול אתר" -#: contrib/admin/views/main.py:278 -#: contrib/admin/views/main.py:363 +#: contrib/admin/views/main.py:278 contrib/admin/views/main.py:363 #, python-format msgid "You may add another %s below." msgstr "ניתן להוסיף %s נוסף מתחת." @@ -940,10 +972,8 @@ msgstr "הוספת %s" msgid "Added %s." msgstr "%s התווסף." -#: contrib/admin/views/main.py:342 -#: contrib/admin/views/main.py:344 -#: contrib/admin/views/main.py:346 -#: core/validators.py:283 +#: contrib/admin/views/main.py:342 contrib/admin/views/main.py:344 +#: contrib/admin/views/main.py:346 core/validators.py:283 #: db/models/manipulators.py:309 msgid "and" msgstr "ו" @@ -969,7 +999,8 @@ msgstr "שינוי %(name)s \"%(obj)s\" בוצע בהצלחה." #: contrib/admin/views/main.py:360 #, python-format -msgid "The %(name)s \"%(obj)s\" was added successfully. You may edit it again below." +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:398 @@ -1015,8 +1046,7 @@ msgstr "בחירת %s לשינוי" msgid "Database error" msgstr "שגיאת בסיס נתונים" -#: contrib/auth/forms.py:17 -#: contrib/auth/forms.py:138 +#: contrib/auth/forms.py:17 contrib/auth/forms.py:138 msgid "The two password fields didn't match." msgstr "שני שדות הסיסמה אינם זהים." @@ -1025,7 +1055,9 @@ msgid "A user with that username already exists." msgstr "משתמש עם שם משתמש זה קיים כבר" #: contrib/auth/forms.py:53 -msgid "Your Web browser doesn't appear to have cookies enabled. Cookies are required for logging in." +msgid "" +"Your Web browser doesn't appear to have cookies enabled. Cookies are " +"required for logging in." msgstr "נראה שעוגיות לא מאופשרות בדפדפן שלך.הן נדרשות כדי להתחבר." #: contrib/auth/forms.py:62 @@ -1033,9 +1065,16 @@ msgid "This account is inactive." msgstr "חשבון זה אינו פעיל." #: contrib/auth/forms.py:84 -msgid "That e-mail address doesn't have an associated user account. Are you sure you've registered?" +msgid "" +"That e-mail address doesn't have an associated user account. Are you sure " +"you've registered?" msgstr "כתובת דואר אלקטרוני זו אינה משוייכת למשתמש. בטוח שנרשמת ?" +#: contrib/auth/forms.py:107 +#, python-format +msgid "Password reset on %s" +msgstr "איפוס סיסמה על %s" + #: contrib/auth/forms.py:117 msgid "The two 'new password' fields didn't match." msgstr "שני שדות 'הסיסמה החדשה' אינם זהים." @@ -1044,8 +1083,7 @@ msgstr "שני שדות 'הסיסמה החדשה' אינם זהים." msgid "Your old password was entered incorrectly. Please enter it again." msgstr "סיסמתך הישנה הוזנה בצורה השגויה. נא להזינה שוב." -#: contrib/auth/models.py:73 -#: contrib/auth/models.py:93 +#: contrib/auth/models.py:73 contrib/auth/models.py:93 msgid "name" msgstr "שם" @@ -1057,8 +1095,7 @@ msgstr "שם קוד" msgid "permission" msgstr "הרשאה" -#: contrib/auth/models.py:79 -#: contrib/auth/models.py:94 +#: contrib/auth/models.py:79 contrib/auth/models.py:94 msgid "permissions" msgstr "הרשאות" @@ -1066,8 +1103,7 @@ msgstr "הרשאות" msgid "group" msgstr "קבוצה" -#: contrib/auth/models.py:98 -#: contrib/auth/models.py:141 +#: contrib/auth/models.py:98 contrib/auth/models.py:141 msgid "groups" msgstr "קבוצות" @@ -1076,8 +1112,11 @@ msgid "username" msgstr "שם משתמש" #: contrib/auth/models.py:131 -msgid "Required. 30 characters or fewer. Alphanumeric characters only (letters, digits and underscores)." -msgstr "דורש 30 תווים או פחות. תווים אלפאנומריים בלבד (אותיות, ספרות וקווים תחתונים)." +msgid "" +"Required. 30 characters or fewer. Alphanumeric characters only (letters, " +"digits and underscores)." +msgstr "" +"דורש 30 תווים או פחות. תווים אלפאנומריים בלבד (אותיות, ספרות וקווים תחתונים)." #: contrib/auth/models.py:132 msgid "first name" @@ -1096,8 +1135,12 @@ msgid "password" msgstr "סיסמה" #: contrib/auth/models.py:135 -msgid "Use '[algo]$[salt]$[hexdigest]' or use the change password form." -msgstr "יש להזין '[algo]$[salt]$[hexdigest]' או להשתמש בטופס שינוי הסיסמה." +msgid "" +"Use '[algo]$[salt]$[hexdigest]' or use the change " +"password form." +msgstr "" +"יש להזין '[algo]$[salt]$[hexdigest]' או להשתמש בטופס " +"שינוי הסיסמה." #: contrib/auth/models.py:136 msgid "staff status" @@ -1112,15 +1155,21 @@ msgid "active" msgstr "פעיל" #: contrib/auth/models.py:137 -msgid "Designates whether this user can log into the Django admin. Unselect this instead of deleting accounts." -msgstr "מציין האם המשתמש יכול להתחבר לאתר הניהול. יש לבטל בחירה זובמקום למחוק חשבונות משתמשים." +msgid "" +"Designates whether this user can log into the Django admin. Unselect this " +"instead of deleting accounts." +msgstr "" +"מציין האם המשתמש יכול להתחבר לאתר הניהול. יש לבטל בחירה זובמקום למחוק " +"חשבונות משתמשים." #: contrib/auth/models.py:138 msgid "superuser status" msgstr "סטטוס משתמש על" #: contrib/auth/models.py:138 -msgid "Designates that this user has all permissions without explicitly assigning them." +msgid "" +"Designates that this user has all permissions without explicitly assigning " +"them." msgstr "מציין שלמשתמש זה יש את כל ההרשאות ללא הצורך המפורש בהענקתן." #: contrib/auth/models.py:139 @@ -1132,8 +1181,12 @@ msgid "date joined" msgstr "תאריך הצטרפות" #: contrib/auth/models.py:142 -msgid "In addition to the permissions manually assigned, this user will also get all permissions granted to each group he/she is in." -msgstr "בנוסף לכל ההרשאות שהוקצו ידנית, יוענקו למשתמש גם כל ההרשאות של כל קבוצה המשוייכת אליו." +msgid "" +"In addition to the permissions manually assigned, this user will also get " +"all permissions granted to each group he/she is in." +msgstr "" +"בנוסף לכל ההרשאות שהוקצו ידנית, יוענקו למשתמש גם כל ההרשאות של כל קבוצה " +"המשוייכת אליו." #: contrib/auth/models.py:143 msgid "user permissions" @@ -1171,8 +1224,7 @@ msgstr "הודעה" msgid "Logged out" msgstr "יצאת מהמערכת" -#: contrib/comments/models.py:67 -#: contrib/comments/models.py:169 +#: contrib/comments/models.py:67 contrib/comments/models.py:169 msgid "object ID" msgstr "מזהה אובייקט" @@ -1180,8 +1232,7 @@ msgstr "מזהה אובייקט" msgid "headline" msgstr "כותרת" -#: contrib/comments/models.py:69 -#: contrib/comments/models.py:90 +#: contrib/comments/models.py:69 contrib/comments/models.py:90 #: contrib/comments/models.py:170 msgid "comment" msgstr "תגובה" @@ -1222,13 +1273,11 @@ msgstr "דירוג #8" msgid "is valid rating" msgstr "האם דירוג חוקי" -#: contrib/comments/models.py:83 -#: contrib/comments/models.py:172 +#: contrib/comments/models.py:83 contrib/comments/models.py:172 msgid "date/time submitted" msgstr "תאריך/שעת הגשה" -#: contrib/comments/models.py:84 -#: contrib/comments/models.py:173 +#: contrib/comments/models.py:84 contrib/comments/models.py:173 msgid "is public" msgstr "ציבורי" @@ -1237,15 +1286,17 @@ msgid "is removed" msgstr "האם הוסר" #: contrib/comments/models.py:86 -msgid "Check this box if the comment is inappropriate. A \"This comment has been removed\" message will be displayed instead." -msgstr "יש לסמן תיבה זו עבור תגובה לא נאותה. הודעת \"תגובה זו נמחקה\" תוצג במקום." +msgid "" +"Check this box if the comment is inappropriate. A \"This comment has been " +"removed\" message will be displayed instead." +msgstr "" +"יש לסמן תיבה זו עבור תגובה לא נאותה. הודעת \"תגובה זו נמחקה\" תוצג במקום." #: contrib/comments/models.py:91 msgid "comments" msgstr "תגובות" -#: contrib/comments/models.py:134 -#: contrib/comments/models.py:213 +#: contrib/comments/models.py:134 contrib/comments/models.py:213 msgid "Content object" msgstr "אובייקט תוכן" @@ -1387,21 +1438,30 @@ msgid "Your name:" msgstr "שמך:" #: contrib/comments/views/comments.py:28 -msgid "This rating is required because you've entered at least one other rating." +msgid "" +"This rating is required because you've entered at least one other rating." msgstr "הדירוג נדרש מאחר והזנת לפחות דרוג אחד אחר." #: contrib/comments/views/comments.py:112 #, python-format msgid "" -"This comment was posted by a user who has posted fewer than %(count)s comment:\n" -"\n" -"%(text)sThis comment was posted by a user who has posted fewer than %(count)s comments:\n" +"This comment was posted by a user who has posted fewer than %(count)s " +"comment:\n" "\n" "%(text)s" -msgstr "" -"תגובה זו נשלחה ממשתמש ששלח פחות מ-%(count)s תגובה:\n" +msgid_plural "" +"This comment was posted by a user who has posted fewer than %(count)s " +"comments:\n" "\n" -"%(text)sתגובה זו נשלחה ממשתמש ששלח פחות מ-%(count)s תגובות:\n" +"%(text)s" +msgstr[0] "" +"ההודעה נשלחה ע\"י משתמש ששלח פחות מ-%(count)s " +"תגובה:\n" +"\n" +"%(text)s" +msgstr[1] "" +"ההודעה נשלחה ע\"י משתמש ששלח פחות מ-%(count)s " +"תגובות:\n" "\n" "%(text)s" @@ -1433,7 +1493,9 @@ msgstr "מישהו התעסק עם טופס התגובה (הפרת אבטחה)" #: contrib/comments/views/comments.py:208 #: contrib/comments/views/comments.py:295 -msgid "The comment form had an invalid 'target' parameter -- the object ID was invalid" +msgid "" +"The comment form had an invalid 'target' parameter -- the object ID was " +"invalid" msgstr "טופס התגובה הכיל פרמטר target לא חוקי -- מזהה האובייקט אינו חוקי" #: contrib/comments/views/comments.py:259 @@ -1466,8 +1528,10 @@ msgid "content types" msgstr "סוגי תוכן" #: contrib/flatpages/models.py:8 -msgid "Example: '/about/contact/'. Make sure to have leading and trailing slashes." -msgstr "לדוגמא: '/about/contact/'. יש לוודא הימצאות הקווים הנטויים בהתחלה ובסוף." +msgid "" +"Example: '/about/contact/'. Make sure to have leading and trailing slashes." +msgstr "" +"לדוגמא: '/about/contact/'. יש לוודא הימצאות הקווים הנטויים בהתחלה ובסוף." #: contrib/flatpages/models.py:9 msgid "title" @@ -1486,8 +1550,12 @@ msgid "template name" msgstr "שם תבנית" #: contrib/flatpages/models.py:13 -msgid "Example: 'flatpages/contact_page.html'. If this isn't provided, the system will use 'flatpages/default.html'." -msgstr "דוגמא: 'flatpages/contact_page.html'. אם זה לא צויין, המערכת תשתמש ב-'flatpages/default.html'." +msgid "" +"Example: 'flatpages/contact_page.html'. If this isn't provided, the system " +"will use 'flatpages/default.html'." +msgstr "" +"דוגמא: 'flatpages/contact_page.html'. אם זה לא צויין, המערכת תשתמש " +"ב-'flatpages/default.html'." #: contrib/flatpages/models.py:14 msgid "registration required" @@ -1524,17 +1592,23 @@ msgstr "שלישי" #: contrib/humanize/templatetags/humanize.py:50 #, python-format msgid "%(value).1f million" -msgstr "%(value).1f מיליון" +msgid_plural "%(value).1f million" +msgstr[0] "%(value).1f מיליון" +msgstr[1] "%(value).1f מיליונים" #: contrib/humanize/templatetags/humanize.py:53 #, python-format msgid "%(value).1f billion" -msgstr "%(value).1f ביליון" +msgid_plural "%(value).1f billion" +msgstr[0] "%(value).1f ביליון" +msgstr[1] "%(value).1f ביליונים" #: contrib/humanize/templatetags/humanize.py:56 #, python-format msgid "%(value).1f trillion" -msgstr "%(value).1f טריליון" +msgid_plural "%(value).1f trillion" +msgstr[0] "%(value).1f טריליון" +msgstr[1] "%(value).1f טריליונים" #: contrib/humanize/templatetags/humanize.py:71 msgid "one" @@ -1584,15 +1658,12 @@ msgstr "מחר" msgid "yesterday" msgstr "אתמול" -#: contrib/localflavor/ar/forms.py:30 -#: contrib/localflavor/ar/forms.py:38 +#: contrib/localflavor/ar/forms.py:30 contrib/localflavor/ar/forms.py:38 msgid "Enter a postal code in the format NNNN or ANNNNAAA." msgstr "יש להזין קוד דואר בתחביר NNNN או ANNNNAAA." -#: contrib/localflavor/ar/forms.py:61 -#: contrib/localflavor/br/forms.py:103 -#: contrib/localflavor/pe/forms.py:34 -#: contrib/localflavor/pe/forms.py:57 +#: contrib/localflavor/ar/forms.py:61 contrib/localflavor/br/forms.py:103 +#: contrib/localflavor/pe/forms.py:34 contrib/localflavor/pe/forms.py:57 msgid "This field requires only numbers." msgstr "יש להזין רק ספרות בשדה זה." @@ -1621,7 +1692,9 @@ msgid "Phone numbers must be in XX-XXXX-XXXX format." msgstr "מספרי טלפון חייבים להיות בתחביר XX-XXXX-XXXX." #: contrib/localflavor/br/forms.py:68 -msgid "Select a valid brazilian state. That state is not one of the available states." +msgid "" +"Select a valid brazilian state. That state is not one of the available " +"states." msgstr "נא לבחור מדינה ברזילאית חוקית. מדינה זו אינה אחת מהמדינות האפשריות." #: contrib/localflavor/br/forms.py:105 @@ -1752,13 +1825,14 @@ msgstr "צוג" msgid "Zurich" msgstr "ציריך" -#: contrib/localflavor/ch/forms.py:18 -#: contrib/localflavor/no/forms.py:14 +#: contrib/localflavor/ch/forms.py:18 contrib/localflavor/no/forms.py:14 msgid "Enter a zip code in the format XXXX." msgstr "יש להזין מיקוד בתחביר XXXX." #: contrib/localflavor/ch/forms.py:90 -msgid "Enter a valid Swiss identity or passport card number in X1234567<0 or 1234567890 format." +msgid "" +"Enter a valid Swiss identity or passport card number in X1234567<0 or " +"1234567890 format." msgstr "יש להזין מספר זיהוי או דרכון שוויצרי בתחביר X1234567<0 או 1234567890." #: contrib/localflavor/cl/forms.py:32 @@ -1833,18 +1907,355 @@ msgstr "שלזוויג-הולשטיין" msgid "Thuringia" msgstr "תורינגיה" -#: contrib/localflavor/de/forms.py:16 -#: contrib/localflavor/fi/forms.py:14 +#: contrib/localflavor/de/forms.py:16 contrib/localflavor/fi/forms.py:14 #: contrib/localflavor/fr/forms.py:17 msgid "Enter a zip code in the format XXXXX." msgstr "יש להזין מיקוד בתחביר XXXXX." #: contrib/localflavor/de/forms.py:60 -msgid "Enter a valid German identity card number in XXXXXXXXXXX-XXXXXXX-XXXXXXX-X format." +msgid "" +"Enter a valid German identity card number in XXXXXXXXXXX-XXXXXXX-XXXXXXX-X " +"format." msgstr "יש להזין מספר זיהוי גרמני חוקי בתחביר XXXXXXXXXXX-XXXXXXX-XXXXXXX-X." -#: contrib/localflavor/fi/forms.py:40 -#: contrib/localflavor/fi/forms.py:45 +#: contrib/localflavor/es/es_provinces.py:5 +#, fuzzy +msgid "Arava" +msgstr "טרנאוה" + +#: contrib/localflavor/es/es_provinces.py:6 +msgid "Albacete" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:7 +msgid "Alacant" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:8 +#, fuzzy +msgid "Almeria" +msgstr "פומרניה" + +#: contrib/localflavor/es/es_provinces.py:9 +#, fuzzy +msgid "Avila" +msgstr "אקיטה" + +#: contrib/localflavor/es/es_provinces.py:10 +#, fuzzy +msgid "Badajoz" +msgstr "ברדיוב" + +#: contrib/localflavor/es/es_provinces.py:11 +msgid "Illes Balears" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:12 +#, fuzzy +msgid "Barcelona" +msgstr "מקדונית - Macedonian" + +#: contrib/localflavor/es/es_provinces.py:13 +msgid "Burgos" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:14 +msgid "Caceres" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:15 +#, fuzzy +msgid "Cadiz" +msgstr "צ'דקה" + +#: contrib/localflavor/es/es_provinces.py:16 +msgid "Castello" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:17 +msgid "Ciudad Real" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:18 +msgid "Cordoba" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:19 +msgid "A Coruna" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:20 +#, fuzzy +msgid "Cuenca" +msgstr "סניצ'ה" + +#: contrib/localflavor/es/es_provinces.py:21 +msgid "Girona" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:22 +#, fuzzy +msgid "Granada" +msgstr "קנדה - Kannada" + +#: contrib/localflavor/es/es_provinces.py:23 +msgid "Guadalajara" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:24 +msgid "Guipuzkoa" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:25 +msgid "Huelva" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:26 +#, fuzzy +msgid "Huesca" +msgstr "שלישי" + +#: contrib/localflavor/es/es_provinces.py:27 +#, fuzzy +msgid "Jaen" +msgstr "ינו'" + +#: contrib/localflavor/es/es_provinces.py:28 +#, fuzzy +msgid "Leon" +msgstr "כניסה" + +#: contrib/localflavor/es/es_provinces.py:29 +#, fuzzy +msgid "Lleida" +msgstr "סילסיה" + +#: contrib/localflavor/es/es_provinces.py:30 +#: contrib/localflavor/es/es_regions.py:17 +msgid "La Rioja" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:31 +msgid "Lugo" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:32 +#: contrib/localflavor/es/es_regions.py:18 +#, fuzzy +msgid "Madrid" +msgstr "מרטין" + +#: contrib/localflavor/es/es_provinces.py:33 +#, fuzzy +msgid "Malaga" +msgstr "גלנטה" + +#: contrib/localflavor/es/es_provinces.py:34 +#, fuzzy +msgid "Murcia" +msgstr "ז'ורה" + +#: contrib/localflavor/es/es_provinces.py:35 +msgid "Navarre" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:36 +#, fuzzy +msgid "Ourense" +msgstr "דרנתה" + +#: contrib/localflavor/es/es_provinces.py:37 +msgid "Asturias" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:38 +#, fuzzy +msgid "Palencia" +msgstr "גאליצית - Galician" + +#: contrib/localflavor/es/es_provinces.py:39 +msgid "Las Palmas" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:40 +msgid "Pontevedra" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:41 +#, fuzzy +msgid "Salamanca" +msgstr "סאיטאמה" + +#: contrib/localflavor/es/es_provinces.py:42 +msgid "Santa Cruz de Tenerife" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:43 +#: contrib/localflavor/es/es_regions.py:11 +#, fuzzy +msgid "Cantabria" +msgstr "קאטלונית - Catalan" + +#: contrib/localflavor/es/es_provinces.py:44 +#, fuzzy +msgid "Segovia" +msgstr "סלובנית - Slovenian" + +#: contrib/localflavor/es/es_provinces.py:45 +#, fuzzy +msgid "Seville" +msgstr "לביצה" + +#: contrib/localflavor/es/es_provinces.py:46 +#, fuzzy +msgid "Soria" +msgstr "סרבית - Serbian" + +#: contrib/localflavor/es/es_provinces.py:47 +msgid "Tarragona" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:48 +#, fuzzy +msgid "Teruel" +msgstr "שלישי" + +#: contrib/localflavor/es/es_provinces.py:49 +msgid "Toledo" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:50 +#, fuzzy +msgid "Valencia" +msgstr "גאליצית - Galician" + +#: contrib/localflavor/es/es_provinces.py:51 +#, fuzzy +msgid "Valladolid" +msgstr "ואלה" + +#: contrib/localflavor/es/es_provinces.py:52 +msgid "Bizkaia" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:53 +#, fuzzy +msgid "Zamora" +msgstr "נארה" + +#: contrib/localflavor/es/es_provinces.py:54 +msgid "Zaragoza" +msgstr "" + +#: contrib/localflavor/es/es_provinces.py:55 +#, fuzzy +msgid "Ceuta" +msgstr "דטבה" + +#: contrib/localflavor/es/es_provinces.py:56 +msgid "Melilla" +msgstr "" + +#: contrib/localflavor/es/es_regions.py:5 +msgid "Andalusia" +msgstr "" + +#: contrib/localflavor/es/es_regions.py:6 +msgid "Aragon" +msgstr "" + +#: contrib/localflavor/es/es_regions.py:7 +msgid "Principality of Asturias" +msgstr "" + +#: contrib/localflavor/es/es_regions.py:8 +msgid "Balearic Islands" +msgstr "" + +#: contrib/localflavor/es/es_regions.py:9 +msgid "Basque Country" +msgstr "" + +#: contrib/localflavor/es/es_regions.py:10 +#, fuzzy +msgid "Canary Islands" +msgstr "חבל הסאר" + +#: contrib/localflavor/es/es_regions.py:12 +msgid "Castile-La Mancha" +msgstr "" + +#: contrib/localflavor/es/es_regions.py:13 +msgid "Castile and Leon" +msgstr "" + +#: contrib/localflavor/es/es_regions.py:14 +#, fuzzy +msgid "Catalonia" +msgstr "קאטלונית - Catalan" + +#: contrib/localflavor/es/es_regions.py:15 +msgid "Extremadura" +msgstr "" + +#: contrib/localflavor/es/es_regions.py:16 +#, fuzzy +msgid "Galicia" +msgstr "גאליצית - Galician" + +#: contrib/localflavor/es/es_regions.py:19 +msgid "Region of Murcia" +msgstr "" + +#: contrib/localflavor/es/es_regions.py:20 +msgid "Foral Community of Navarre" +msgstr "" + +#: contrib/localflavor/es/es_regions.py:21 +msgid "Valencian Community" +msgstr "" + +#: contrib/localflavor/es/forms.py:22 +#, fuzzy +msgid "Enter a valid postal code in the range and format 01XXX - 52XXX." +msgstr "יש להזין מיקוד בתחביר XXX XXX." + +#: contrib/localflavor/es/forms.py:39 +#, fuzzy +msgid "" +"Enter a valid phone number in one of the formats 6XXXXXXXX, 8XXXXXXXX or " +"9XXXXXXXX." +msgstr "נא להזין שדה מספר מס (NIP) בתחביר XXX-XXX-XX-XX או XX-XX-XXX-XXX." + +#: contrib/localflavor/es/forms.py:73 contrib/localflavor/es/forms.py:108 +#: db/models/fields/related.py:55 +#, python-format +msgid "Please enter a valid %s." +msgstr "יש להזין %s חוקי." + +#: contrib/localflavor/es/forms.py:91 +msgid "Invalid checksum for NIF." +msgstr "" + +#: contrib/localflavor/es/forms.py:97 +msgid "Invalid checksum for NIE." +msgstr "" + +#: contrib/localflavor/es/forms.py:106 +msgid "Invalid checksum for CIF." +msgstr "" + +#: contrib/localflavor/es/forms.py:136 +#, fuzzy +msgid "" +"Please enter a valid bank account number in format XXXX-XXXX-XX-XXXXXXXXXX." +msgstr "יש להזין מספר זיהוי גרמני חוקי בתחביר XXXXXXXXXXX-XXXXXXX-XXXXXXX-X." + +#: contrib/localflavor/es/forms.py:150 +msgid "Invalid checksum for bank account number." +msgstr "" + +#: contrib/localflavor/fi/forms.py:40 contrib/localflavor/fi/forms.py:45 msgid "Enter a valid Finnish social security number." msgstr "יש להזין מספר ביטוח לאומי פיני חוקי." @@ -1853,7 +2264,8 @@ msgid "Enter a zip code in the format XXXXXXX." msgstr "יש להזין מיקוד בתחביר XXXXXXX." #: contrib/localflavor/is_/forms.py:17 -msgid "Enter a valid Icelandic identification number. The format is XXXXXX-XXXX." +msgid "" +"Enter a valid Icelandic identification number. The format is XXXXXX-XXXX." msgstr "יש להזין מספר זיהוי איסלנדי חוקי. התחביר הוא XXXXXX-XXXX." #: contrib/localflavor/is_/forms.py:31 @@ -2136,19 +2548,35 @@ msgstr "שדה זה דורש 8 ספרות." msgid "This field requires 11 digits." msgstr "שדה זה דורש 11 ספרות." -#: contrib/localflavor/pl/forms.py:53 +#: contrib/localflavor/pl/forms.py:41 msgid "National Identification Number consists of 11 digits." msgstr "מספר זיהוי לאומי מורכב מ-11 ספרות" -#: contrib/localflavor/pl/forms.py:59 +#: contrib/localflavor/pl/forms.py:47 msgid "Wrong checksum for the National Identification Number." msgstr "סכום ביקורת שגוי עבודי מספר הזיהוי הלאומי" -#: contrib/localflavor/pl/forms.py:71 -msgid "Enter a tax number field (NIP) in the format XXX-XXX-XX-XX or XX-XX-XXX-XXX." +#: contrib/localflavor/pl/forms.py:72 +msgid "" +"Enter a tax number field (NIP) in the format XXX-XXX-XX-XX or XX-XX-XXX-XXX." msgstr "נא להזין שדה מספר מס (NIP) בתחביר XXX-XXX-XX-XX או XX-XX-XXX-XXX." -#: contrib/localflavor/pl/forms.py:82 +#: contrib/localflavor/pl/forms.py:78 +#, fuzzy +msgid "Wrong checksum for the Tax Number (NIP)." +msgstr "סכום ביקורת שגוי עבודי מספר הזיהוי הלאומי" + +#: contrib/localflavor/pl/forms.py:107 +#, fuzzy +msgid "National Business Register Number (REGON) consists of 7 or 9 digits." +msgstr "מספר זיהוי לאומי מורכב מ-11 ספרות" + +#: contrib/localflavor/pl/forms.py:113 +#, fuzzy +msgid "Wrong checksum for the National Business Register Number (REGON)." +msgstr "סכום ביקורת שגוי עבודי מספר הזיהוי הלאומי" + +#: contrib/localflavor/pl/forms.py:148 msgid "Enter a postal code in the format XX-XXX." msgstr "נא להזין מיקוד בתחביר XX-XXX." @@ -2585,7 +3013,9 @@ msgid "redirect from" msgstr "הפניה מ" #: contrib/redirects/models.py:8 -msgid "This should be an absolute path, excluding the domain name. Example: '/events/search/'." +msgid "" +"This should be an absolute path, excluding the domain name. Example: '/" +"events/search/'." msgstr "זה אמור להיות נתיב מלא, ללא שם המתחם. לדוגמא: '‎/events/search'." #: contrib/redirects/models.py:9 @@ -2593,7 +3023,9 @@ msgid "redirect to" msgstr "הפניה אל" #: contrib/redirects/models.py:10 -msgid "This can be either an absolute path (as above) or a full URL starting with 'http://'." +msgid "" +"This can be either an absolute path (as above) or a full URL starting with " +"'http://'." msgstr "יכול להיות נתיב מלא (כנ\"ל) או URL מלא המתחיל ב'http://'." #: contrib/redirects/models.py:13 @@ -2645,7 +3077,9 @@ msgid "This value must contain only letters, numbers and underscores." msgstr "ערך זה חייב להכיל אותיות, ספרות וקווים תחתונים בלבד." #: core/validators.py:76 -msgid "This value must contain only letters, numbers, underscores, dashes or slashes." +msgid "" +"This value must contain only letters, numbers, underscores, dashes or " +"slashes." msgstr "ערך זה חייב להכיל אותיות, ספרות, מקפים, קווים תחתונים ונטויים בלבד." #: core/validators.py:80 @@ -2684,8 +3118,7 @@ msgstr "מותר להזין ספרות בלבד." msgid "This value can't be comprised solely of digits." msgstr "ערך זה אינו יכול להכיל ספרות בלבד." -#: core/validators.py:128 -#: newforms/fields.py:149 +#: core/validators.py:128 newforms/fields.py:157 msgid "Enter a whole number." msgstr "נא להזין מספר שלם." @@ -2702,8 +3135,7 @@ msgstr "שנה צריכה להיות 1900 ומעלה." msgid "Invalid date: %s" msgstr "תאריך שגוי: %s" -#: core/validators.py:156 -#: db/models/fields/__init__.py:505 +#: core/validators.py:156 db/models/fields/__init__.py:509 msgid "Enter a valid date in YYYY-MM-DD format." msgstr "יש להזין תאריך במבנה YYYY-MM-DD." @@ -2711,26 +3143,23 @@ msgstr "יש להזין תאריך במבנה YYYY-MM-DD." msgid "Enter a valid time in HH:MM format." msgstr "יש להזין שעה במבנה HH:MM." -#: core/validators.py:165 -#: db/models/fields/__init__.py:579 +#: core/validators.py:165 db/models/fields/__init__.py:583 msgid "Enter a valid date/time in YYYY-MM-DD HH:MM format." msgstr "יש להזין תאריך ושעה במבנה YYYY-MM-DD HH:MM." -#: core/validators.py:170 -#: newforms/fields.py:343 +#: core/validators.py:170 newforms/fields.py:408 msgid "Enter a valid e-mail address." msgstr "יש להזין כתובת דוא\"ל חוקית." -#: core/validators.py:182 -#: core/validators.py:474 -#: newforms/fields.py:377 +#: core/validators.py:182 core/validators.py:474 newforms/fields.py:438 #: oldforms/__init__.py:686 msgid "No file was submitted. Check the encoding type on the form." msgstr "לא נשלח שום קובץ. נא לבדוק את סוג הקידוד של הטופס." -#: core/validators.py:193 -#: newforms/fields.py:405 -msgid "Upload a valid image. The file you uploaded was either not an image or a corrupted image." +#: core/validators.py:193 newforms/fields.py:462 +msgid "" +"Upload a valid image. The file you uploaded was either not an image or a " +"corrupted image." msgstr "נא להעלות תמונה חוקית. הקובץ שהעלת אינו תמונה אומכיל תמונה מקולקלת." #: core/validators.py:200 @@ -2771,8 +3200,7 @@ msgstr "מבנה XML שגוי: %s" msgid "Invalid URL: %s" msgstr "URL שגוי: %s" -#: core/validators.py:259 -#: core/validators.py:261 +#: core/validators.py:259 core/validators.py:261 #, python-format msgid "The URL %s is a broken link." msgstr "ה-URL‏ %s הוא קישור שבור." @@ -2784,7 +3212,9 @@ msgstr "יש להזין קיצור חוקי למדינה בארה\"ב." #: core/validators.py:281 #, python-format msgid "Watch your mouth! The word %s is not allowed here." -msgstr "שמור על לשונך! המילה %s אסורה לשימוש כאן." +msgid_plural "Watch your mouth! The words %s are not allowed here." +msgstr[0] "שמור על לשונך! המילה %s אסורה לשימוש כאן." +msgstr[1] "שמור על לשונך! המילים %s אסורות לשימוש כאן." #: core/validators.py:288 #, python-format @@ -2795,8 +3225,7 @@ msgstr "תוכן השדה חייב להיות זהה לשדה '%s'." msgid "Please enter something for at least one field." msgstr "יש להזין תוכן בלפחות אחד מהשדות." -#: core/validators.py:316 -#: core/validators.py:327 +#: core/validators.py:316 core/validators.py:327 msgid "Please enter both fields or leave them both empty." msgstr "יש להזין תוכן בשני השדות או להשאיר את שניהם ריקים." @@ -2840,18 +3269,28 @@ msgstr "יש להזין מספר עשרוני חוקי." #: core/validators.py:444 #, python-format -msgid "Please enter a valid decimal number with at most %s total digit.Please enter a valid decimal number with at most %s total digits." -msgstr "נא להזין מספר עשרוני חוקי עפ %s ספרה לכל היותר.נא להזין מספר עשרוני חוקי עם %s ספרות לכל היותר." +msgid "Please enter a valid decimal number with at most %s total digit." +msgid_plural "" +"Please enter a valid decimal number with at most %s total digits." +msgstr[0] "יש להזין מספר עשרוני חוקי עם ספרה %s לכל היותר." +msgstr[1] "יש להזין מספר עשרוני חוקי עם %s ספרות לכל היותר." #: core/validators.py:447 #, python-format -msgid "Please enter a valid decimal number with a whole part of at most %s digit.Please enter a valid decimal number with a whole part of at most %s digits." -msgstr "נא להזין מספר עשרוני חוקי כאשר החלק השלם מכיל %s ספרה לכל היותר.נא להזין מספר עשרוני חוקי כאשר החלק השלם מכיל %s ספרות לכל היותר." +msgid "" +"Please enter a valid decimal number with a whole part of at most %s digit." +msgid_plural "" +"Please enter a valid decimal number with a whole part of at most %s digits." +msgstr[0] "יש להזין מספר עשרוני חוקי עם ספרה %s בחלק השלם לכל היותר." +msgstr[1] "יש להזין מספר עשרוני חוקי עם %s ספרות בחלק השלם לכל היותר." #: core/validators.py:450 #, python-format -msgid "Please enter a valid decimal number with at most %s decimal place.Please enter a valid decimal number with at most %s decimal places." -msgstr "נא להזין מספר עשרוני חוקי עם %s מקום עשרוני לכל היותר.נא להזין מספר עשרוני חוקי עם %s מקומקומות עשרוניים לא היותר." +msgid "Please enter a valid decimal number with at most %s decimal place." +msgid_plural "" +"Please enter a valid decimal number with at most %s decimal places." +msgstr[0] "יש להזין מספר עשרוני חוקי עם ספרה %s בחלק העשרוני לכל היותר." +msgstr[1] "יש להזין מספר עשרוני חוקי עם %s ספרות בחלק העשרוני לכל היותר." #: core/validators.py:458 msgid "Please enter a valid floating point number." @@ -2882,38 +3321,58 @@ msgstr "לא ניתן לאחזר כלום מ %s." #: core/validators.py:539 #, python-format -msgid "The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'." +msgid "" +"The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'." msgstr "ה-URL·%(url)s·החזיר כותרת·Content-Type·לא חוקית·'%(contenttype)s'." #: core/validators.py:572 #, python-format -msgid "Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with \"%(start)s\".)" +msgid "" +"Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with " +"\"%(start)s\".)" msgstr "נא לסגור את תג·%(tag)s·בשורה·%(line)s.·(השורה מתחילה ב·\"%(start)s\".)" #: core/validators.py:576 #, python-format -msgid "Some text starting on line %(line)s is not allowed in that context. (Line starts with \"%(start)s\".)" -msgstr "חלק מהטקסט בשורה·%(line)s·אסור בהקשר זה.·(השורה·מתחילה ב·\"%(start)s\".)" +msgid "" +"Some text starting on line %(line)s is not allowed in that context. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"חלק מהטקסט בשורה·%(line)s·אסור בהקשר זה.·(השורה·מתחילה ב·\"%(start)s\".)" #: core/validators.py:581 #, python-format -msgid "\"%(attr)s\" on line %(line)s is an invalid attribute. (Line starts with \"%(start)s\".)" -msgstr "\"%(attr)s\"·בשורה·%(line)s·אינה תכונה חוקית.·(השורה מתחילה ב·\"%(start)s\".)" +msgid "" +"\"%(attr)s\" on line %(line)s is an invalid attribute. (Line starts with \"%" +"(start)s\".)" +msgstr "" +"\"%(attr)s\"·בשורה·%(line)s·אינה תכונה חוקית.·(השורה מתחילה ב·\"%(start)s\".)" #: core/validators.py:586 #, python-format -msgid "\"<%(tag)s>\" on line %(line)s is an invalid tag. (Line starts with \"%(start)s\".)" -msgstr "\"<%(tag)s>\"·בשורה·%(line)s·אינו תג חוקי.·(השורה מתחילה ב·\"%(start)s\".)" +msgid "" +"\"<%(tag)s>\" on line %(line)s is an invalid tag. (Line starts with \"%" +"(start)s\".)" +msgstr "" +"\"<%(tag)s>\"·בשורה·%(line)s·אינו תג חוקי.·(השורה מתחילה ב·\"%(start)s\".)" #: core/validators.py:590 #, python-format -msgid "A tag on line %(line)s is missing one or more required attributes. (Line starts with \"%(start)s\".)" -msgstr "לתג בשורה %(line)s חסרה תכונה אחת או יותר נדרשות. (השורה מתחילה ב-\"%(start)s\".)" +msgid "" +"A tag on line %(line)s is missing one or more required attributes. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"לתג בשורה %(line)s חסרה תכונה אחת או יותר נדרשות. (השורה מתחילה ב-\"%(start)s" +"\".)" #: core/validators.py:595 #, python-format -msgid "The \"%(attr)s\" attribute on line %(line)s has an invalid value. (Line starts with \"%(start)s\".)" -msgstr "לתכונה·\"%(attr)s\"·בשורה·%(line)s·יש ערך לא חוקי.·(השורה·מתחילה ב·\"%(start)s\".)" +msgid "" +"The \"%(attr)s\" attribute on line %(line)s has an invalid value. (Line " +"starts with \"%(start)s\".)" +msgstr "" +"לתכונה·\"%(attr)s\"·בשורה·%(line)s·יש ערך לא חוקי.·(השורה·מתחילה ב·\"%(start)" +"s\".)" #: db/models/manipulators.py:308 #, python-format @@ -2925,174 +3384,159 @@ msgstr "%(object)s עם %(type)s קיים כבר עבור %(field)s נתון." msgid "%(optname)s with this %(fieldname)s already exists." msgstr "%(optname)s·עם·%(fieldname)s·זה קיימת כבר." -#: db/models/fields/__init__.py:159 -#: db/models/fields/__init__.py:316 -#: db/models/fields/__init__.py:731 -#: db/models/fields/__init__.py:742 -#: newforms/fields.py:93 -#: newforms/fields.py:513 -#: newforms/fields.py:589 -#: newforms/fields.py:600 -#: newforms/models.py:193 -#: oldforms/__init__.py:373 +#: db/models/fields/__init__.py:161 db/models/fields/__init__.py:318 +#: db/models/fields/__init__.py:735 db/models/fields/__init__.py:746 +#: newforms/fields.py:45 newforms/models.py:211 oldforms/__init__.py:373 msgid "This field is required." msgstr "יש להזין תוכן בשדה זה." -#: db/models/fields/__init__.py:414 +#: db/models/fields/__init__.py:418 msgid "This value must be an integer." msgstr "ערך זה חייב להיות מספר שלם." -#: db/models/fields/__init__.py:450 +#: db/models/fields/__init__.py:454 msgid "This value must be either True or False." msgstr "ערך זה חייב להיות אמת או שקר." -#: db/models/fields/__init__.py:471 +#: db/models/fields/__init__.py:475 msgid "This field cannot be null." msgstr "שדה זה אינו יכול להכיל null." -#: db/models/fields/__init__.py:640 +#: db/models/fields/__init__.py:644 msgid "This value must be a decimal number." msgstr "ערך זה חייב להיות מספר עשרוני." -#: db/models/fields/__init__.py:751 +#: db/models/fields/__init__.py:755 msgid "Enter a valid filename." msgstr "יש להזין שם קובץ חוקי." -#: db/models/fields/__init__.py:900 +#: db/models/fields/__init__.py:904 msgid "This value must be either None, True or False." msgstr "ערך זה חייב להיות כלום, אמת או שקר." -#: db/models/fields/related.py:55 -#, python-format -msgid "Please enter a valid %s." -msgstr "יש להזין %s חוקי." - #: db/models/fields/related.py:658 msgid "Separate multiple IDs with commas." msgstr "יש להפריד מזהים מרובים בפסיקים." #: db/models/fields/related.py:660 -msgid "Hold down \"Control\", or \"Command\" on a Mac, to select more than one." +msgid "" +"Hold down \"Control\", or \"Command\" on a Mac, to select more than one." msgstr "החזק את \"Control\", או \"Command\" על מק, לחוץ כדי לבחור יותר מאחד." #: db/models/fields/related.py:707 #, python-format -msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid.Please enter valid %(self)s IDs. The values %(value)r are invalid." -msgstr "נא להזין מזהה %(self)s חוקי. הערך %(value)r אינו חוקי. נא להזין מזהה %(self)s חוקי. הערכים %(value)r אינם חוקיים." +msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid." +msgid_plural "" +"Please enter valid %(self)s IDs. The values %(value)r are invalid." +msgstr[0] "נא להזין מזהה %(self)s חוקי. הערך %(value)r אינו חוקי." +msgstr[1] "" +"נא להזין מזהי %(self)s חוקיים. הערכים %(value)r אינם חוקיים." -#: newforms/fields.py:123 +#: newforms/fields.py:46 +msgid "Enter a valid value." +msgstr "יש להזין ערך חוקי." + +#: newforms/fields.py:129 #, python-format msgid "Ensure this value has at most %(max)d characters (it has %(length)d)." msgstr "נא לוודא שערך זה מכיל %(max)d תווים לכל היותר (מכיל %(length)d)." -#: newforms/fields.py:125 +#: newforms/fields.py:130 #, python-format msgid "Ensure this value has at least %(min)d characters (it has %(length)d)." msgstr "נא לוודא שערך זה מכיל לפחות %(min)d תווים (מכיל %(length)d)." -#: newforms/fields.py:151 -#: newforms/fields.py:174 -#: newforms/fields.py:204 +#: newforms/fields.py:158 newforms/fields.py:187 newforms/fields.py:216 #, python-format msgid "Ensure this value is less than or equal to %s." msgstr "יש לוודא שערך זה פחות מ או שווה ל %s." -#: newforms/fields.py:153 -#: newforms/fields.py:176 -#: newforms/fields.py:206 +#: newforms/fields.py:159 newforms/fields.py:188 newforms/fields.py:217 #, python-format msgid "Ensure this value is greater than or equal to %s." msgstr "יש לוודא שהערך גדול מ או שווה ל %s." -#: newforms/fields.py:172 -#: newforms/fields.py:199 +#: newforms/fields.py:186 newforms/fields.py:215 msgid "Enter a number." msgstr "נא להזין מספר." -#: newforms/fields.py:208 +#: newforms/fields.py:218 #, python-format msgid "Ensure that there are no more than %s digits in total." msgstr "נא לוודא שאין יותר מ-%s ספרות סה\"כ" -#: newforms/fields.py:210 +#: newforms/fields.py:219 #, python-format msgid "Ensure that there are no more than %s decimal places." msgstr "נא לוודא שאין יותר מ-%s ספרות אחרי הנקודה" -#: newforms/fields.py:212 +#: newforms/fields.py:220 #, python-format msgid "Ensure that there are no more than %s digits before the decimal point." msgstr "נא לוודא שאין יותר מ-%s ספרות לפני הנקודה העשרונית" -#: newforms/fields.py:245 -#: newforms/fields.py:633 +#: newforms/fields.py:268 newforms/fields.py:724 msgid "Enter a valid date." msgstr "יש להזין תאריך חוקי." -#: newforms/fields.py:272 -#: newforms/fields.py:635 +#: newforms/fields.py:301 newforms/fields.py:725 msgid "Enter a valid time." msgstr "יש להזין שעה חוקית." -#: newforms/fields.py:308 +#: newforms/fields.py:340 msgid "Enter a valid date/time." msgstr "יש להזין תאריך ושעה חוקיים." -#: newforms/fields.py:321 -msgid "Enter a valid value." -msgstr "יש להזין ערך חוקי." - -#: newforms/fields.py:379 +#: newforms/fields.py:439 msgid "No file was submitted." msgstr "לא נשלח שום קובץ" -#: newforms/fields.py:381 -#: oldforms/__init__.py:688 +#: newforms/fields.py:440 oldforms/__init__.py:688 msgid "The submitted file is empty." msgstr "הקובץ שנשלח ריק." -#: newforms/fields.py:419 -#: newforms/fields.py:444 +#: newforms/fields.py:498 msgid "Enter a valid URL." msgstr "יש להזין URL חוקי." -#: newforms/fields.py:446 +#: newforms/fields.py:499 msgid "This URL appears to be a broken link." msgstr "ה-URL הזה נראה כקישור שבור." -#: newforms/fields.py:501 -#: newforms/models.py:180 +#: newforms/fields.py:560 newforms/models.py:194 msgid "Select a valid choice. That choice is not one of the available choices." msgstr "יש לבחור אפשרות חוקית; '%(data)s' אינו בין %(choices)s." -#: newforms/fields.py:517 -#: newforms/fields.py:593 -#: newforms/models.py:197 +#: newforms/fields.py:599 +#, python-format +msgid "Select a valid choice. %(value)s is not one of the available choices." +msgstr "יש לבחור אפשרות חוקית. %(value)s אינו בין האפשרויות הזמינות." + +#: newforms/fields.py:600 newforms/fields.py:662 newforms/models.py:215 msgid "Enter a list of values." msgstr "יש להזין רשימת ערכים" -#: newforms/fields.py:523 -#: newforms/models.py:203 +#: newforms/fields.py:753 +msgid "Enter a valid IPv4 address." +msgstr "יש להזין כתובת IPv4 חוקית." + +#: newforms/models.py:221 #, python-format msgid "Select a valid choice. %s is not one of the available choices." msgstr "יש לבחור אפשרות חוקית. %s אינו בין האפשרויות הזמינות." -#: newforms/fields.py:644 -msgid "Enter a valid IPv4 address." -msgstr "יש להזין כתובת IPv4 חוקית." - #: oldforms/__init__.py:408 #, python-format msgid "Ensure your text is less than %s character." -msgstr "נא לוודא שהטקסט שלך מכיל פחות מ %s תו." +msgid_plural "Ensure your text is less than %s characters." +msgstr[0] "נא לוודא שהטקסט שלך מכיל פחות מ %s תו." +msgstr[1] "נא לוודא שהטקסט שלך מכיל פחות מ %s תווים." #: oldforms/__init__.py:413 msgid "Line breaks are not allowed here." msgstr "מעברי שורה אסורים כאן." -#: oldforms/__init__.py:511 -#: oldforms/__init__.py:585 -#: oldforms/__init__.py:624 +#: oldforms/__init__.py:511 oldforms/__init__.py:585 oldforms/__init__.py:624 #, python-format msgid "Select a valid choice; '%(data)s' is not in %(choices)s." msgstr "יש לבחור אפשרות חוקית; '%(data)s' אינו בין %(choices)s." @@ -3109,26 +3553,28 @@ msgstr "יש להזין מספר חיובי." msgid "Enter a whole number between 0 and 32,767." msgstr "יש להזין מספר שלם בין 0 ל- 32,767." -#: template/defaultfilters.py:541 +#: template/defaultfilters.py:555 msgid "yes,no,maybe" msgstr "כן,לא,אולי" -#: template/defaultfilters.py:570 +#: template/defaultfilters.py:585 #, python-format msgid "%(size)d byte" -msgstr "%(size)d בתים" +msgid_plural "%(size)d bytes" +msgstr[0] "%(size)d בית" +msgstr[1] "%(size)d בתים" -#: template/defaultfilters.py:572 +#: template/defaultfilters.py:587 #, python-format msgid "%.1f KB" msgstr "%.1f KB" -#: template/defaultfilters.py:574 +#: template/defaultfilters.py:589 #, python-format msgid "%.1f MB" msgstr "%.1f MB" -#: template/defaultfilters.py:575 +#: template/defaultfilters.py:590 #, python-format msgid "%.1f GB" msgstr "%.1f GB" @@ -3221,28 +3667,23 @@ msgstr "ינואר" msgid "February" msgstr "פברואר" -#: utils/dates.py:18 -#: utils/dates.py:31 +#: utils/dates.py:18 utils/dates.py:31 msgid "March" msgstr "מרס" -#: utils/dates.py:18 -#: utils/dates.py:31 +#: utils/dates.py:18 utils/dates.py:31 msgid "April" msgstr "אפריל" -#: utils/dates.py:18 -#: utils/dates.py:31 +#: utils/dates.py:18 utils/dates.py:31 msgid "May" msgstr "מאי" -#: utils/dates.py:18 -#: utils/dates.py:31 +#: utils/dates.py:18 utils/dates.py:31 msgid "June" msgstr "יוני" -#: utils/dates.py:19 -#: utils/dates.py:31 +#: utils/dates.py:19 utils/dates.py:31 msgid "July" msgstr "יולי" @@ -3348,27 +3789,39 @@ msgstr "או" #: utils/timesince.py:21 msgid "year" -msgstr "שנה" +msgid_plural "years" +msgstr[0] "שנה" +msgstr[1] "שנים" #: utils/timesince.py:22 msgid "month" -msgstr "חודש" +msgid_plural "months" +msgstr[0] "חודש" +msgstr[1] "חודשים" #: utils/timesince.py:23 msgid "week" -msgstr "שבוע" +msgid_plural "weeks" +msgstr[0] "שבוע" +msgstr[1] "שבועות" #: utils/timesince.py:24 msgid "day" -msgstr "יום" +msgid_plural "days" +msgstr[0] "יום" +msgstr[1] "ימים" #: utils/timesince.py:25 msgid "hour" -msgstr "שעה" +msgid_plural "hours" +msgstr[0] "שעה" +msgstr[1] "שעות" #: utils/timesince.py:26 msgid "minute" -msgstr "דקה" +msgid_plural "minutes" +msgstr[0] "דקה" +msgstr[1] "דקות" #: utils/timesince.py:46 msgid "minutes" @@ -3384,23 +3837,23 @@ msgstr "%(number)d %(type)s" msgid ", %(number)d %(type)s" msgstr ", %(number)d %(type)s" -#: utils/translation/trans_real.py:391 +#: utils/translation/trans_real.py:395 msgid "DATE_FORMAT" msgstr "d.m.Y" -#: utils/translation/trans_real.py:392 +#: utils/translation/trans_real.py:396 msgid "DATETIME_FORMAT" msgstr "d.m.y H:i:s" -#: utils/translation/trans_real.py:393 +#: utils/translation/trans_real.py:397 msgid "TIME_FORMAT" msgstr "H:i:s" -#: utils/translation/trans_real.py:409 +#: utils/translation/trans_real.py:413 msgid "YEAR_MONTH_FORMAT" msgstr "d.m.Y" -#: utils/translation/trans_real.py:410 +#: utils/translation/trans_real.py:414 msgid "MONTH_DAY_FORMAT" msgstr "d.m.Y" diff --git a/django/conf/locale/sv/LC_MESSAGES/django.mo b/django/conf/locale/sv/LC_MESSAGES/django.mo index 9d35676a65d99f4d7afefc4a01440100f248ede9..bb0deda35c4daf5999c04f83c3edfafc669341be 100644 GIT binary patch delta 5761 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 A3jhEB diff --git a/django/conf/locale/sv/LC_MESSAGES/django.po b/django/conf/locale/sv/LC_MESSAGES/django.po index d77cf04cc4..bf0d87e64e 100644 --- a/django/conf/locale/sv/LC_MESSAGES/django.po +++ b/django/conf/locale/sv/LC_MESSAGES/django.po @@ -7,8 +7,8 @@ msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2007-07-18 16:49+0200\n" -"PO-Revision-Date: 2007-03-06 10:30+0100\n" -"Last-Translator: Philip Lindborg \n" +"PO-Revision-Date: 2007-11-03 00:30+0100\n" +"Last-Translator: Dmitri Fedortchenko \n" "Language-Team: Django I18N \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -146,7 +146,7 @@ msgid_plural "" msgstr[0] "" "Var god och fyll giltiga %(self)s ID-nummer. Värdet %(value)r är ogiltigt." msgstr[1] "" -"Var god och fyll giltiga %(self)s ID-nummer. Värdena %(value)r är ogiltiga." +"Var god och fyll giltiga %(self)s ID-nummer. Värden %(value)r är ogiltiga." #: conf/global_settings.py:38 msgid "Arabic" @@ -318,20 +318,20 @@ msgstr "Traditionell Kinesiska" #: core/validators.py:71 msgid "This value must contain only letters, numbers and underscores." -msgstr "Det här värdet får bara innehålla bokstäver, siffror och understreck." +msgstr "Det här värdet får endast innehålla bokstäver, siffror och understreck." #: core/validators.py:75 msgid "" "This value must contain only letters, numbers, underscores, dashes or " "slashes." msgstr "" -"Det här värdet får bara innehålla bokstäver, siffror, understreck, " +"Det här värdet får endast innehålla bokstäver, siffror, understreck, " "bindestreck eller snedstreck." #: core/validators.py:79 msgid "This value must contain only letters, numbers, underscores or hyphens." msgstr "" -"Det här värdet får bara innehålla bokstäver, siffror, understreck eller " +"Det här värdet får endast innehålla bokstäver, siffror, understreck eller " "avstavningstecken." #: core/validators.py:83 @@ -352,7 +352,7 @@ msgstr "Fyll i giltiga e-postadresser avskilda med kommatecken." #: core/validators.py:110 msgid "Please enter a valid IP address." -msgstr "Var god fyll i en giltigt IP-adress." +msgstr "Var god fyll i en giltig IP-adress." #: core/validators.py:114 msgid "Empty values are not allowed here." @@ -414,7 +414,7 @@ msgstr "" #: core/validators.py:204 #, python-format msgid "The URL %s does not point to a valid QuickTime video." -msgstr "URL:en %s pekar inte på en giltig QuickTime-video." +msgstr "URL:en %s pekar inte mot en giltig QuickTime-video." #: core/validators.py:208 msgid "A valid URL is required." @@ -432,7 +432,7 @@ msgstr "" #: core/validators.py:229 #, python-format msgid "Badly formed XML: %s" -msgstr "Missformad XML: %s" +msgstr "Felaktigt XML: %s" #: core/validators.py:246 #, python-format @@ -480,7 +480,7 @@ msgstr "Det här fältet måste anges om %(field)s inte är %(value)s" #: core/validators.py:359 msgid "Duplicate values are not allowed." -msgstr "Dubbelvärden är inte tillåtna." +msgstr "Dubletter är inte tillåtna." #: core/validators.py:374 #, python-format @@ -544,12 +544,12 @@ msgstr "Fyll i ett giltigt decimaltal." #: core/validators.py:454 #, python-format msgid "Make sure your uploaded file is at least %s bytes big." -msgstr "Se till att filen du laddade upp är minst %s bytes stor." +msgstr "Se till att filen du laddade upp är minst %s byte stor." #: core/validators.py:455 #, python-format msgid "Make sure your uploaded file is at most %s bytes big." -msgstr "Se till att filen du laddade upp är som mest %s bytes stor." +msgstr "Se till att filen du laddade upp är som mest %s byte stor." #: core/validators.py:472 msgid "The format for this field is wrong." @@ -876,7 +876,7 @@ msgstr "är ett giltigt betyg" #: contrib/comments/models.py:83 contrib/comments/models.py:169 msgid "date/time submitted" -msgstr "datum/tid skickat" +msgstr "skickat datum/tid" #: contrib/comments/models.py:84 contrib/comments/models.py:170 msgid "is public" @@ -915,7 +915,7 @@ msgid "" "\n" "http://%(domain)s%(url)s" msgstr "" -"Postat av %(user)s %(date)s\n" +"Inlagt av %(user)s %(date)s\n" "\n" "%(comment)s\n" "\n" @@ -947,7 +947,7 @@ msgstr "poäng" #: contrib/comments/models.py:234 msgid "score date" -msgstr "poängdatum" +msgstr "poängen tillsatt den" #: contrib/comments/models.py:237 msgid "karma score" @@ -960,7 +960,7 @@ msgstr "karmapoäng" #: contrib/comments/models.py:242 #, python-format msgid "%(score)d rating by %(user)s" -msgstr "Betyget %(score)d av %(user)s" +msgstr "Poäng %(score)d av %(user)s" #: contrib/comments/models.py:258 #, python-format @@ -996,20 +996,20 @@ msgstr "borttagningsdatum" #: contrib/comments/models.py:280 msgid "moderator deletion" -msgstr "moderatorborttagning" +msgstr "borttaget av moderator" #: contrib/comments/models.py:281 msgid "moderator deletions" -msgstr "moderatorborttagningar" +msgstr "borttagna av moderator" #: contrib/comments/models.py:285 #, python-format msgid "Moderator deletion by %r" -msgstr "Moderatorborttagning av %r" +msgstr "Borttaget av moderator %r" #: contrib/comments/views/karma.py:20 msgid "Anonymous users cannot vote" -msgstr "Anonyma användare kan inte rösta" +msgstr "Anonyma användare får inte rösta" #: contrib/comments/views/karma.py:24 msgid "Invalid comment ID" @@ -1162,7 +1162,7 @@ msgstr "domännamn" #: contrib/sites/models.py:16 msgid "display name" -msgstr "visat namn" +msgstr "visningsnamn" #: contrib/sites/models.py:20 msgid "site" @@ -1243,8 +1243,8 @@ msgid "" "Please enter a correct username and password. Note that both fields are case-" "sensitive." msgstr "" -"Var god ange ett korrekt användarnamn och lösenord. Observera att båda " -"fälten är skiftlägeskänsliga." +"Var god ange ett korrekt användarnamn och lösenord. Tänk på att " +"skilja mellan gemener och versaler." #: contrib/admin/views/decorators.py:24 #: contrib/admin/templates/admin/login.html:25 @@ -1474,12 +1474,12 @@ msgstr "Decimaltal" #: contrib/admin/views/doc.py:299 msgid "E-mail address" -msgstr "E-postadress:" +msgstr "E-postadress" #: contrib/admin/views/doc.py:300 contrib/admin/views/doc.py:301 #: contrib/admin/views/doc.py:304 msgid "File path" -msgstr "Filsökväg" +msgstr "Sökväg till fil" #: contrib/admin/views/doc.py:302 msgid "Floating point number" @@ -1697,7 +1697,7 @@ msgstr "Sidan kunde inte hittas" #: contrib/admin/templates/admin/404.html:10 msgid "We're sorry, but the requested page could not be found." -msgstr "Vi är ledsna, men den efterfrågade sidan kunde inte hittas." +msgstr "Vi beklagar, men den efterfrågade sidan kunde tyvärr inte hittas." #: contrib/admin/templates/admin/index.html:17 #, python-format @@ -2026,11 +2026,11 @@ msgstr "Python-modellklassnamn" #: contrib/contenttypes/models.py:40 msgid "content type" -msgstr "innehållstyp" +msgstr "typ av innehåll" #: contrib/contenttypes/models.py:41 msgid "content types" -msgstr "innehållstyper" +msgstr "typer av innehåll" #: contrib/auth/views.py:41 msgid "Logged out" @@ -2178,7 +2178,7 @@ msgstr "meddelande" #: contrib/auth/forms.py:17 contrib/auth/forms.py:138 msgid "The two password fields didn't match." -msgstr "De båda lösenorden stämde inte överens." +msgstr "Lösenorden stämde inte överens." #: contrib/auth/forms.py:25 msgid "A user with that username already exists." @@ -2218,7 +2218,7 @@ msgstr "Fyll i ett postnummer. Du måste ha mellanslag mellan nummerdelarna." #: contrib/localflavor/br/forms.py:18 msgid "Enter a zip code in the format XXXXX-XXX." -msgstr "Fyll i zipkod på formatet XXXXX-XXX." +msgstr "Fyll i ett postnummer på formatet XXXXX-XXX." #: contrib/localflavor/br/forms.py:30 msgid "Phone numbers must be in XX-XXXX-XXXX format." @@ -2246,16 +2246,16 @@ msgstr "Ogiltigt CNPJ-nummer." #: contrib/localflavor/au/forms.py:18 msgid "Enter a 4 digit post code." -msgstr "Fyll i en fyrsiffrig postkod." +msgstr "Fyll i ett fyrsiffrigt postnummer." #: contrib/localflavor/fr/forms.py:17 contrib/localflavor/de/forms.py:16 #: contrib/localflavor/fi/forms.py:14 msgid "Enter a zip code in the format XXXXX." -msgstr "Fyll i en zipkod på formatet XXXXX." +msgstr "Fyll i ett postnummer på formatet XXXXX." #: contrib/localflavor/us/forms.py:18 msgid "Enter a zip code in the format XXXXX or XXXXX-XXXX." -msgstr "Fyll i zipkod på formatet XXXXX eller XXXXX-XXXX." +msgstr "Fyll i ett postnummer på formatet XXXXX eller XXXXX-XXXX." #: contrib/localflavor/us/forms.py:51 msgid "Enter a valid U.S. Social Security number in XXX-XX-XXXX format." @@ -2335,7 +2335,7 @@ msgstr "" #: contrib/localflavor/jp/forms.py:21 msgid "Enter a postal code in the format XXXXXXX or XXX-XXXX." -msgstr "Fyll i postkod på formatet XXXXXXX eller XXX-XXXX." +msgstr "Fyll i ett postnummer på formatet XXXXXXX eller XXX-XXXX." #: contrib/localflavor/jp/jp_prefectures.py:4 msgid "Hokkaido" @@ -2631,7 +2631,7 @@ msgstr "Zürich" #: contrib/localflavor/ch/forms.py:18 contrib/localflavor/no/forms.py:14 msgid "Enter a zip code in the format XXXX." -msgstr "Fyll i zipkod på formatet XXXX." +msgstr "Fyll i postnummer på formatet XXXX." #: contrib/localflavor/ch/forms.py:90 msgid "" @@ -2652,7 +2652,7 @@ msgstr "Det isländska personnumret är inte giltigt." #: contrib/localflavor/it/forms.py:16 msgid "Enter a valid zip code." -msgstr "Fyll i en giltigt zipkod." +msgstr "Fyll i ett giltigt postnummer." #: contrib/localflavor/it/forms.py:41 msgid "Enter a valid Social Security number." @@ -2739,11 +2739,11 @@ msgstr "" #: contrib/flatpages/models.py:18 msgid "flat page" -msgstr "flat sida" +msgstr "statisk sida" #: contrib/flatpages/models.py:19 msgid "flat pages" -msgstr "flata sidor" +msgstr "statiska sidor" #: utils/dates.py:6 msgid "Monday" @@ -3024,7 +3024,7 @@ msgstr "ja,nej,kanske" msgid "%(size)d byte" msgid_plural "%(size)d bytes" msgstr[0] "%(size)d byte" -msgstr[1] "%(size)d bytes" +msgstr[1] "%(size)d byte" #: template/defaultfilters.py:516 #, python-format diff --git a/django/conf/locale/sv/LC_MESSAGES/djangojs.mo b/django/conf/locale/sv/LC_MESSAGES/djangojs.mo index 5daac634243e5c2d43dc052eecb68be05c59393b..f84e25d5fd9eb1b43a448c0fce822a92081d7240 100644 GIT binary patch delta 121 zcmbQh+r_)VhtZdffq~&4D+2=)kY)vP*nu<$kmd!_0zg_8NXr3f79ec{q)mXdBa~kT zq&a~6Y9I|Xf}v&eOvZE;mXwsl^vOJIZcK-lP7Y=>;8V!WFG|nPWAIErytIe`02Buj AXaE2J delta 127 zcmeC;oxr=nhtZdhfq~&4D+2=)kmhA$U|PWFIyI-rW46^!z*q&-BAf Gix>c|;}rh@ diff --git a/django/conf/locale/sv/LC_MESSAGES/djangojs.po b/django/conf/locale/sv/LC_MESSAGES/djangojs.po index 29fbe2eb15..8cead596b7 100644 --- a/django/conf/locale/sv/LC_MESSAGES/djangojs.po +++ b/django/conf/locale/sv/LC_MESSAGES/djangojs.po @@ -58,7 +58,7 @@ msgstr "" #: contrib/admin/media/js/dateparse.js:33 msgid "Sunday Monday Tuesday Wednesday Thursday Friday Saturday" -msgstr "Söndag Mondag Tisdag Onsdag Torsdag Fredag Lördag" +msgstr "Söndag Måndag Tisdag Onsdag Torsdag Fredag Lördag" #: contrib/admin/media/js/calendar.js:25 msgid "S M T W T F S" @@ -96,7 +96,7 @@ msgstr "06.00" #: contrib/admin/media/js/admin/DateTimeShortcuts.js:84 msgid "Noon" -msgstr "Mitt på dagen" +msgstr "Middag" #: contrib/admin/media/js/admin/DateTimeShortcuts.js:88 #: contrib/admin/media/js/admin/DateTimeShortcuts.js:183 @@ -118,5 +118,5 @@ msgstr "Igår" #: contrib/admin/media/js/admin/DateTimeShortcuts.js:179 msgid "Tomorrow" -msgstr "Imorgon" +msgstr "I morgon" diff --git a/django/conf/locale/zh_TW/LC_MESSAGES/django.mo b/django/conf/locale/zh_TW/LC_MESSAGES/django.mo index 891e4bf9678b61f2e9e4faee6afdbd131163bae4..9c31d511363e377831dd3728545570ee060ead9a 100644 GIT binary patch literal 41345 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 diff --git a/django/conf/locale/zh_TW/LC_MESSAGES/django.po b/django/conf/locale/zh_TW/LC_MESSAGES/django.po index 70ff1bd0f5..650ac5b912 100644 --- a/django/conf/locale/zh_TW/LC_MESSAGES/django.po +++ b/django/conf/locale/zh_TW/LC_MESSAGES/django.po @@ -1,21 +1,1186 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# FIRST AUTHOR , YEAR. +# zh_TW translation for Django. +# Copyright (C) 2005-2007 +# This file is distributed under the same license as the Django package. # -#, fuzzy msgid "" msgstr "" "Project-Id-Version: django v1.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2006-05-16 10:13+0200\n" -"PO-Revision-Date: 2005-12-28 23:30+0800\n" -"Last-Translator: yungyuc \n" +"PO-Revision-Date: 2007-04-05 01:32+1000\n" +"Last-Translator: terryh \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" +#: db/models/manipulators.py:307 +#, python-format +msgid "%(object)s with this %(type)s already exists for the given %(field)s." +msgstr "含有 %(type)s 的 %(object)s 對給予的 %(field)s 已經存在了。" + +#: db/models/manipulators.py:308 contrib/admin/views/main.py:335 +#: contrib/admin/views/main.py:337 contrib/admin/views/main.py:339 +msgid "and" +msgstr "和" + +#: db/models/fields/related.py:53 +#, python-format +msgid "Please enter a valid %s." +msgstr "請輸入正確的 %s。" + +#: db/models/fields/related.py:642 +msgid "Separate multiple IDs with commas." +msgstr "用逗點分隔IDs" + +#: db/models/fields/related.py:644 +msgid "" +"Hold down \"Control\", or \"Command\" on a Mac, to select more than one." +msgstr "按住 \"Control\", 或者在 Mac 上按 \"Command\", 以選取更多值" + +#: db/models/fields/related.py:691 +#, python-format +msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid." +msgid_plural "" +"Please enter valid %(self)s IDs. The values %(value)r are invalid." +msgstr[0] "請輸入有效的 %(self)s ID。輸入值 %(value)r 是錯誤的。" +msgstr[1] "" + +#: db/models/fields/__init__.py:42 +#, python-format +msgid "%(optname)s with this %(fieldname)s already exists." +msgstr "%(optname)s 有 %(fieldname)s 已經存在。" + +#: db/models/fields/__init__.py:117 db/models/fields/__init__.py:274 +#: db/models/fields/__init__.py:610 db/models/fields/__init__.py:621 +#: oldforms/__init__.py:357 newforms/fields.py:80 newforms/fields.py:376 +#: newforms/fields.py:452 newforms/fields.py:463 newforms/models.py:178 +msgid "This field is required." +msgstr "這個欄位是必須的。" + +#: db/models/fields/__init__.py:367 +msgid "This value must be an integer." +msgstr "這個值必須是整數。" + +#: db/models/fields/__init__.py:402 +msgid "This value must be either True or False." +msgstr "這個值必須是 True 或 False。" + +#: db/models/fields/__init__.py:423 +msgid "This field cannot be null." +msgstr "這個值不能是 null。" + +#: db/models/fields/__init__.py:457 core/validators.py:148 +msgid "Enter a valid date in YYYY-MM-DD format." +msgstr "輸入一個 YYYY-MM-DD 格式的有效日期。" + +#: db/models/fields/__init__.py:526 core/validators.py:157 +msgid "Enter a valid date/time in YYYY-MM-DD HH:MM format." +msgstr "輸入一個 YYYY-MM-DD HH:MM 格式的有效日期/時間。" + +#: db/models/fields/__init__.py:630 +msgid "Enter a valid filename." +msgstr "輸入一個有效的檔名。" + +#: db/models/fields/__init__.py:751 +msgid "This value must be either None, True or False." +msgstr "這個值必須是 None, True 或 False。" + +#: conf/global_settings.py:39 +msgid "Arabic" +msgstr "阿拉伯語" + +#: conf/global_settings.py:40 +msgid "Bengali" +msgstr "孟加拉語" + +#: conf/global_settings.py:41 +msgid "Catalan" +msgstr "嘉泰羅尼亞語" + +#: conf/global_settings.py:42 +msgid "Czech" +msgstr "捷克語" + +#: conf/global_settings.py:43 +msgid "Welsh" +msgstr "威爾斯語" + +#: conf/global_settings.py:44 +msgid "Danish" +msgstr "丹麥語" + +#: conf/global_settings.py:45 +msgid "German" +msgstr "德語" + +#: conf/global_settings.py:46 +msgid "Greek" +msgstr "希臘語" + +#: conf/global_settings.py:47 +msgid "English" +msgstr "英語" + +#: conf/global_settings.py:48 +msgid "Spanish" +msgstr "西班牙語" + +#: conf/global_settings.py:49 +msgid "Argentinean Spanish" +msgstr "阿根廷的西班牙語" + +#: conf/global_settings.py:50 +msgid "Finnish" +msgstr "芬蘭語" + +#: conf/global_settings.py:51 +msgid "French" +msgstr "法語" + +#: conf/global_settings.py:52 +msgid "Galician" +msgstr "加里西亞語" + +#: conf/global_settings.py:53 +msgid "Hungarian" +msgstr "匈牙利語" + +#: conf/global_settings.py:54 +msgid "Hebrew" +msgstr "希伯來語" + +#: conf/global_settings.py:55 +msgid "Icelandic" +msgstr "冰島語" + +#: conf/global_settings.py:56 +msgid "Italian" +msgstr "義大利語" + +#: conf/global_settings.py:57 +msgid "Japanese" +msgstr "日語" + +#: conf/global_settings.py:58 +msgid "Kannada" +msgstr "坎那達語" + +#: conf/global_settings.py:59 +msgid "Latvian" +msgstr "拉脫維亞語" + +#: conf/global_settings.py:60 +msgid "Macedonian" +msgstr "馬其頓語" + +#: conf/global_settings.py:61 +msgid "Dutch" +msgstr "荷蘭語" + +#: conf/global_settings.py:62 +msgid "Norwegian" +msgstr "挪威語" + +#: conf/global_settings.py:63 +msgid "Polish" +msgstr "波蘭嶼" + +#: conf/global_settings.py:64 +msgid "Portugese" +msgstr "葡萄牙語" + +#: conf/global_settings.py:65 +msgid "Brazilian" +msgstr "巴西語" + +#: conf/global_settings.py:66 +msgid "Romanian" +msgstr "羅馬尼亞語" + +#: conf/global_settings.py:67 +msgid "Russian" +msgstr "俄語" + +#: conf/global_settings.py:68 +msgid "Slovak" +msgstr "斯洛伐克語" + +#: conf/global_settings.py:69 +msgid "Slovenian" +msgstr "斯洛維尼亞語" + +#: conf/global_settings.py:70 +msgid "Serbian" +msgstr "塞爾維亞語" + +#: conf/global_settings.py:71 +msgid "Swedish" +msgstr "瑞典語" + +#: conf/global_settings.py:72 +msgid "Tamil" +msgstr "坦米爾語" + +#: conf/global_settings.py:73 +msgid "Telugu" +msgstr "泰盧固語" + +#: conf/global_settings.py:74 +msgid "Turkish" +msgstr "土耳其語" + +#: conf/global_settings.py:75 +msgid "Ukrainian" +msgstr "烏克蘭語" + +#: conf/global_settings.py:76 +msgid "Simplified Chinese" +msgstr "簡體中文" + +#: conf/global_settings.py:77 +msgid "Traditional Chinese" +msgstr "繁體中文" + +#: utils/timesince.py:12 +msgid "year" +msgid_plural "years" +msgstr[0] "年" +msgstr[1] "" + +#: utils/timesince.py:13 +msgid "month" +msgid_plural "months" +msgstr[0] "月" +msgstr[1] "" + +#: utils/timesince.py:14 +msgid "week" +msgid_plural "weeks" +msgstr[0] "週" +msgstr[1] "" + +#: utils/timesince.py:15 +msgid "day" +msgid_plural "days" +msgstr[0] "天" +msgstr[1] "" + +#: utils/timesince.py:16 +msgid "hour" +msgid_plural "hours" +msgstr[0] "小時" +msgstr[1] "" + +#: utils/timesince.py:17 +msgid "minute" +msgid_plural "minutes" +msgstr[0] "分鐘" +msgstr[1] "" + +#: utils/dates.py:6 +msgid "Monday" +msgstr "星期一" + +#: utils/dates.py:6 +msgid "Tuesday" +msgstr "星期二" + +#: utils/dates.py:6 +msgid "Wednesday" +msgstr "星期三" + +#: utils/dates.py:6 +msgid "Thursday" +msgstr "星期四" + +#: utils/dates.py:6 +msgid "Friday" +msgstr "星期五" + +#: utils/dates.py:7 +msgid "Saturday" +msgstr "星期六" + +#: utils/dates.py:7 +msgid "Sunday" +msgstr "星期日" + +#: utils/dates.py:14 +msgid "January" +msgstr "一月" + +#: utils/dates.py:14 +msgid "February" +msgstr "二月" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "March" +msgstr "三月" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "April" +msgstr "四月" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "May" +msgstr "五月" + +#: utils/dates.py:14 utils/dates.py:27 +msgid "June" +msgstr "六月" + +#: utils/dates.py:15 utils/dates.py:27 +msgid "July" +msgstr "七月" + +#: utils/dates.py:15 +msgid "August" +msgstr "八月" + +#: utils/dates.py:15 +msgid "September" +msgstr "九月" + +#: utils/dates.py:15 +msgid "October" +msgstr "十月" + +#: utils/dates.py:15 +msgid "November" +msgstr "十一月" + +#: utils/dates.py:16 +msgid "December" +msgstr "十二月" + +#: utils/dates.py:19 +msgid "jan" +msgstr "一月" + +#: utils/dates.py:19 +msgid "feb" +msgstr "二月" + +#: utils/dates.py:19 +msgid "mar" +msgstr "三月" + +#: utils/dates.py:19 +msgid "apr" +msgstr "四月" + +#: utils/dates.py:19 +msgid "may" +msgstr "五月" + +#: utils/dates.py:19 +msgid "jun" +msgstr "六月" + +#: utils/dates.py:20 +msgid "jul" +msgstr "七月" + +#: utils/dates.py:20 +msgid "aug" +msgstr "八月" + +#: utils/dates.py:20 +msgid "sep" +msgstr "九月" + +#: utils/dates.py:20 +msgid "oct" +msgstr "十月" + +#: utils/dates.py:20 +msgid "nov" +msgstr "十一月" + +#: utils/dates.py:20 +msgid "dec" +msgstr "十二月" + +#: utils/dates.py:27 +msgid "Jan." +msgstr "一月" + +#: utils/dates.py:27 +msgid "Feb." +msgstr "二月" + +#: utils/dates.py:28 +msgid "Aug." +msgstr "八月" + +#: utils/dates.py:28 +msgid "Sept." +msgstr "九月" + +#: utils/dates.py:28 +msgid "Oct." +msgstr "十月" + +#: utils/dates.py:28 +msgid "Nov." +msgstr "十一月" + +#: utils/dates.py:28 +msgid "Dec." +msgstr "十二月" + +#: utils/dateformat.py:40 +msgid "p.m." +msgstr "" + +#: utils/dateformat.py:41 +msgid "a.m." +msgstr "" + +#: utils/dateformat.py:46 +msgid "PM" +msgstr "" + + +#: utils/dateformat.py:47 +msgid "AM" +msgstr "" + +#: utils/dateformat.py:95 +msgid "midnight" +msgstr "午夜" + +#: utils/dateformat.py:97 +msgid "noon" +msgstr "中午" + +#: utils/translation/trans_real.py:358 +msgid "DATE_FORMAT" +msgstr "N j, Y" + +#: utils/translation/trans_real.py:359 +msgid "DATETIME_FORMAT" +msgstr "N j, Y, P" + +#: utils/translation/trans_real.py:360 +msgid "TIME_FORMAT" +msgstr "P" + + +#: utils/translation/trans_real.py:376 +msgid "YEAR_MONTH_FORMAT" +msgstr "F Y" + +#: utils/translation/trans_real.py:377 +msgid "MONTH_DAY_FORMAT" + +msgstr "F j" + +#: oldforms/__init__.py:392 +#, python-format +msgid "Ensure your text is less than %s character." +msgid_plural "Ensure your text is less than %s characters." +msgstr[0] "確定你輸入的文字少於 %s 個字。" +msgstr[1] "" + +#: oldforms/__init__.py:397 +msgid "Line breaks are not allowed here." +msgstr "這裡不允許斷行" + +#: oldforms/__init__.py:498 oldforms/__init__.py:571 oldforms/__init__.py:610 +#, python-format +msgid "Select a valid choice; '%(data)s' is not in %(choices)s." +msgstr "選擇一個有效的選項: '%(data)s' 不在 %(choices)s 中。" + +#: oldforms/__init__.py:577 contrib/admin/filterspecs.py:150 +#: newforms/widgets.py:174 +msgid "Unknown" +msgstr "未知" + +#: oldforms/__init__.py:577 contrib/admin/filterspecs.py:143 +#: newforms/widgets.py:174 +msgid "Yes" +msgstr "是" + +#: oldforms/__init__.py:577 contrib/admin/filterspecs.py:143 +#: newforms/widgets.py:174 +msgid "No" +msgstr "否" + +#: oldforms/__init__.py:672 core/validators.py:174 core/validators.py:445 +msgid "No file was submitted. Check the encoding type on the form." +msgstr "没有檔案被提交。請檢查表單的編碼類型。" + +#: oldforms/__init__.py:674 +msgid "The submitted file is empty." +msgstr "提交的檔案是空的。" + +#: oldforms/__init__.py:730 +msgid "Enter a whole number between -32,768 and 32,767." +msgstr "輸入 -32,768 到 32,768 之間的一個整數。" + +#: oldforms/__init__.py:740 +msgid "Enter a positive number." +msgstr "輸入正整數。" + +#: oldforms/__init__.py:750 +msgid "Enter a whole number between 0 and 32,767." +msgstr "輸入 0 到 32,768 之間的一個整數。" + +#: contrib/localflavor/no/forms.py:15 +msgid "Enter a zip code in the format XXXX." +msgstr "用 XXXX 的格式輸入郵遞區號。" + +#: contrib/localflavor/no/forms.py:36 +msgid "Enter a valid Norwegian social security number." +msgstr "請輸入一個有效的挪威身份證號碼。" + +#: contrib/localflavor/it/forms.py:14 contrib/localflavor/fr/forms.py:17 +#: contrib/localflavor/fi/forms.py:14 contrib/localflavor/de/forms.py:16 +msgid "Enter a zip code in the format XXXXX." +msgstr "用 XXXXX 的格式輸入郵遞區號。" + +#: contrib/localflavor/jp/forms.py:21 +msgid "Enter a postal code in the format XXXXXXX or XXX-XXXX." +msgstr "用 XXXXXXX 或 XXX-XXXX 的格式輸入郵遞區號。" + +#: contrib/localflavor/jp/jp_prefectures.py:4 +msgid "Hokkaido" +msgstr "北海道" + +#: contrib/localflavor/jp/jp_prefectures.py:5 +msgid "Aomori" +msgstr "青森" + +#: contrib/localflavor/jp/jp_prefectures.py:6 +msgid "Iwate" +msgstr "岩手" + +#: contrib/localflavor/jp/jp_prefectures.py:7 +msgid "Miyagi" +msgstr "宮城縣" + +#: contrib/localflavor/jp/jp_prefectures.py:8 +msgid "Akita" +msgstr "秋田" + +#: contrib/localflavor/jp/jp_prefectures.py:9 +msgid "Yamagata" +msgstr "山形" + +#: contrib/localflavor/jp/jp_prefectures.py:10 +msgid "Fukushima" +msgstr "福島" + +#: contrib/localflavor/jp/jp_prefectures.py:11 +msgid "Ibaraki" +msgstr "茨城" + +#: contrib/localflavor/jp/jp_prefectures.py:12 +msgid "Tochigi" +msgstr "櫪木" + +#: contrib/localflavor/jp/jp_prefectures.py:13 +msgid "Gunma" +msgstr "群馬" + +#: contrib/localflavor/jp/jp_prefectures.py:14 +msgid "Saitama" +msgstr "埼玉" + +#: contrib/localflavor/jp/jp_prefectures.py:15 +msgid "Chiba" +msgstr "千葉" + +#: contrib/localflavor/jp/jp_prefectures.py:16 +msgid "Tokyo" +msgstr "東京" + +#: contrib/localflavor/jp/jp_prefectures.py:17 +msgid "Kanagawa" +msgstr "神奈川" + +#: contrib/localflavor/jp/jp_prefectures.py:18 +msgid "Yamanashi" +msgstr "山梨" + +#: contrib/localflavor/jp/jp_prefectures.py:19 +msgid "Nagano" +msgstr "長野" + +#: contrib/localflavor/jp/jp_prefectures.py:20 +msgid "Niigata" +msgstr "新瀉" + +#: contrib/localflavor/jp/jp_prefectures.py:21 +msgid "Toyama" +msgstr "富山" + +#: contrib/localflavor/jp/jp_prefectures.py:22 +msgid "Ishikawa" +msgstr "石川" + +#: contrib/localflavor/jp/jp_prefectures.py:23 +msgid "Fukui" +msgstr "福井" + +#: contrib/localflavor/jp/jp_prefectures.py:24 +msgid "Gifu" +msgstr "岐阜" + +#: contrib/localflavor/jp/jp_prefectures.py:25 +msgid "Shizuoka" +msgstr "靜岡" + +#: contrib/localflavor/jp/jp_prefectures.py:26 +msgid "Aichi" +msgstr "愛知" + +#: contrib/localflavor/jp/jp_prefectures.py:27 +msgid "Mie" +msgstr "三重" + +#: contrib/localflavor/jp/jp_prefectures.py:28 +msgid "Shiga" +msgstr "滋賀" + +#: contrib/localflavor/jp/jp_prefectures.py:29 +msgid "Kyoto" +msgstr "京都" + +#: contrib/localflavor/jp/jp_prefectures.py:30 +msgid "Osaka" +msgstr "大阪" + +#: contrib/localflavor/jp/jp_prefectures.py:31 +msgid "Hyogo" +msgstr "兵庫" + +#: contrib/localflavor/jp/jp_prefectures.py:32 +msgid "Nara" +msgstr "奈良" + +#: contrib/localflavor/jp/jp_prefectures.py:33 +msgid "Wakayama" +msgstr "和歌山" + +#: contrib/localflavor/jp/jp_prefectures.py:34 +msgid "Tottori" +msgstr "鳥取" + +#: contrib/localflavor/jp/jp_prefectures.py:35 +msgid "Shimane" +msgstr "島根" + +#: contrib/localflavor/jp/jp_prefectures.py:36 +msgid "Okayama" +msgstr "岡山" + +#: contrib/localflavor/jp/jp_prefectures.py:37 +msgid "Hiroshima" +msgstr "廣島" + +#: contrib/localflavor/jp/jp_prefectures.py:38 +msgid "Yamaguchi" +msgstr "山口" + +#: contrib/localflavor/jp/jp_prefectures.py:39 +msgid "Tokushima" +msgstr "德島" + +#: contrib/localflavor/jp/jp_prefectures.py:40 +msgid "Kagawa" +msgstr "香川" + +#: contrib/localflavor/jp/jp_prefectures.py:41 +msgid "Ehime" +msgstr "愛媛縣" + +#: contrib/localflavor/jp/jp_prefectures.py:42 +msgid "Kochi" +msgstr "高知" + +#: contrib/localflavor/jp/jp_prefectures.py:43 +msgid "Fukuoka" +msgstr "福岡" + +#: contrib/localflavor/jp/jp_prefectures.py:44 +msgid "Saga" +msgstr "左賀" + +#: contrib/localflavor/jp/jp_prefectures.py:45 +msgid "Nagasaki" +msgstr "長崎" + +#: contrib/localflavor/jp/jp_prefectures.py:46 +msgid "Kumamoto" +msgstr "熊本" + +#: contrib/localflavor/jp/jp_prefectures.py:47 +msgid "Oita" +msgstr "大分" + +#: contrib/localflavor/jp/jp_prefectures.py:48 +msgid "Miyazaki" +msgstr "宫崎" + +#: contrib/localflavor/jp/jp_prefectures.py:49 +msgid "Kagoshima" +msgstr "鹿兒島" + +#: contrib/localflavor/jp/jp_prefectures.py:50 +msgid "Okinawa" +msgstr "琉球" + +#: contrib/localflavor/br/forms.py:18 +msgid "Enter a zip code in the format XXXXX-XXX." +msgstr "用 XXXXX-XXX 的格式輸入郵遞區號。" + +#: contrib/localflavor/br/forms.py:30 +msgid "Phone numbers must be in XX-XXXX-XXXX format." +msgstr "電話號碼必須是 XX-XXXX-XXXX 格式。" + +#: contrib/localflavor/fi/forms.py:40 contrib/localflavor/fi/forms.py:46 +msgid "Enter a valid Finnish social security number." +msgstr "請輸入一個有效的芬蘭身份證號碼。" + +#: contrib/localflavor/uk/forms.py:18 +msgid "Enter a postcode. A space is required between the two postcode parts." +msgstr "輸入郵政區號。兩組的必須要有空格" + +#: contrib/localflavor/de/forms.py:63 +msgid "" +"Enter a valid German identity card number in XXXXXXXXXXX-XXXXXXX-XXXXXXX-X " +"format" +msgstr "" +"以 XXXXXXXXXXX-XXXXXXX-XXXXXXX-X 的格式輸入一個有效的德國身份證號碼。" + + +#: contrib/localflavor/de/de_states.py:5 +msgid "Baden-Wuerttemberg" +msgstr "巴登符騰堡" + +#: contrib/localflavor/de/de_states.py:6 +msgid "Bavaria" +msgstr "巴伐利亞" + +#: contrib/localflavor/de/de_states.py:7 +msgid "Berlin" +msgstr "柏林" + +#: contrib/localflavor/de/de_states.py:8 +msgid "Brandenburg" +msgstr "勃蘭登堡" + +#: contrib/localflavor/de/de_states.py:9 +msgid "Bremen" +msgstr "布萊梅" + +#: contrib/localflavor/de/de_states.py:10 +msgid "Hamburg" +msgstr "漢堡" + +#: contrib/localflavor/de/de_states.py:11 +msgid "Hessen" +msgstr "黑森" + +#: contrib/localflavor/de/de_states.py:12 +msgid "Mecklenburg-Western Pomerania" +msgstr "梅克倫堡-前波莫瑞" + +#: contrib/localflavor/de/de_states.py:13 +msgid "Lower Saxony" +msgstr "下薩克森" + +#: contrib/localflavor/de/de_states.py:14 +msgid "North Rhine-Westphalia" +msgstr "北萊茵-威斯特法倫" + +#: contrib/localflavor/de/de_states.py:15 +msgid "Rhineland-Palatinate" +msgstr "萊茵蘭-普法爾茨" + +#: contrib/localflavor/de/de_states.py:16 +msgid "Saarland" +msgstr "薩爾蘭" + +#: contrib/localflavor/de/de_states.py:17 +msgid "Saxony" +msgstr "薩克森" + +#: contrib/localflavor/de/de_states.py:18 +msgid "Saxony-Anhalt" +msgstr "薩克森-安哈爾特" + +#: contrib/localflavor/de/de_states.py:19 +msgid "Schleswig-Holstein" +msgstr "石勒蘇益格-荷爾斯泰因" + +#: contrib/localflavor/de/de_states.py:20 +msgid "Thuringia" +msgstr "圖林根" + +#: contrib/localflavor/usa/forms.py:18 +msgid "Enter a zip code in the format XXXXX or XXXXX-XXXX." +msgstr "以 XXXXX 或 XXXXX-XXX 的格式輸入一個郵遞區號。" + +#: contrib/localflavor/usa/forms.py:51 +msgid "Enter a valid U.S. Social Security number in XXX-XX-XXXX format." +msgstr "以 XXX-XX-XXXX 的格式輸入一有效的美国身份證字號。" + +#: contrib/sessions/models.py:68 +msgid "session key" +msgstr "session 鍵值" + +#: contrib/sessions/models.py:69 +msgid "session data" +msgstr "session 資料" + +#: contrib/sessions/models.py:70 +msgid "expire date" +msgstr "到期日期" + +#: contrib/sessions/models.py:74 +msgid "session" +msgstr "session" + +#: contrib/sessions/models.py:75 +msgid "sessions" +msgstr "sessions" + +#: contrib/auth/forms.py:17 contrib/auth/forms.py:138 +msgid "The two password fields didn't match." +msgstr "兩個密碼欄位不同。" + +#: contrib/auth/forms.py:25 +msgid "A user with that username already exists." +msgstr "一個相同名稱的使用者已經存在。" + +#: contrib/auth/forms.py:53 +msgid "" +"Your Web browser doesn't appear to have cookies enabled. Cookies are " +"required for logging in." +msgstr "你的Web瀏覽器好象不允許使用cookie。登入時需要使用cookie。" + +#: contrib/auth/forms.py:60 contrib/admin/views/decorators.py:10 +msgid "" +"Please enter a correct username and password. Note that both fields are case-" +"sensitive." +msgstr "請輸入有效的使用者名稱與密碼。請注意兩個欄位都有分大小寫" + +#: contrib/auth/forms.py:62 +msgid "This account is inactive." +msgstr "這個帳戶未啟用" + +#: contrib/auth/forms.py:85 +msgid "" +"That e-mail address doesn't have an associated user account. Are you sure " +"you've registered?" +msgstr "這個電子郵件地址没有和任何用户帳號有關聯。你確定你已經註冊了?" + +#: contrib/auth/forms.py:117 +msgid "The two 'new password' fields didn't match." +msgstr "兩個'新密碼'欄位不一致" + +#: contrib/auth/forms.py:124 +msgid "Your old password was entered incorrectly. Please enter it again." +msgstr "你的舊密碼不正確。請重新輸入。" + +#: contrib/auth/views.py:39 +msgid "Logged out" +msgstr "登出" + +#: contrib/auth/models.py:38 contrib/auth/models.py:57 +msgid "name" +msgstr "名稱" + +#: contrib/auth/models.py:40 +msgid "codename" +msgstr "代碼" + +#: contrib/auth/models.py:42 +msgid "permission" +msgstr "權限" + +#: contrib/auth/models.py:43 contrib/auth/models.py:58 +msgid "permissions" +msgstr "權限" + +#: contrib/auth/models.py:60 +msgid "group" +msgstr "群組" + +#: contrib/auth/models.py:61 contrib/auth/models.py:100 +msgid "groups" +msgstr "群組" + +#: contrib/auth/models.py:90 +msgid "username" +msgstr "使用者名稱" + +#: contrib/auth/models.py:90 +msgid "" +"Required. 30 characters or fewer. Alphanumeric characters only (letters, " +"digits and underscores)." +msgstr "必須。不超過30。只能用能字母數字與底線(字母、數字和底線)。" + +#: contrib/auth/models.py:91 +msgid "first name" +msgstr "名字" + +#: contrib/auth/models.py:92 +msgid "last name" +msgstr "姓氏" + +#: contrib/auth/models.py:93 +msgid "e-mail address" +msgstr "電子郵件地址" + +#: contrib/auth/models.py:94 +msgid "password" +msgstr "密碼" + +#: contrib/auth/models.py:94 +msgid "" +"Use '[algo]$[salt]$[hexdigest]' or use the change " +"password form." +msgstr "使用 '[algo]$[salt]$[hexdigest]' 或是 修改密碼表單." + +#: contrib/auth/models.py:95 +msgid "staff status" +msgstr "工作人員狀態" + +#: contrib/auth/models.py:95 +msgid "Designates whether the user can log into this admin site." +msgstr "指定是否使用者可以登入此管理網站。" + +#: contrib/auth/models.py:96 +msgid "active" +msgstr "活動中" + +#: contrib/auth/models.py:96 +msgid "" +"Designates whether this user can log into the Django admin. Unselect this " +"instead of deleting accounts." +msgstr "指定是否使用者可以登入此管理網站。用取消選擇來取代刪除帳號。" + +#: contrib/auth/models.py:97 +msgid "superuser status" +msgstr "超級使用者狀態" + +#: contrib/auth/models.py:97 +msgid "" +"Designates that this user has all permissions without explicitly assigning " +"them." +msgstr "指定是否使用者可以登入到這個管理網站" + +#: contrib/auth/models.py:98 +msgid "last login" +msgstr "上次登入" + +#: contrib/auth/models.py:99 +msgid "date joined" +msgstr "加入日期" + +#: contrib/auth/models.py:101 +msgid "" +"In addition to the permissions manually assigned, this user will also get " +"all permissions granted to each group he/she is in." +msgstr "除了手動指定的權限之外,這個使用者也會得到其群組擁有的所有權限。" + +#: contrib/auth/models.py:102 +msgid "user permissions" +msgstr "使用者權限" + +#: contrib/auth/models.py:105 +msgid "user" +msgstr "使用者" + +#: contrib/auth/models.py:106 +msgid "users" +msgstr "使用者" + +#: contrib/auth/models.py:111 +msgid "Personal info" +msgstr "個人資訊" + +#: contrib/auth/models.py:112 +msgid "Permissions" +msgstr "權限" + +#: contrib/auth/models.py:113 +msgid "Important dates" +msgstr "重要日期" + +#: contrib/auth/models.py:114 +msgid "Groups" +msgstr "群組" + +#: contrib/auth/models.py:258 +msgid "message" +msgstr "訊息" + +#: contrib/humanize/templatetags/humanize.py:17 +msgid "th" +msgstr "" + +#: contrib/humanize/templatetags/humanize.py:17 +msgid "st" +msgstr "" + +#: contrib/humanize/templatetags/humanize.py:17 +msgid "nd" +msgstr "" + +#: contrib/humanize/templatetags/humanize.py:17 +msgid "rd" +msgstr "" + +#: contrib/humanize/templatetags/humanize.py:47 +#, python-format +msgid "%(value).1f million" +msgid_plural "%(value).1f million" +msgstr[0] "%(value).1f 百萬" +msgstr[1] "" + +#: contrib/humanize/templatetags/humanize.py:50 +#, python-format +msgid "%(value).1f billion" +msgid_plural "%(value).1f billion" +msgstr[0] "%(value).1f 十億" +msgstr[1] "" + +#: contrib/humanize/templatetags/humanize.py:53 +#, python-format +msgid "%(value).1f trillion" +msgid_plural "%(value).1f trillion" +msgstr[0] "%(value).1f 兆" +msgstr[1] "" + +#: contrib/humanize/templatetags/humanize.py:68 +msgid "one" +msgstr "一" + +#: contrib/humanize/templatetags/humanize.py:68 +msgid "two" +msgstr "二" + +#: contrib/humanize/templatetags/humanize.py:68 +msgid "three" +msgstr "三" + +#: contrib/humanize/templatetags/humanize.py:68 +msgid "four" +msgstr "四" + +#: contrib/humanize/templatetags/humanize.py:68 +msgid "five" +msgstr "五" + +#: contrib/humanize/templatetags/humanize.py:68 +msgid "six" +msgstr "六" + +#: contrib/humanize/templatetags/humanize.py:68 +msgid "seven" +msgstr "七" + +#: contrib/humanize/templatetags/humanize.py:68 +msgid "eight" +msgstr "八" + +#: contrib/humanize/templatetags/humanize.py:68 +msgid "nine" +msgstr "九" + +#: contrib/contenttypes/models.py:36 +msgid "python model class name" +msgstr "python模組名稱" + +#: contrib/contenttypes/models.py:39 +msgid "content type" +msgstr "內容類型" + +#: contrib/contenttypes/models.py:40 +msgid "content types" +msgstr "內容類型" + +#: contrib/redirects/models.py:7 +msgid "redirect from" +msgstr "重導向自" + +#: contrib/redirects/models.py:8 +msgid "" +"This should be an absolute path, excluding the domain name. Example: '/" +"events/search/'." +msgstr "應該是一個絕對路徑,不包括網域。例如:'/events/search/'。" + +#: contrib/redirects/models.py:9 +msgid "redirect to" +msgstr "重導向到" + +#: contrib/redirects/models.py:10 +msgid "" +"This can be either an absolute path (as above) or a full URL starting with " +"'http://'." +msgstr "此可為一絕對路徑 (如上) 或一個以 'http://' 開頭的完整 URL。" + +#: contrib/redirects/models.py:13 +msgid "redirect" +msgstr "重導向" + +#: contrib/redirects/models.py:14 +msgid "redirects" +msgstr "重導向" + +#: contrib/flatpages/models.py:7 contrib/admin/views/doc.py:315 +msgid "URL" +msgstr "URL" + +#: contrib/flatpages/models.py:8 +msgid "" +"Example: '/about/contact/'. Make sure to have leading and trailing slashes." +msgstr "例如: '/about/contact/'。開頭與結束都一定要有斜線。" + +#: contrib/flatpages/models.py:9 +msgid "title" +msgstr "標題" + +#: contrib/flatpages/models.py:10 +msgid "content" +msgstr "內容" + +#: contrib/flatpages/models.py:11 +msgid "enable comments" +msgstr "啟用評論" + +#: contrib/flatpages/models.py:12 +msgid "template name" +msgstr "模板名稱" + +#: contrib/flatpages/models.py:13 +msgid "" +"Example: 'flatpages/contact_page.html'. If this isn't provided, the system " +"will use 'flatpages/default.html'." +msgstr "例如: 'flatpages/contact_page'。如果沒有提供它,系統會使用 'flatpages/default.html'。" + +#: contrib/flatpages/models.py:14 +msgid "registration required" +msgstr "需要註冊" + +#: contrib/flatpages/models.py:14 +msgid "If this is checked, only logged-in users will be able to view the page." +msgstr "如果此項被選取,則只有登入的使用者可以檢視頁面。" + +#: contrib/flatpages/models.py:18 +msgid "flat page" +msgstr "簡平頁面" + +#: contrib/flatpages/models.py:19 +msgid "flat pages" +msgstr "簡平頁面" + #: contrib/comments/models.py:67 contrib/comments/models.py:166 msgid "object ID" msgstr "物件 ID" @@ -88,7 +1253,6 @@ msgid "" msgstr "如果此評論不恰當則選取這個方塊,其將以 \"此評論已被移除\" 訊息取代。" #: contrib/comments/models.py:91 -#, fuzzy msgid "comments" msgstr "評論" @@ -124,12 +1288,10 @@ msgid "approved by staff" msgstr "由工作人員核准" #: contrib/comments/models.py:176 -#, fuzzy msgid "free comment" msgstr "自由評論" #: contrib/comments/models.py:177 -#, fuzzy msgid "free comments" msgstr "自由評論" @@ -142,19 +1304,17 @@ msgid "score date" msgstr "分數日期" #: contrib/comments/models.py:237 -#, fuzzy msgid "karma score" msgstr "Karma 分數" #: contrib/comments/models.py:238 -#, fuzzy msgid "karma scores" msgstr "Karma 分數" #: contrib/comments/models.py:242 #, python-format msgid "%(score)d rating by %(user)s" -msgstr "被 %(user)s 評定為 %(score)d 等級" +msgstr "被 %(user)s 定級為 %(score)d " #: contrib/comments/models.py:258 #, python-format @@ -172,12 +1332,10 @@ msgid "flag date" msgstr "標識日期" #: contrib/comments/models.py:268 -#, fuzzy msgid "user flag" msgstr "使用者旗標" #: contrib/comments/models.py:269 -#, fuzzy msgid "user flags" msgstr "使用者旗標" @@ -191,12 +1349,10 @@ msgid "deletion date" msgstr "刪除日期" #: contrib/comments/models.py:280 -#, fuzzy msgid "moderator deletion" msgstr "仲裁刪除" #: contrib/comments/models.py:281 -#, fuzzy msgid "moderator deletions" msgstr "仲裁刪除" @@ -205,6 +1361,75 @@ msgstr "仲裁刪除" msgid "Moderator deletion by %r" msgstr "由 %r 仲裁刪除" +#: contrib/comments/templates/comments/form.html:6 +#: contrib/comments/templates/comments/form.html:8 +#: contrib/admin/templates/admin/login.html:17 +msgid "Username:" +msgstr "" + +#: contrib/comments/templates/comments/form.html:6 +#: contrib/admin/templates/admin_doc/bookmarklets.html:4 +#: contrib/admin/templates/admin_doc/missing_docutils.html:4 +#: contrib/admin/templates/admin_doc/view_detail.html:4 +#: contrib/admin/templates/admin_doc/template_filter_index.html:5 +#: contrib/admin/templates/admin_doc/view_index.html:5 +#: contrib/admin/templates/admin_doc/template_tag_index.html:5 +#: contrib/admin/templates/admin_doc/model_detail.html:3 +#: contrib/admin/templates/admin_doc/model_index.html:5 +#: contrib/admin/templates/admin_doc/index.html:4 +#: contrib/admin/templates/admin_doc/template_detail.html:4 +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/admin/base.html:25 +#: contrib/admin/templates/admin/auth/user/change_password.html:9 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/registration/password_change_done.html:3 +msgid "Log out" +msgstr "登出" + +#: contrib/comments/templates/comments/form.html:8 +#: contrib/admin/templates/admin/login.html:20 +msgid "Password:" +msgstr "密碼" + +#: contrib/comments/templates/comments/form.html:8 +msgid "Forgotten your password?" +msgstr "忘記密碼" + +#: contrib/comments/templates/comments/form.html:12 +msgid "Ratings" +msgstr "等級" + +#: contrib/comments/templates/comments/form.html:12 +#: contrib/comments/templates/comments/form.html:23 +msgid "Required" +msgstr "必須的" + +#: contrib/comments/templates/comments/form.html:12 +#: contrib/comments/templates/comments/form.html:23 +msgid "Optional" +msgstr "可選的" + +#: contrib/comments/templates/comments/form.html:23 +msgid "Post a photo" +msgstr "上傳一張照片" + +#: contrib/comments/templates/comments/form.html:28 +#: contrib/comments/templates/comments/freeform.html:5 +msgid "Comment:" +msgstr "評論:" + +#: contrib/comments/templates/comments/form.html:35 +#: contrib/comments/templates/comments/freeform.html:10 +msgid "Preview comment" +msgstr "評論預覽" + +#: contrib/comments/templates/comments/freeform.html:4 +msgid "Your name:" +msgstr "你的名字:" + #: contrib/comments/views/karma.py:19 msgid "Anonymous users cannot vote" msgstr "匿名使用者不可投票" @@ -215,9 +1440,9 @@ msgstr "無效的評論 ID" #: contrib/comments/views/karma.py:25 msgid "No voting for yourself" -msgstr "不要投票給你自己" +msgstr "不能投票給你自己" -#: contrib/comments/views/comments.py:28 +#: contrib/comments/views/comments.py:27 msgid "" "This rating is required because you've entered at least one other rating." msgstr "此等級被要求是因為你至少輸入了一個其它的等級。" @@ -235,41 +1460,37 @@ msgid_plural "" "\n" "%(text)s" msgstr[0] "" -"張貼此評論的使用者之所有評論少於 %(count)s:\n" -"\n" -"%(text)s" -msgstr[1] "" -"張貼此評論的使用者之所有評論少於 %(count)s:\n" +"此評論由一個發表數少於 %(count)s 則評論的使用者張貼:\n" "\n" "%(text)s" -#: contrib/comments/views/comments.py:117 +#: contrib/comments/views/comments.py:116 #, python-format msgid "" "This comment was posted by a sketchy user:\n" "\n" "%(text)s" msgstr "" -"此評論被一個隨便的使用者所張貼:\n" +"此評論由一個膚淺的使用者張貼:\n" "\n" "%(text)s" -#: contrib/comments/views/comments.py:189 +#: contrib/comments/views/comments.py:188 #: contrib/comments/views/comments.py:280 msgid "Only POSTs are allowed" -msgstr "只允許 POST" +msgstr "只允許張貼" -#: contrib/comments/views/comments.py:193 +#: contrib/comments/views/comments.py:192 #: contrib/comments/views/comments.py:284 msgid "One or more of the required fields wasn't submitted" msgstr "一個或多個所需的欄位沒有送出" -#: contrib/comments/views/comments.py:197 +#: contrib/comments/views/comments.py:196 #: contrib/comments/views/comments.py:286 msgid "Somebody tampered with the comment form (security violation)" msgstr "有人篡改了評論表單 (違反保全)" -#: contrib/comments/views/comments.py:207 +#: contrib/comments/views/comments.py:206 #: contrib/comments/views/comments.py:292 msgid "" "The comment form had an invalid 'target' parameter -- the object ID was " @@ -279,80 +1500,23 @@ msgstr "此評論表單有一個無效的 'target' 參數 -- 物件 ID 是無效 #: contrib/comments/views/comments.py:257 #: contrib/comments/views/comments.py:321 msgid "The comment form didn't provide either 'preview' or 'post'" -msgstr "此評論表單沒有提供 'preview' 或 'post'" +msgstr "評論表格無法提供 '預覽' 或 '張貼' 功能" -#: contrib/comments/templates/comments/form.html:6 -#: contrib/comments/templates/comments/form.html:8 -#: contrib/admin/templates/admin/login.html:17 -msgid "Username:" -msgstr "使用者名稱:" +#: contrib/sites/models.py:10 +msgid "domain name" +msgstr "網域名稱" -#: contrib/comments/templates/comments/form.html:6 -#: contrib/admin/templates/admin/login.html:20 -msgid "Password:" -msgstr "密碼:" +#: contrib/sites/models.py:11 +msgid "display name" +msgstr "顯示名稱" -#: contrib/comments/templates/comments/form.html:6 -#, fuzzy -msgid "Forgotten your password?" -msgstr "變更我的密碼" +#: contrib/sites/models.py:15 +msgid "site" +msgstr "網站" -#: contrib/comments/templates/comments/form.html:8 -#: contrib/admin/templates/admin/object_history.html:3 -#: contrib/admin/templates/admin/change_list.html:5 -#: contrib/admin/templates/admin/base.html:23 -#: contrib/admin/templates/admin/delete_confirmation.html:3 -#: contrib/admin/templates/admin/change_form.html:10 -#: contrib/admin/templates/registration/password_change_done.html:3 -#: contrib/admin/templates/registration/password_change_form.html:3 -#: contrib/admin/templates/admin_doc/bookmarklets.html:4 -#: contrib/admin/templates/admin_doc/view_detail.html:4 -#: contrib/admin/templates/admin_doc/template_tag_index.html:5 -#: contrib/admin/templates/admin_doc/template_detail.html:4 -#: contrib/admin/templates/admin_doc/template_filter_index.html:5 -#: contrib/admin/templates/admin_doc/missing_docutils.html:4 -#: contrib/admin/templates/admin_doc/view_index.html:5 -#: contrib/admin/templates/admin_doc/model_detail.html:3 -#: contrib/admin/templates/admin_doc/index.html:4 -#: contrib/admin/templates/admin_doc/model_index.html:5 -msgid "Log out" -msgstr "登出" - -#: contrib/comments/templates/comments/form.html:12 -#, fuzzy -msgid "Ratings" -msgstr "等級 #1" - -#: contrib/comments/templates/comments/form.html:12 -#: contrib/comments/templates/comments/form.html:23 -msgid "Required" -msgstr "必需的" - -#: contrib/comments/templates/comments/form.html:12 -#: contrib/comments/templates/comments/form.html:23 -msgid "Optional" -msgstr "可選擇的" - -#: contrib/comments/templates/comments/form.html:23 -msgid "Post a photo" -msgstr "張貼照片" - -#: contrib/comments/templates/comments/form.html:27 -#: contrib/comments/templates/comments/freeform.html:5 -#, fuzzy -msgid "Comment:" -msgstr "評論" - -#: contrib/comments/templates/comments/form.html:32 -#: contrib/comments/templates/comments/freeform.html:9 -#, fuzzy -msgid "Preview comment" -msgstr "自由評論" - -#: contrib/comments/templates/comments/freeform.html:4 -#, fuzzy -msgid "Your name:" -msgstr "使用者名稱" +#: contrib/sites/models.py:16 +msgid "sites" +msgstr "網站" #: contrib/admin/filterspecs.py:40 #, python-format @@ -364,7 +1528,7 @@ msgstr "" "
        \n" #: contrib/admin/filterspecs.py:70 contrib/admin/filterspecs.py:88 -#: contrib/admin/filterspecs.py:143 +#: contrib/admin/filterspecs.py:143 contrib/admin/filterspecs.py:169 msgid "All" msgstr "全部" @@ -374,7 +1538,7 @@ msgstr "任何日期" #: contrib/admin/filterspecs.py:110 msgid "Today" -msgstr "本日" +msgstr "今天" #: contrib/admin/filterspecs.py:113 msgid "Past 7 days" @@ -386,19 +1550,7 @@ msgstr "本月" #: contrib/admin/filterspecs.py:117 msgid "This year" -msgstr "本年" - -#: contrib/admin/filterspecs.py:143 -msgid "Yes" -msgstr "是" - -#: contrib/admin/filterspecs.py:143 -msgid "No" -msgstr "否" - -#: contrib/admin/filterspecs.py:150 -msgid "Unknown" -msgstr "未知" +msgstr "今年" #: contrib/admin/models.py:16 msgid "action time" @@ -428,552 +1580,65 @@ msgstr "紀錄項目" msgid "log entries" msgstr "紀錄項目" -#: contrib/admin/templatetags/admin_list.py:228 +#: contrib/admin/templatetags/admin_list.py:247 msgid "All dates" msgstr "所有日期" -#: contrib/admin/views/decorators.py:9 contrib/auth/forms.py:36 -#: contrib/auth/forms.py:41 -msgid "" -"Please enter a correct username and password. Note that both fields are case-" -"sensitive." -msgstr "請輸入一個正確的使用者名稱與密碼。注意此二欄位都是大小寫有別的。" - -#: contrib/admin/views/decorators.py:23 -#: contrib/admin/templates/admin/login.html:25 -msgid "Log in" -msgstr "登入" - -#: contrib/admin/views/decorators.py:61 -msgid "" -"Please log in again, because your session has expired. Don't worry: Your " -"submission has been saved." -msgstr "請再登入一次,因為你的 session 已經到期。不必擔心: 你的提交已被儲存。" - -#: contrib/admin/views/decorators.py:68 -msgid "" -"Looks like your browser isn't configured to accept cookies. Please enable " -"cookies, reload this page, and try again." -msgstr "" -"看起來你的瀏覽器沒有組態成允許 cookie。請啟用 cookie、重新載入此頁,然後再試" -"一次。" - -#: contrib/admin/views/decorators.py:82 -msgid "Usernames cannot contain the '@' character." -msgstr "使用者名稱不能包含 '@' 字元。" - -#: contrib/admin/views/decorators.py:84 -#, python-format -msgid "Your e-mail address is not your username. Try '%s' instead." -msgstr "你的電子郵件地址不是你的使用者名稱。試著改用 '%s'。" - -#: contrib/admin/views/main.py:226 -msgid "Site administration" -msgstr "網站管理" - -#: contrib/admin/views/main.py:260 -#, python-format -msgid "The %(name)s \"%(obj)s\" was added successfully." -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 "你可以在下面再編輯一次。" - -#: contrib/admin/views/main.py:272 contrib/admin/views/main.py:357 -#, python-format -msgid "You may add another %s below." -msgstr "你可以在下面新增另一個 %s。" - -#: contrib/admin/views/main.py:290 -#, python-format -msgid "Add %s" -msgstr "新增 %s" - -#: contrib/admin/views/main.py:336 -#, python-format -msgid "Added %s." -msgstr "%s 已新增。" - -#: contrib/admin/views/main.py:336 contrib/admin/views/main.py:338 -#: contrib/admin/views/main.py:340 -msgid "and" -msgstr "和" - -#: contrib/admin/views/main.py:338 -#, python-format -msgid "Changed %s." -msgstr "%s 已變更。" - -#: contrib/admin/views/main.py:340 -#, python-format -msgid "Deleted %s." -msgstr "%s 已刪除。" - -#: contrib/admin/views/main.py:343 -msgid "No fields changed." -msgstr "沒有欄位被變更。" - -#: contrib/admin/views/main.py:346 -#, python-format -msgid "The %(name)s \"%(obj)s\" was changed successfully." -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 "變更 %s" - -#: contrib/admin/views/main.py:470 -#, python-format -msgid "One or more %(fieldname)s in %(name)s: %(obj)s" -msgstr "在 %(name) 裡的一個或多個 %(fieldname)s: %(obj)s" - -#: contrib/admin/views/main.py:475 -#, python-format -msgid "One or more %(fieldname)s in %(name)s:" -msgstr "在 %(name)s 裡的一個或多個 %(fieldname)s:" - -#: contrib/admin/views/main.py:508 -#, python-format -msgid "The %(name)s \"%(obj)s\" was deleted successfully." -msgstr "%(name)s \"%(obj)s\" 已成功刪除。" - -#: contrib/admin/views/main.py:511 -msgid "Are you sure?" -msgstr "你確定?" - -#: contrib/admin/views/main.py:533 -#, python-format -msgid "Change history: %s" -msgstr "變更歷史: %s" - -#: contrib/admin/views/main.py:565 -#, python-format -msgid "Select %s" -msgstr "選擇 %s" - -#: contrib/admin/views/main.py:565 -#, python-format -msgid "Select %s to change" -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 -#: contrib/admin/views/doc.py:295 contrib/admin/views/doc.py:297 -msgid "Integer" -msgstr "整數" - -#: contrib/admin/views/doc.py:278 -msgid "Boolean (Either True or False)" -msgstr "布林值 (True 或 False)" - -#: contrib/admin/views/doc.py:279 contrib/admin/views/doc.py:296 -#, python-format -msgid "String (up to %(maxlength)s)" -msgstr "字串 (最長到 %(maxlength)s)" - -#: contrib/admin/views/doc.py:280 -msgid "Comma-separated integers" -msgstr "逗號分隔的整數" - -#: contrib/admin/views/doc.py:281 -msgid "Date (without time)" -msgstr "日期 (不包括時間)" - -#: contrib/admin/views/doc.py:282 -msgid "Date (with time)" -msgstr "日期 (包括時間)" - -#: contrib/admin/views/doc.py:283 -msgid "E-mail address" -msgstr "電子郵件地址" - -#: contrib/admin/views/doc.py:284 contrib/admin/views/doc.py:287 -msgid "File path" -msgstr "檔案路徑" - -#: contrib/admin/views/doc.py:285 -msgid "Decimal number" -msgstr "小數" - -#: contrib/admin/views/doc.py:291 -msgid "Boolean (Either True, False or None)" -msgstr "布林值 (True, False 或 None)" - -#: contrib/admin/views/doc.py:292 -msgid "Relation to parent model" -msgstr "與父模型的關係" - -#: contrib/admin/views/doc.py:293 -msgid "Phone number" -msgstr "電話號碼" - -#: contrib/admin/views/doc.py:298 -msgid "Text" -msgstr "文字" - -#: contrib/admin/views/doc.py:299 -msgid "Time" -msgstr "時間" - -#: contrib/admin/views/doc.py:300 contrib/flatpages/models.py:7 -msgid "URL" -msgstr "URL" - -#: contrib/admin/views/doc.py:301 -msgid "U.S. state (two uppercase letters)" -msgstr "U.S. 州名 (兩個大寫字母)" - -#: contrib/admin/views/doc.py:302 -msgid "XML text" -msgstr "XML 文字" - -#: contrib/admin/templates/admin/object_history.html:3 -#: contrib/admin/templates/admin/change_list.html:5 -#: contrib/admin/templates/admin/base.html:23 -#: contrib/admin/templates/admin/delete_confirmation.html:3 -#: contrib/admin/templates/admin/change_form.html:10 -#: contrib/admin/templates/registration/password_change_done.html:3 -#: contrib/admin/templates/registration/password_change_form.html:3 #: contrib/admin/templates/admin_doc/bookmarklets.html:3 -msgid "Documentation" -msgstr "文件" - -#: contrib/admin/templates/admin/object_history.html:3 -#: contrib/admin/templates/admin/change_list.html:5 -#: contrib/admin/templates/admin/base.html:23 -#: contrib/admin/templates/admin/delete_confirmation.html:3 -#: contrib/admin/templates/admin/change_form.html:10 -#: contrib/admin/templates/registration/password_change_done.html:3 -#: contrib/admin/templates/registration/password_change_form.html:3 -#: contrib/admin/templates/admin_doc/bookmarklets.html:4 -#: contrib/admin/templates/admin_doc/view_detail.html:4 -#: contrib/admin/templates/admin_doc/template_tag_index.html:5 -#: contrib/admin/templates/admin_doc/template_detail.html:4 -#: contrib/admin/templates/admin_doc/template_filter_index.html:5 -#: contrib/admin/templates/admin_doc/missing_docutils.html:4 -#: contrib/admin/templates/admin_doc/view_index.html:5 -#: contrib/admin/templates/admin_doc/model_detail.html:3 -#: contrib/admin/templates/admin_doc/index.html:4 -#: contrib/admin/templates/admin_doc/model_index.html:5 -msgid "Change password" -msgstr "變更密碼" - -#: contrib/admin/templates/admin/object_history.html:5 #: contrib/admin/templates/admin/500.html:4 -#: contrib/admin/templates/admin/change_list.html:6 -#: contrib/admin/templates/admin/base.html:28 +#: contrib/admin/templates/admin/invalid_setup.html:4 +#: contrib/admin/templates/admin/object_history.html:5 #: contrib/admin/templates/admin/delete_confirmation.html:6 +#: contrib/admin/templates/admin/change_list.html:6 #: contrib/admin/templates/admin/change_form.html:13 -#: contrib/admin/templates/registration/password_change_done.html:4 -#: contrib/admin/templates/registration/password_reset_form.html:4 +#: contrib/admin/templates/admin/base.html:30 +#: contrib/admin/templates/admin/auth/user/change_password.html:12 #: contrib/admin/templates/registration/logged_out.html:4 #: contrib/admin/templates/registration/password_reset_done.html:4 #: contrib/admin/templates/registration/password_change_form.html:4 -#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +#: contrib/admin/templates/registration/password_change_done.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:4 msgid "Home" msgstr "首頁" -#: contrib/admin/templates/admin/object_history.html:5 -#: contrib/admin/templates/admin/change_form.html:20 -msgid "History" -msgstr "歷史" - -#: contrib/admin/templates/admin/object_history.html:18 -msgid "Date/time" -msgstr "日期/時間" - -#: contrib/admin/templates/admin/object_history.html:19 -msgid "User" -msgstr "使用者" - -#: contrib/admin/templates/admin/object_history.html:20 -msgid "Action" -msgstr "動作" - -#: contrib/admin/templates/admin/object_history.html:26 -msgid "DATE_WITH_TIME_FULL" -msgstr "DATE_WITH_TIME_FULL" - -#: contrib/admin/templates/admin/object_history.html:36 -msgid "" -"This object doesn't have a change history. It probably wasn't added via this " -"admin site." -msgstr "這個物件沒有變更的歷史。它可能不是透過這個管理網站新增的。" - -#: contrib/admin/templates/admin/base_site.html:4 -msgid "Django site admin" -msgstr "Django 網站管理" - -#: contrib/admin/templates/admin/base_site.html:7 -msgid "Django administration" -msgstr "Django 管理" - -#: contrib/admin/templates/admin/500.html:4 -msgid "Server error" -msgstr "伺服器錯誤" - -#: contrib/admin/templates/admin/500.html:6 -msgid "Server error (500)" -msgstr "伺服器錯誤 (500)" - -#: contrib/admin/templates/admin/500.html:9 -msgid "Server Error (500)" -msgstr "伺服器錯誤 (500)" - -#: contrib/admin/templates/admin/500.html:10 -msgid "" -"There's been an error. It's been reported to the site administrators via e-" -"mail and should be fixed shortly. Thanks for your patience." -msgstr "" -"有一個錯誤。它已經被透過電子郵件報告給了網站管理員,應該不久就會解決。感謝你" -"的忍耐。" - -#: contrib/admin/templates/admin/404.html:4 -#: contrib/admin/templates/admin/404.html:8 -msgid "Page not found" -msgstr "找不到頁面" - -#: contrib/admin/templates/admin/404.html:10 -msgid "We're sorry, but the requested page could not be found." -msgstr "我們很抱歉,不過被要求的頁面找不到。" - -#: contrib/admin/templates/admin/index.html:17 -#, python-format -msgid "Models available in the %(name)s application." -msgstr "" - -#: contrib/admin/templates/admin/index.html:28 -#: contrib/admin/templates/admin/change_form.html:15 -msgid "Add" -msgstr "新增" - -#: contrib/admin/templates/admin/index.html:34 -msgid "Change" -msgstr "變更" - -#: contrib/admin/templates/admin/index.html:44 -msgid "You don't have permission to edit anything." -msgstr "你沒有編輯任何東西的權限。" - -#: contrib/admin/templates/admin/index.html:52 -msgid "Recent Actions" -msgstr "最近的動作" - -#: contrib/admin/templates/admin/index.html:53 -msgid "My Actions" -msgstr "我的動作" - -#: contrib/admin/templates/admin/index.html:57 -msgid "None available" -msgstr "沒有可用的" - -#: contrib/admin/templates/admin/change_list.html:11 -#, python-format -msgid "Add %(name)s" -msgstr "新增 %(name)s" - -#: contrib/admin/templates/admin/login.html:22 -msgid "Have you forgotten your password?" -msgstr "你 忘記密碼了嗎?" - -#: contrib/admin/templates/admin/base.html:23 -msgid "Welcome," -msgstr "歡迎," - -#: contrib/admin/templates/admin/delete_confirmation.html:9 -#: contrib/admin/templates/admin/submit_line.html:3 -msgid "Delete" -msgstr "刪除" - -#: contrib/admin/templates/admin/delete_confirmation.html:14 -#, python-format -msgid "" -"Deleting the %(object_name)s '%(object)s' would result in deleting related " -"objects, but your account doesn't have permission to delete the following " -"types of objects:" -msgstr "" -"刪除 %(object_name)s '%(object)s' 會把相關的物件也刪除,不過你的帳號並沒有刪" -"除以下型態物件的權限:" - -#: contrib/admin/templates/admin/delete_confirmation.html:21 -#, python-format -msgid "" -"Are you sure you want to delete the %(object_name)s \"%(object)s\"? All of " -"the following related items will be deleted:" -msgstr "" -"你確定想要刪除 %(object_name)s \"%(object)s\"?以下所有的相關項目都會被刪除:" - -#: contrib/admin/templates/admin/delete_confirmation.html:26 -msgid "Yes, I'm sure" -msgstr "是的,我確定" - -#: contrib/admin/templates/admin/filter.html:2 -#, python-format -msgid " By %(title)s " -msgstr " 根據 %(title)s " - -#: contrib/admin/templates/admin/search_form.html:8 -msgid "Go" -msgstr "去" - -#: contrib/admin/templates/admin/change_form.html:21 -msgid "View on site" -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] "請更正以下的錯誤。" - -#: contrib/admin/templates/admin/change_form.html:48 -msgid "Ordering" -msgstr "排序中" - -#: contrib/admin/templates/admin/change_form.html:51 -msgid "Order:" -msgstr "順序:" - -#: contrib/admin/templates/admin/submit_line.html:4 -msgid "Save as new" -msgstr "儲存為新的" - -#: contrib/admin/templates/admin/submit_line.html:5 -msgid "Save and add another" -msgstr "儲存並新增另一個" - -#: contrib/admin/templates/admin/submit_line.html:6 -msgid "Save and continue editing" -msgstr "儲存並繼續編輯" - -#: contrib/admin/templates/admin/submit_line.html:7 -msgid "Save" -msgstr "儲存" - -#: contrib/admin/templates/registration/password_change_done.html:4 -#: contrib/admin/templates/registration/password_change_form.html:4 -#: contrib/admin/templates/registration/password_change_form.html:6 -#: contrib/admin/templates/registration/password_change_form.html:10 -msgid "Password change" -msgstr "密碼變更" - -#: contrib/admin/templates/registration/password_change_done.html:6 -#: contrib/admin/templates/registration/password_change_done.html:10 -msgid "Password change successful" -msgstr "密碼成功地變更" - -#: contrib/admin/templates/registration/password_change_done.html:12 -msgid "Your password was changed." -msgstr "你的密碼已變更。" - -#: contrib/admin/templates/registration/password_reset_form.html:4 -#: contrib/admin/templates/registration/password_reset_form.html:6 -#: contrib/admin/templates/registration/password_reset_form.html:10 -#: contrib/admin/templates/registration/password_reset_done.html:4 -msgid "Password reset" -msgstr "密碼重設" - -#: contrib/admin/templates/registration/password_reset_form.html:12 -msgid "" -"Forgotten your password? Enter your e-mail address below, and we'll reset " -"your password and e-mail the new one to you." -msgstr "" -"忘記你的密碼了?在下面輸入你的電子郵件地址,我們就會重設你的密碼並把新的用電" -"子郵件寄給你。" - -#: contrib/admin/templates/registration/password_reset_form.html:16 -msgid "E-mail address:" -msgstr "電子郵件地址:" - -#: contrib/admin/templates/registration/password_reset_form.html:16 -msgid "Reset my password" -msgstr "重設我的密碼" - -#: contrib/admin/templates/registration/logged_out.html:8 -msgid "Thanks for spending some quality time with the Web site today." -msgstr "感謝你今天花了重要的時間留在本網站。" - -#: contrib/admin/templates/registration/logged_out.html:10 -msgid "Log in again" -msgstr "再次登入" - -#: contrib/admin/templates/registration/password_reset_done.html:6 -#: contrib/admin/templates/registration/password_reset_done.html:10 -msgid "Password reset successful" -msgstr "密碼成功地重設" - -#: contrib/admin/templates/registration/password_reset_done.html:12 -msgid "" -"We've e-mailed a new password to the e-mail address you submitted. You " -"should be receiving it shortly." -msgstr "我們已經把新的密碼寄到你送出的電子郵件地址。你應該不久就能收到。" - -#: contrib/admin/templates/registration/password_change_form.html:12 -msgid "" -"Please enter your old password, for security's sake, and then enter your new " -"password twice so we can verify you typed it in correctly." -msgstr "" -"為了安全上的考慮,請輸入你的舊密碼,再輸入新密碼兩次,讓我們核驗你已正確地輸" -"入。" - -#: contrib/admin/templates/registration/password_change_form.html:17 -msgid "Old password:" -msgstr "舊密碼:" - -#: contrib/admin/templates/registration/password_change_form.html:19 -msgid "New password:" -msgstr "新密碼:" - -#: contrib/admin/templates/registration/password_change_form.html:21 -msgid "Confirm password:" -msgstr "確認密碼:" - -#: contrib/admin/templates/registration/password_change_form.html:23 -msgid "Change my password" -msgstr "變更我的密碼" - -#: contrib/admin/templates/registration/password_reset_email.html:2 -msgid "You're receiving this e-mail because you requested a password reset" -msgstr "因為你要求重設密碼,所以收到了這封電子郵件" - -#: contrib/admin/templates/registration/password_reset_email.html:3 -#, python-format -msgid "for your user account at %(site_name)s" -msgstr "你在 %(site_name)s 裡的使用者帳號" - -#: contrib/admin/templates/registration/password_reset_email.html:5 -#, python-format -msgid "Your new password is: %(new_password)s" -msgstr "你的新密碼是: %(new_password)s" - -#: contrib/admin/templates/registration/password_reset_email.html:7 -msgid "Feel free to change this password by going to this page:" -msgstr "放心地到此頁面變更密碼:" - -#: contrib/admin/templates/registration/password_reset_email.html:11 -msgid "Your username, in case you've forgotten:" -msgstr "你的使用者名稱,萬一你已經忘記的話:" - -#: contrib/admin/templates/registration/password_reset_email.html:13 -msgid "Thanks for using our site!" -msgstr "感謝使用本網站!" - -#: contrib/admin/templates/registration/password_reset_email.html:15 -#, python-format -msgid "The %(site_name)s team" -msgstr "%(site_name)s 團隊" +#: contrib/admin/templates/admin_doc/bookmarklets.html:3 +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/admin/base.html:25 +#: contrib/admin/templates/admin/auth/user/change_password.html:9 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/registration/password_change_done.html:3 +msgid "Documentation" +msgstr "文件" #: contrib/admin/templates/admin_doc/bookmarklets.html:3 msgid "Bookmarklets" -msgstr "Bookmarklets" +msgstr "書籤" + +#: contrib/admin/templates/admin_doc/bookmarklets.html:4 +#: contrib/admin/templates/admin_doc/missing_docutils.html:4 +#: contrib/admin/templates/admin_doc/view_detail.html:4 +#: contrib/admin/templates/admin_doc/template_filter_index.html:5 +#: contrib/admin/templates/admin_doc/view_index.html:5 +#: contrib/admin/templates/admin_doc/template_tag_index.html:5 +#: contrib/admin/templates/admin_doc/model_detail.html:3 +#: contrib/admin/templates/admin_doc/model_index.html:5 +#: contrib/admin/templates/admin_doc/index.html:4 +#: contrib/admin/templates/admin_doc/template_detail.html:4 +#: contrib/admin/templates/admin/object_history.html:3 +#: contrib/admin/templates/admin/delete_confirmation.html:3 +#: contrib/admin/templates/admin/change_list.html:5 +#: contrib/admin/templates/admin/change_form.html:10 +#: contrib/admin/templates/admin/base.html:25 +#: contrib/admin/templates/admin/auth/user/change_password.html:9 +#: contrib/admin/templates/admin/auth/user/change_password.html:15 +#: contrib/admin/templates/admin/auth/user/change_password.html:46 +#: contrib/admin/templates/registration/password_change_form.html:3 +#: contrib/admin/templates/registration/password_change_done.html:3 +msgid "Change password" +msgstr "變更密碼" #: contrib/admin/templates/admin_doc/bookmarklets.html:5 msgid "Documentation bookmarklets" @@ -1031,674 +1696,812 @@ msgstr "編輯此物件 (新視窗)" msgid "As above, but opens the admin page in a new window." msgstr "如上,但在新視窗裡開啟管理頁面。" -#: contrib/admin/templates/widget/date_time.html:3 -msgid "Date:" -msgstr "日期:" +#: contrib/admin/templates/admin/submit_line.html:3 +#: contrib/admin/templates/admin/delete_confirmation.html:9 +msgid "Delete" +msgstr "刪除" -#: contrib/admin/templates/widget/date_time.html:4 -msgid "Time:" -msgstr "時間:" +#: contrib/admin/templates/admin/submit_line.html:4 +msgid "Save as new" +msgstr "儲存為新的" + +#: contrib/admin/templates/admin/submit_line.html:5 +msgid "Save and add another" +msgstr "儲存並新增另一個" + +#: contrib/admin/templates/admin/submit_line.html:6 +msgid "Save and continue editing" +msgstr "儲存並繼續編輯" + +#: contrib/admin/templates/admin/submit_line.html:7 +msgid "Save" +msgstr "儲存" + +#: contrib/admin/templates/admin/500.html:4 +msgid "Server error" +msgstr "伺服器錯誤" + +#: contrib/admin/templates/admin/500.html:6 +msgid "Server error (500)" +msgstr "伺服器錯誤 (500)" + +#: contrib/admin/templates/admin/500.html:9 +msgid "Server Error (500)" +msgstr "伺服器錯誤 (500)" + +#: contrib/admin/templates/admin/500.html:10 +msgid "" +"There's been an error. It's been reported to the site administrators via e-" +"mail and should be fixed shortly. Thanks for your patience." +msgstr "存在一個錯誤。它已經透過電子郵件回報給網站管理員了,並且應該很快被改正。謝謝你的關心。" + +#: contrib/admin/templates/admin/filter.html:2 +#, python-format +msgid " By %(filter_title)s " +msgstr " 由 %(filter_title)s" + +#: contrib/admin/templates/admin/filters.html:4 +msgid "Filter" +msgstr "過濾器" + +#: contrib/admin/templates/admin/invalid_setup.html:8 +msgid "" +"Something's wrong with your database installation. Make sure the appropriate " +"database tables have been created, and make sure the database is readable by " +"the appropriate user." +msgstr "你的資料庫安裝有錯誤。確定資料庫表格已經建立,並確定資料庫可被合適的使用者讀取。" + +#: contrib/admin/templates/admin/search_form.html:8 +msgid "Go" +msgstr "去" + +#: contrib/admin/templates/admin/search_form.html:10 +#, python-format +msgid "1 result" +msgid_plural "%(counter)s results" +msgstr[0] "1 則结果" +msgstr[1] "%(counter)s 則结果" + +#: contrib/admin/templates/admin/search_form.html:10 +#, python-format +msgid "%(full_result_count)s total" +msgstr "總共 %(full_result_count)s" + +#: contrib/admin/templates/admin/object_history.html:5 +#: contrib/admin/templates/admin/change_form.html:21 + +msgid "History" +msgstr "歷史" + +#: contrib/admin/templates/admin/object_history.html:18 +msgid "Date/time" +msgstr "日期/時間" + + +#: contrib/admin/templates/admin/object_history.html:19 +msgid "User" +msgstr "使用者" + +#: contrib/admin/templates/admin/object_history.html:20 + +msgid "Action" +msgstr "動作" + +#: contrib/admin/templates/admin/object_history.html:26 +msgid "DATE_WITH_TIME_FULL" +msgstr "N j, Y, P" + + +#: contrib/admin/templates/admin/object_history.html:36 +msgid "" +"This object doesn't have a change history. It probably wasn't added via this " +"admin site." +msgstr "這個物件沒有變更的歷史。它可能不是透過這個管理網站新增的。" + + +#: contrib/admin/templates/admin/delete_confirmation.html:14 +#, python-format +msgid "" +"Deleting the %(object_name)s '%(escaped_object)s' would result in deleting " +"related objects, but your account doesn't have permission to delete the " + +"following types of objects:" +msgstr "" +"刪除 %(object_name)s '%(escaped_object)s' 會把相關的物件也刪除,不過你的帳號並沒有刪" +"除以下型態物件的權限:" + +#: contrib/admin/templates/admin/delete_confirmation.html:21 +#, python-format +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 "" +"你確定想要刪除 %(object_name)s \"%(escaped_object)s\"?以下所有的相關項目都會被刪除:" + +#: contrib/admin/templates/admin/delete_confirmation.html:26 +msgid "Yes, I'm sure" +msgstr "是的,我確定" + +#: contrib/admin/templates/admin/pagination.html:10 +msgid "Show all" +msgstr "顯示全部" + +#: contrib/admin/templates/admin/change_list.html:12 +#, python-format +msgid "Add %(name)s" +msgstr "新增 %(name)s" + +#: contrib/admin/templates/admin/change_form.html:15 +#: contrib/admin/templates/admin/index.html:28 +msgid "Add" +msgstr "新增" + +#: contrib/admin/templates/admin/change_form.html:22 +msgid "View on site" +msgstr "在網站上檢視" + +#: contrib/admin/templates/admin/change_form.html:32 +#: contrib/admin/templates/admin/auth/user/change_password.html:24 +msgid "Please correct the error below." +msgid_plural "Please correct the errors below." +msgstr[0] "请更正下面的錯誤。" + +#: contrib/admin/templates/admin/change_form.html:50 +msgid "Ordering" +msgstr "排序中" + +#: contrib/admin/templates/admin/change_form.html:53 +msgid "Order:" +msgstr "排序:" + +#: contrib/admin/templates/admin/base.html:25 +msgid "Welcome," +msgstr "歡迎" + +#: contrib/admin/templates/admin/404.html:4 +#: contrib/admin/templates/admin/404.html:8 +msgid "Page not found" +msgstr "頁面沒有找到" + +#: contrib/admin/templates/admin/404.html:10 +msgid "We're sorry, but the requested page could not be found." +msgstr "很抱歉,請求的頁面無法找到。" + +#: contrib/admin/templates/admin/login.html:25 +#: contrib/admin/views/decorators.py:24 +msgid "Log in" +msgstr "登入" + +#: contrib/admin/templates/admin/index.html:17 +#, python-format +msgid "Models available in the %(name)s application." +msgstr "在 %(name)s 應用中模形可用。" + +#: contrib/admin/templates/admin/index.html:18 +#, python-format +msgid "%(name)s" +msgstr "%(name)s" + +#: contrib/admin/templates/admin/index.html:34 +msgid "Change" +msgstr "變更" + +#: contrib/admin/templates/admin/index.html:44 +msgid "You don't have permission to edit anything." +msgstr "你沒有編輯任何東西的權限。" + +#: contrib/admin/templates/admin/index.html:52 +msgid "Recent Actions" +msgstr "最近的動作" + +#: contrib/admin/templates/admin/index.html:53 +msgid "My Actions" +msgstr "我的動作" + +#: contrib/admin/templates/admin/index.html:57 +msgid "None available" +msgstr "沒有可用的" + +#: contrib/admin/templates/admin/base_site.html:4 +msgid "Django site admin" +msgstr "Django 網站管理" + +#: contrib/admin/templates/admin/base_site.html:7 +msgid "Django administration" +msgstr "Django 管理" + +#: contrib/admin/templates/admin/auth/user/add_form.html:6 +msgid "" +"First, enter a username and password. Then, you'll be able to edit more user " +"options." +msgstr "首先,輸入一個使用者名稱和密碼。然後你可以編輯更多使用者選項。" + +#: contrib/admin/templates/admin/auth/user/add_form.html:12 +msgid "Username" +msgstr "使用者名稱" + +#: contrib/admin/templates/admin/auth/user/add_form.html:18 +#: contrib/admin/templates/admin/auth/user/change_password.html:34 +msgid "Password" +msgstr "密碼" + +#: contrib/admin/templates/admin/auth/user/add_form.html:23 +#: contrib/admin/templates/admin/auth/user/change_password.html:39 +msgid "Password (again)" +msgstr "密碼(重複)" + +#: contrib/admin/templates/admin/auth/user/add_form.html:24 +#: contrib/admin/templates/admin/auth/user/change_password.html:40 +msgid "Enter the same password as above, for verification." +msgstr "" + +#: contrib/admin/templates/admin/auth/user/change_password.html:28 +#, python-format +msgid "Enter a new password for the user %(username)s." +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" -msgstr "重導向自" +#: contrib/admin/templates/widget/date_time.html:3 +msgid "Date:" +msgstr "日期" -#: contrib/redirects/models.py:8 +#: contrib/admin/templates/widget/date_time.html:4 +msgid "Time:" +msgstr "時間" + +#: contrib/admin/templates/registration/logged_out.html:8 +msgid "Thanks for spending some quality time with the Web site today." +msgstr "感謝你今天花了重要的時間留在本網站。" + +#: contrib/admin/templates/registration/logged_out.html:10 +msgid "Log in again" +msgstr "再次登入" + +#: contrib/admin/templates/registration/password_reset_email.html:2 +msgid "You're receiving this e-mail because you requested a password reset" +msgstr "因為你要求重設密碼,所以收到了這封電子郵件" + +#: contrib/admin/templates/registration/password_reset_email.html:3 +#, python-format +msgid "for your user account at %(site_name)s" +msgstr "你在 %(site_name)s 裡的使用者帳號" + +#: contrib/admin/templates/registration/password_reset_email.html:5 +#, python-format +msgid "Your new password is: %(new_password)s" +msgstr "你的新密碼是: %(new_password)s" + +#: contrib/admin/templates/registration/password_reset_email.html:7 +msgid "Feel free to change this password by going to this page:" +msgstr "放心地到此頁面變更密碼:" + +#: contrib/admin/templates/registration/password_reset_email.html:11 +msgid "Your username, in case you've forgotten:" +msgstr "你的使用者名稱,萬一你已經忘記的話:" + +#: contrib/admin/templates/registration/password_reset_email.html:13 +msgid "Thanks for using our site!" +msgstr "感謝使用本網站!" + +#: contrib/admin/templates/registration/password_reset_email.html:15 +#, python-format +msgid "The %(site_name)s team" +msgstr "%(site_name)s 團隊" + + +#: contrib/admin/templates/registration/password_reset_done.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:4 +#: contrib/admin/templates/registration/password_reset_form.html:6 +#: contrib/admin/templates/registration/password_reset_form.html:10 +msgid "Password reset" +msgstr "密碼重設" + +#: contrib/admin/templates/registration/password_reset_done.html:6 +#: contrib/admin/templates/registration/password_reset_done.html:10 +msgid "Password reset successful" +msgstr "密碼成功地重設" + +#: contrib/admin/templates/registration/password_reset_done.html:12 msgid "" -"This should be an absolute path, excluding the domain name. Example: '/" -"events/search/'." -msgstr "此應為一絕對路徑,但不包括網域名稱。範例: '/events/search/'。" +"We've e-mailed a new password to the e-mail address you submitted. You " +"should be receiving it shortly." +msgstr "我們已經把新的密碼寄到你送出的電子郵件地址。你應該不久就能收到。" -#: contrib/redirects/models.py:9 -msgid "redirect to" -msgstr "重導向至" +#: contrib/admin/templates/registration/password_change_form.html:4 +#: contrib/admin/templates/registration/password_change_form.html:6 +#: contrib/admin/templates/registration/password_change_form.html:10 +#: contrib/admin/templates/registration/password_change_done.html:4 +msgid "Password change" +msgstr "密碼變更" -#: contrib/redirects/models.py:10 +#: contrib/admin/templates/registration/password_change_form.html:12 msgid "" -"This can be either an absolute path (as above) or a full URL starting with " -"'http://'." -msgstr "此可為一絕對路徑 (如上) 或一個以 'http://' 開頭的完整 URL. " +"Please enter your old password, for security's sake, and then enter your new " +"password twice so we can verify you typed it in correctly." +msgstr "為了安全上的考慮,請輸入你的舊密碼,再輸入新密碼兩次,讓我們核驗你已正確地輸入。" -#: contrib/redirects/models.py:12 -msgid "redirect" -msgstr "重導向" +#: contrib/admin/templates/registration/password_change_form.html:17 +msgid "Old password:" +msgstr "舊密碼" -#: contrib/redirects/models.py:13 -msgid "redirects" -msgstr "重導向" +#: contrib/admin/templates/registration/password_change_form.html:19 +msgid "New password:" +msgstr "新密碼" -#: contrib/flatpages/models.py:8 +#: contrib/admin/templates/registration/password_change_form.html:21 +msgid "Confirm password:" +msgstr "確認密碼" + +#: contrib/admin/templates/registration/password_change_form.html:23 +msgid "Change my password" +msgstr "變更我的密碼" + +#: contrib/admin/templates/registration/password_change_done.html:6 +#: contrib/admin/templates/registration/password_change_done.html:10 +msgid "Password change successful" +msgstr "密碼成功地變更" + +#: contrib/admin/templates/registration/password_change_done.html:12 +msgid "Your password was changed." +msgstr "你的密碼已變更。" + +#: contrib/admin/templates/registration/password_reset_form.html:12 msgid "" -"Example: '/about/contact/'. Make sure to have leading and trailing slashes." -msgstr "範例: '/about/contact/'。開頭與結束都一定要有斜線。" +"Forgotten your password? Enter your e-mail address below, and we'll reset " +"your password and e-mail the new one to you." +msgstr "忘記你的密碼了?在下面輸入你的電子郵件地址,我們就會重設你的密碼並把新的用電" +"子郵件寄給你。" +#: contrib/admin/templates/registration/password_reset_form.html:16 +msgid "E-mail address:" +msgstr "電子郵件地址:" -#: contrib/flatpages/models.py:9 -msgid "title" -msgstr "標題" +#: contrib/admin/templates/registration/password_reset_form.html:16 +msgid "Reset my password" +msgstr "重設我的密碼" -#: contrib/flatpages/models.py:10 -msgid "content" -msgstr "內容" +#: contrib/admin/views/main.py:223 +msgid "Site administration" +msgstr "網站管理" -#: contrib/flatpages/models.py:11 -msgid "enable comments" -msgstr "啟用評論" +#: contrib/admin/views/main.py:257 contrib/admin/views/auth.py:19 +#, python-format +msgid "The %(name)s \"%(obj)s\" was added successfully." +msgstr "%(name)s \"%(obj)s\" 已成功變更。" -#: contrib/flatpages/models.py:12 -msgid "template name" -msgstr "模板名稱" +#: contrib/admin/views/main.py:261 contrib/admin/views/main.py:347 +#: contrib/admin/views/auth.py:24 +msgid "You may edit it again below." +msgstr "你可以在下面再編輯一次。" -#: contrib/flatpages/models.py:13 -msgid "" -"Example: 'flatpages/contact_page'. If this isn't provided, the system will " -"use 'flatpages/default'." +#: contrib/admin/views/main.py:271 contrib/admin/views/main.py:356 +#, python-format +msgid "You may add another %s below." +msgstr "你可以在下面新增另一個 %s。" + +#: contrib/admin/views/main.py:289 +#, python-format +msgid "Add %s" +msgstr "新增 %s" + +#: contrib/admin/views/main.py:335 +#, python-format +msgid "Added %s." +msgstr "%s 已新增。" + +#: contrib/admin/views/main.py:337 +#, python-format +msgid "Changed %s." +msgstr "%s 已變更。" + +#: contrib/admin/views/main.py:339 +#, python-format +msgid "Deleted %s." +msgstr "%s 已删除。" + +#: contrib/admin/views/main.py:342 +msgid "No fields changed." +msgstr "沒有欄位被變更。" + +#: contrib/admin/views/main.py:345 +#, python-format +msgid "The %(name)s \"%(obj)s\" was changed successfully." msgstr "" -"範例: 'flatpages/contact_page'。如果沒有提供它,系統會使用 'flatpages/" -"default'。" -#: contrib/flatpages/models.py:14 -msgid "registration required" -msgstr "需要註冊" +#: contrib/admin/views/main.py:353 +#, python-format +msgid "" +"The %(name)s \"%(obj)s\" was added successfully. You may edit it again below." +msgstr "%(name)s \"%(obj)s\" 已成功變更。" -#: contrib/flatpages/models.py:14 -msgid "If this is checked, only logged-in users will be able to view the page." -msgstr "如果此項被選取,則只有登入的使用者可以檢視頁面。" +#: contrib/admin/views/main.py:391 +#, python-format +msgid "Change %s" +msgstr "變更 %s" -#: contrib/flatpages/models.py:18 -msgid "flat page" -msgstr "簡平頁面" +#: contrib/admin/views/main.py:476 +#, python-format +msgid "One or more %(fieldname)s in %(name)s: %(obj)s" +msgstr "在 %(name)s: 裡的一個或多個 %(fieldname)s %(obj)s" -#: contrib/flatpages/models.py:19 -msgid "flat pages" -msgstr "簡平頁面" +#: contrib/admin/views/main.py:481 +#, python-format +msgid "One or more %(fieldname)s in %(name)s:" +msgstr "在 %(name)s 裡的一個或多個 %(fieldname)s:" -#: contrib/auth/models.py:13 contrib/auth/models.py:26 -msgid "name" -msgstr "名稱" +#: contrib/admin/views/main.py:514 +#, python-format +msgid "The %(name)s \"%(obj)s\" was deleted successfully." +msgstr "%(name)s \"%(obj)s\" 已成功刪除。" -#: contrib/auth/models.py:15 -msgid "codename" -msgstr "codename" +#: contrib/admin/views/main.py:517 +msgid "Are you sure?" +msgstr "你確定?" -#: contrib/auth/models.py:17 -#, fuzzy -msgid "permission" -msgstr "權限" +#: contrib/admin/views/main.py:539 +#, python-format +msgid "Change history: %s" +msgstr "變更歷史: %s" -#: contrib/auth/models.py:18 contrib/auth/models.py:27 -#, fuzzy -msgid "permissions" -msgstr "權限" +#: contrib/admin/views/main.py:573 +#, python-format +msgid "Select %s" +msgstr "選擇 %s" -#: contrib/auth/models.py:29 -#, fuzzy -msgid "group" -msgstr "群組" +#: contrib/admin/views/main.py:573 +#, python-format +msgid "Select %s to change" +msgstr "選擇 %s 來變更" -#: contrib/auth/models.py:30 contrib/auth/models.py:65 -#, fuzzy -msgid "groups" -msgstr "群組" +#: contrib/admin/views/main.py:768 +msgid "Database error" +msgstr "資料庫錯誤" -#: contrib/auth/models.py:55 -msgid "username" -msgstr "使用者名稱" +#: contrib/admin/views/decorators.py:62 +msgid "" +"Please log in again, because your session has expired. Don't worry: Your " +"submission has been saved." +msgstr "請再登入一次,因為你的 session 已經到期。不必擔心: 你的提交已被儲存。" -#: contrib/auth/models.py:56 -msgid "first name" -msgstr "名字" +#: contrib/admin/views/decorators.py:69 +msgid "" +"Looks like your browser isn't configured to accept cookies. Please enable " +"cookies, reload this page, and try again." +msgstr "看起來你的瀏覽器沒有設定成允許 cookie。請啟用 cookie、重新載入此頁,然後再試一次" -#: contrib/auth/models.py:57 -msgid "last name" -msgstr "姓氏" +#: contrib/admin/views/decorators.py:83 +msgid "Usernames cannot contain the '@' character." +msgstr "使用者名稱不能包含 '@' 字元。" -#: contrib/auth/models.py:58 -msgid "e-mail address" +#: contrib/admin/views/decorators.py:85 +#, python-format +msgid "Your e-mail address is not your username. Try '%s' instead." +msgstr "你的電子郵件地址不是你的使用者名稱。試著改用 '%s'。" + +#: contrib/admin/views/doc.py:46 contrib/admin/views/doc.py:48 +#: contrib/admin/views/doc.py:50 +msgid "tag:" +msgstr "標籤:" + +#: contrib/admin/views/doc.py:77 contrib/admin/views/doc.py:79 +#: contrib/admin/views/doc.py:81 +msgid "filter:" +msgstr "過濾器:" + +#: contrib/admin/views/doc.py:135 contrib/admin/views/doc.py:137 +#: contrib/admin/views/doc.py:139 +msgid "view:" +msgstr "視圖:" + +#: contrib/admin/views/doc.py:164 +#, python-format +msgid "App %r not found" +msgstr "應用程式 %r 没有找到" + +#: contrib/admin/views/doc.py:171 +#, python-format +msgid "Model %(name)r not found in app %(label)r" +msgstr "在應用程式 %(label)r 裡找不到模型 %(name)r" + +#: contrib/admin/views/doc.py:183 +#, python-format +msgid "the related `%(label)s.%(type)s` object" +msgstr "相關的 `%(label)s.%(type)s` 物件" + +#: contrib/admin/views/doc.py:183 contrib/admin/views/doc.py:205 +#: contrib/admin/views/doc.py:219 contrib/admin/views/doc.py:224 +msgid "model:" +msgstr "模型:" + +#: contrib/admin/views/doc.py:214 +#, python-format +msgid "related `%(label)s.%(name)s` objects" +msgstr "相關的 `%(label)s.%(name)s` 物件" + +#: contrib/admin/views/doc.py:219 +#, python-format +msgid "all %s" +msgstr "所有 %s" + +#: contrib/admin/views/doc.py:224 +#, python-format +msgid "number of %s" +msgstr "%s 的数量" + +#: contrib/admin/views/doc.py:229 +#, python-format +msgid "Fields on %s objects" +msgstr "%s 物件的欄位" + +#: contrib/admin/views/doc.py:291 contrib/admin/views/doc.py:301 +#: contrib/admin/views/doc.py:303 contrib/admin/views/doc.py:309 +#: contrib/admin/views/doc.py:310 contrib/admin/views/doc.py:312 +msgid "Integer" +msgstr "整數" + +#: contrib/admin/views/doc.py:292 +msgid "Boolean (Either True or False)" +msgstr "布林值 (True 或 False)" + +#: contrib/admin/views/doc.py:293 contrib/admin/views/doc.py:311 +#, python-format +msgid "String (up to %(maxlength)s)" +msgstr "字串(最長 %(maxlength)s)" + +#: contrib/admin/views/doc.py:294 +msgid "Comma-separated integers" +msgstr "逗號分隔的整數" + +#: contrib/admin/views/doc.py:295 +msgid "Date (without time)" +msgstr "日期 (不包括時間)" + +#: contrib/admin/views/doc.py:296 +msgid "Date (with time)" +msgstr "日期 (包括時間)" + +#: contrib/admin/views/doc.py:297 +msgid "E-mail address" msgstr "電子郵件地址" -#: contrib/auth/models.py:59 -msgid "password" -msgstr "密碼" +#: contrib/admin/views/doc.py:298 contrib/admin/views/doc.py:299 +#: contrib/admin/views/doc.py:302 +msgid "File path" +msgstr "檔案路徑" -#: contrib/auth/models.py:59 -msgid "Use '[algo]$[salt]$[hexdigest]'" -msgstr "使用 '[algo]$[salt]$[hexdigest]'" +#: contrib/admin/views/doc.py:300 +msgid "Decimal number" +msgstr "小數" -#: contrib/auth/models.py:60 -msgid "staff status" -msgstr "工作人員狀態" +#: contrib/admin/views/doc.py:306 +msgid "Boolean (Either True, False or None)" +msgstr "布林值 (True, False 或 None)" -#: contrib/auth/models.py:60 -msgid "Designates whether the user can log into this admin site." -msgstr "指明何使用者可以登入此管理網站。" +#: contrib/admin/views/doc.py:307 +msgid "Relation to parent model" +msgstr "與父模型的關係" -#: contrib/auth/models.py:61 -msgid "active" -msgstr "活動中" +#: contrib/admin/views/doc.py:308 +msgid "Phone number" +msgstr "電話號碼" -#: contrib/auth/models.py:62 -msgid "superuser status" -msgstr "超級使用者狀態" +#: contrib/admin/views/doc.py:313 +msgid "Text" +msgstr "文字" -#: contrib/auth/models.py:63 -msgid "last login" -msgstr "上次登入" +#: contrib/admin/views/doc.py:314 +msgid "Time" +msgstr "時間" -#: contrib/auth/models.py:64 -msgid "date joined" -msgstr "加入日期" +#: contrib/admin/views/doc.py:316 +msgid "U.S. state (two uppercase letters)" +msgstr "美國州名 (兩個大寫字母)" -#: contrib/auth/models.py:66 -msgid "" -"In addition to the permissions manually assigned, this user will also get " -"all permissions granted to each group he/she is in." -msgstr "除了手動指定的權限之外,這個使用者也會得到其群組擁有的所有權限。" +#: contrib/admin/views/doc.py:317 +msgid "XML text" +msgstr "XML 文件" -#: contrib/auth/models.py:67 -#, fuzzy -msgid "user permissions" -msgstr "權限" +#: contrib/admin/views/doc.py:343 +#, python-format +msgid "%s does not appear to be a urlpattern object" +msgstr "%s 似乎不是一個 urlpattern 物件" -#: contrib/auth/models.py:70 -#, fuzzy -msgid "user" -msgstr "使用者" +#: contrib/admin/views/auth.py:30 +msgid "Add user" +msgstr "增加使用者" -#: contrib/auth/models.py:71 -#, fuzzy -msgid "users" -msgstr "使用者" - -#: contrib/auth/models.py:76 -msgid "Personal info" -msgstr "個人資訊" - -#: contrib/auth/models.py:77 -msgid "Permissions" -msgstr "權限" - -#: contrib/auth/models.py:78 -msgid "Important dates" -msgstr "重要日期" - -#: contrib/auth/models.py:79 -msgid "Groups" -msgstr "群組" - -#: contrib/auth/models.py:219 -#, fuzzy -msgid "message" -msgstr "訊息" - -#: contrib/auth/forms.py:30 -msgid "" -"Your Web browser doesn't appear to have cookies enabled. Cookies are " -"required for logging in." -msgstr "你的網頁瀏覽器看來沒有啟用 cookie。若要登入則需使用 cookie。" - -#: contrib/contenttypes/models.py:25 -#, fuzzy -msgid "python model class name" -msgstr "python 模組名稱" - -#: contrib/contenttypes/models.py:28 -msgid "content type" -msgstr "內容型態" - -#: contrib/contenttypes/models.py:29 -msgid "content types" -msgstr "內容型態" - -#: contrib/sessions/models.py:35 -msgid "session key" -msgstr "session 鍵值" - -#: contrib/sessions/models.py:36 -msgid "session data" -msgstr "session 資料" - -#: contrib/sessions/models.py:37 -msgid "expire date" -msgstr "到期日期" - -#: contrib/sessions/models.py:41 -msgid "session" -msgstr "session" - -#: contrib/sessions/models.py:42 -msgid "sessions" -msgstr "sessions" - -#: contrib/sites/models.py:10 -msgid "domain name" -msgstr "網域名稱" - -#: contrib/sites/models.py:11 -msgid "display name" -msgstr "顯示名稱" - -#: contrib/sites/models.py:15 -msgid "site" -msgstr "網站" - -#: contrib/sites/models.py:16 -msgid "sites" -msgstr "網站" - -#: utils/translation.py:360 -msgid "DATE_FORMAT" -msgstr "DATE_FORMAT" - -#: utils/translation.py:361 -msgid "DATETIME_FORMAT" -msgstr "DATETIME_FORMAT" - -#: utils/translation.py:362 -msgid "TIME_FORMAT" -msgstr "TIME_FORMAT" - -#: utils/dates.py:6 -msgid "Monday" -msgstr "週一" - -#: utils/dates.py:6 -msgid "Tuesday" -msgstr "週二" - -#: utils/dates.py:6 -msgid "Wednesday" -msgstr "週三" - -#: utils/dates.py:6 -msgid "Thursday" -msgstr "週四" - -#: utils/dates.py:6 -msgid "Friday" -msgstr "週五" - -#: utils/dates.py:7 -msgid "Saturday" -msgstr "週六" - -#: utils/dates.py:7 -msgid "Sunday" -msgstr "週日" - -#: utils/dates.py:14 -msgid "January" -msgstr "一月" - -#: utils/dates.py:14 -msgid "February" -msgstr "二月" - -#: utils/dates.py:14 utils/dates.py:27 -msgid "March" -msgstr "三月" - -#: utils/dates.py:14 utils/dates.py:27 -msgid "April" -msgstr "四月" - -#: utils/dates.py:14 utils/dates.py:27 -msgid "May" -msgstr "五月" - -#: utils/dates.py:14 utils/dates.py:27 -msgid "June" -msgstr "六月" - -#: utils/dates.py:15 utils/dates.py:27 -msgid "July" -msgstr "七月" - -#: utils/dates.py:15 -msgid "August" -msgstr "八月" - -#: utils/dates.py:15 -msgid "September" -msgstr "九月" - -#: utils/dates.py:15 -msgid "October" -msgstr "十月" - -#: utils/dates.py:15 -msgid "November" -msgstr "十一月" - -#: utils/dates.py:16 -msgid "December" -msgstr "十二月" - -#: utils/dates.py:19 -#, fuzzy -msgid "jan" -msgstr "和" - -#: utils/dates.py:19 -msgid "feb" +#: contrib/admin/views/auth.py:57 +msgid "Password changed successfully." msgstr "" -#: utils/dates.py:19 -msgid "mar" -msgstr "" +#: contrib/admin/views/auth.py:64 +#, python-format +msgid "Change password: %s" +msgstr "密碼變更: %s" -#: utils/dates.py:19 -msgid "apr" -msgstr "" +#: newforms/fields.py:103 newforms/fields.py:256 +#, python-format +msgid "Ensure this value has at most %d characters." +msgstr "確定你輸入的值最多有 %d 字。" -#: utils/dates.py:19 -#, fuzzy -msgid "may" -msgstr "日" +#: newforms/fields.py:105 newforms/fields.py:258 +#, python-format +msgid "Ensure this value has at least %d characters." +msgstr "確定你輸入的值最少有 %d 字。" -#: utils/dates.py:19 -msgid "jun" -msgstr "" +#: newforms/fields.py:128 core/validators.py:120 +msgid "Enter a whole number." +msgstr "輸入整數" -#: utils/dates.py:20 -msgid "jul" -msgstr "" +#: newforms/fields.py:130 +#, python-format +msgid "Ensure this value is less than or equal to %s." +msgstr "這個值必须小於或等於 %s。" -#: utils/dates.py:20 -msgid "aug" -msgstr "" +#: newforms/fields.py:132 +#, python-format +msgid "Ensure this value is greater than or equal to %s." +msgstr "這個值必须大於或等於 %s。" -#: utils/dates.py:20 -msgid "sep" -msgstr "" +#: newforms/fields.py:165 +msgid "Enter a valid date." +msgstr "輸入有效的日期" -#: utils/dates.py:20 -msgid "oct" -msgstr "" +#: newforms/fields.py:192 +msgid "Enter a valid time." +msgstr "輸入有效的時間" -#: utils/dates.py:20 -msgid "nov" -msgstr "" +#: newforms/fields.py:228 +msgid "Enter a valid date/time." +msgstr "輸入有效的日期/時間" -#: utils/dates.py:20 -msgid "dec" -msgstr "" +#: newforms/fields.py:242 +msgid "Enter a valid value." +msgstr "輸入有效的值" -#: utils/dates.py:27 -msgid "Jan." -msgstr "一月" +#: newforms/fields.py:271 core/validators.py:162 +msgid "Enter a valid e-mail address." +msgstr "輸入有效的電子郵件地址。" -#: utils/dates.py:27 -msgid "Feb." -msgstr "二月" +#: newforms/fields.py:289 newforms/fields.py:311 +msgid "Enter a valid URL." +msgstr "輸入有效的URL" -#: utils/dates.py:28 -msgid "Aug." -msgstr "八月" +#: newforms/fields.py:313 +msgid "This URL appears to be a broken link." +msgstr "URL似乎是是斷掉的連結。" -#: utils/dates.py:28 -msgid "Sept." -msgstr "九月" +#: newforms/fields.py:362 newforms/models.py:165 +msgid "Select a valid choice. That choice is not one of the available choices." +msgstr "選擇有效的選項: 此選擇不在可用的选项中。" -#: utils/dates.py:28 -msgid "Oct." -msgstr "十月" +#: newforms/fields.py:380 newforms/fields.py:456 newforms/models.py:182 +msgid "Enter a list of values." +msgstr "輸入一個列表的值" -#: utils/dates.py:28 -msgid "Nov." -msgstr "十一月" +#: newforms/fields.py:389 newforms/models.py:188 +#, python-format +msgid "Select a valid choice. %s is not one of the available choices." +msgstr "選擇一個有效的選項: '%s' 不在可用的選項中。" -#: utils/dates.py:28 -msgid "Dec." -msgstr "十二月" +#: template/defaultfilters.py:491 +msgid "yes,no,maybe" +msgstr "是、否、也許" -#: utils/timesince.py:12 -msgid "year" -msgid_plural "years" -msgstr[0] "年" -msgstr[1] "年" +#: views/generic/create_update.py:43 +#, python-format +msgid "The %(verbose_name)s was created successfully." +msgstr "%(verbose_name)s 新增成功。" -#: utils/timesince.py:13 -msgid "month" -msgid_plural "months" -msgstr[0] "月" -msgstr[1] "月" +#: views/generic/create_update.py:117 +#, python-format +msgid "The %(verbose_name)s was updated successfully." +msgstr "%(verbose_name)s 變更成功。" -#: utils/timesince.py:14 -msgid "week" -msgid_plural "weeks" -msgstr[0] "" -msgstr[1] "" +#: views/generic/create_update.py:184 +#, python-format +msgid "The %(verbose_name)s was deleted." +msgstr "%(verbose_name)s 已被删除。" -#: utils/timesince.py:15 -msgid "day" -msgid_plural "days" -msgstr[0] "日" -msgstr[1] "日" - -#: utils/timesince.py:16 -msgid "hour" -msgid_plural "hours" -msgstr[0] "時" -msgstr[1] "時" - -#: utils/timesince.py:17 -msgid "minute" -msgid_plural "minutes" -msgstr[0] "分" -msgstr[1] "分" - -#: conf/global_settings.py:37 -msgid "Bengali" -msgstr "孟加拉文" - -#: conf/global_settings.py:38 -msgid "Czech" -msgstr "捷克文" - -#: conf/global_settings.py:39 -msgid "Welsh" -msgstr "威爾斯文" - -#: conf/global_settings.py:40 -msgid "Danish" -msgstr "丹麥文" - -#: conf/global_settings.py:41 -msgid "German" -msgstr "德文" - -#: conf/global_settings.py:42 -msgid "Greek" -msgstr "" - -#: conf/global_settings.py:43 -msgid "English" -msgstr "英文" - -#: conf/global_settings.py:44 -msgid "Spanish" -msgstr "西班牙文" - -#: conf/global_settings.py:45 -msgid "French" -msgstr "法文" - -#: conf/global_settings.py:46 -msgid "Galician" -msgstr "加利西亞文" - -#: conf/global_settings.py:47 -msgid "Hungarian" -msgstr "" - -#: conf/global_settings.py:48 -msgid "Hebrew" -msgstr "" - -#: conf/global_settings.py:49 -msgid "Icelandic" -msgstr "冰島文" - -#: conf/global_settings.py:50 -msgid "Italian" -msgstr "義大利文" - -#: conf/global_settings.py:51 -msgid "Japanese" -msgstr "日文" - -#: conf/global_settings.py:52 -msgid "Dutch" -msgstr "荷蘭文" - -#: conf/global_settings.py:53 -msgid "Norwegian" -msgstr "挪威文" - -#: conf/global_settings.py:54 -msgid "Brazilian" -msgstr "巴西文" - -#: conf/global_settings.py:55 -msgid "Romanian" -msgstr "羅馬尼亞文" - -#: conf/global_settings.py:56 -msgid "Russian" -msgstr "俄文" - -#: conf/global_settings.py:57 -msgid "Slovak" -msgstr "斯洛伐克文" - -#: conf/global_settings.py:58 -#, fuzzy -msgid "Slovenian" -msgstr "斯洛伐克文" - -#: conf/global_settings.py:59 -msgid "Serbian" -msgstr "塞爾維亞文" - -#: conf/global_settings.py:60 -msgid "Swedish" -msgstr "瑞典文" - -#: conf/global_settings.py:61 -#, fuzzy -msgid "Ukrainian" -msgstr "巴西文" - -#: conf/global_settings.py:62 -msgid "Simplified Chinese" -msgstr "簡體中文" - -#: conf/global_settings.py:63 -#, fuzzy -msgid "Traditional Chinese" -msgstr "繁體中文" - -#: core/validators.py:60 +#: core/validators.py:64 msgid "This value must contain only letters, numbers and underscores." msgstr "此值僅能包含字母、數字與底線。" -#: core/validators.py:64 -#, fuzzy +#: core/validators.py:68 msgid "" "This value must contain only letters, numbers, underscores, dashes or " "slashes." msgstr "此值僅能包含字母、數字、底線與斜線。" #: core/validators.py:72 +msgid "This value must contain only letters, numbers, underscores or hyphens." +msgstr "此值僅能包含字母、數字、底線與連字號" + +#: core/validators.py:76 msgid "Uppercase letters are not allowed here." msgstr "此處不允許大寫字母。" -#: core/validators.py:76 +#: core/validators.py:80 msgid "Lowercase letters are not allowed here." msgstr "此處不允許小寫字母。" -#: core/validators.py:83 +#: core/validators.py:87 msgid "Enter only digits separated by commas." msgstr "輸入以逗號分隔的數字。" -#: core/validators.py:95 +#: core/validators.py:99 msgid "Enter valid e-mail addresses separated by commas." msgstr "輸入以逗號分隔的有效電子郵件地址。" -#: core/validators.py:99 +#: core/validators.py:103 msgid "Please enter a valid IP address." msgstr "請輸入有效的 IP 位址。" -#: core/validators.py:103 +#: core/validators.py:107 msgid "Empty values are not allowed here." msgstr "此處不允許空值。" -#: core/validators.py:107 +#: core/validators.py:111 msgid "Non-numeric characters aren't allowed here." msgstr "此處不允許非數字字元。" -#: core/validators.py:111 +#: core/validators.py:115 msgid "This value can't be comprised solely of digits." msgstr "此值不能只以數字組成。" -#: core/validators.py:116 -msgid "Enter a whole number." -msgstr "輸入一個整數。" - -#: core/validators.py:120 +#: core/validators.py:124 msgid "Only alphabetical characters are allowed here." msgstr "此處只允許字母。" -#: core/validators.py:124 -msgid "Enter a valid date in YYYY-MM-DD format." -msgstr "以 YYYY-MM-DD 格式輸入有效的日期。" +#: core/validators.py:139 +msgid "Year must be 1900 or later." +msgstr "年份必須是1900或之後" -#: core/validators.py:128 +#: core/validators.py:143 +#, python-format +msgid "Invalid date: %s" +msgstr "" + +#: core/validators.py:153 msgid "Enter a valid time in HH:MM format." -msgstr "以 HH:MM 格式輸入有效的時間。" -#: core/validators.py:132 db/models/fields/__init__.py:468 -msgid "Enter a valid date/time in YYYY-MM-DD HH:MM format." -msgstr "以 YYYY-MM-DD HH:MM 格式輸入有效的日期/時間。" +msgstr "輸入 HH:MM 有效的時間格式。" -#: core/validators.py:136 -msgid "Enter a valid e-mail address." -msgstr "輸入有效的電子郵件地址。" - -#: core/validators.py:148 +#: core/validators.py:178 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." -msgstr "上傳一個有效的影像。你上傳的檔案不是影像,否則就是壞掉了。" +msgstr "上傳一個有效的影像。你上傳的檔案不是影像,否則就是壞掉的圖。" -#: core/validators.py:155 +#: core/validators.py:185 #, python-format msgid "The URL %s does not point to a valid image." -msgstr "URL %s 未指向有效的影像。" +msgstr "URL %s 不是指向一個有效的圖片。" -#: core/validators.py:159 +#: core/validators.py:189 #, python-format msgid "Phone numbers must be in XXX-XXX-XXXX format. \"%s\" is invalid." msgstr "電話號碼必須是 XXX-XXX-XXXX 格式。\"%s\" 無效。" -#: core/validators.py:167 +#: core/validators.py:197 #, python-format msgid "The URL %s does not point to a valid QuickTime video." + msgstr "URL %s 未指向有效的 QuickTime 視像。" -#: core/validators.py:171 +#: core/validators.py:201 msgid "A valid URL is required." msgstr "必須是有效的 URL。" -#: core/validators.py:185 + +#: core/validators.py:215 #, python-format msgid "" "Valid HTML is required. Specific errors are:\n" @@ -1707,68 +2510,85 @@ msgstr "" "必須是有效的 HTML。具體的錯誤是:\n" "%s" -#: core/validators.py:192 +#: core/validators.py:222 #, python-format msgid "Badly formed XML: %s" msgstr "排列錯誤的 XML: %s" -#: core/validators.py:202 +#: core/validators.py:239 #, python-format msgid "Invalid URL: %s" msgstr "無效的 URL: %s" -#: core/validators.py:206 core/validators.py:208 +#: core/validators.py:244 core/validators.py:246 #, python-format msgid "The URL %s is a broken link." msgstr "URL %s 是斷掉的連結。" -#: core/validators.py:214 +#: core/validators.py:252 msgid "Enter a valid U.S. state abbreviation." msgstr "輸入有效的 U.S. 州名簡稱。" -#: core/validators.py:229 +#: core/validators.py:266 #, 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] "看管口舌!此處不允許 %s 這樣的字眼。" +msgstr[0] "看住你的嘴!此處不允許 %s 這樣的字眼。" +msgstr[1] "" -#: core/validators.py:236 +#: core/validators.py:273 #, python-format msgid "This field must match the '%s' field." msgstr "此欄位必須符合 '%s' 欄位。" -#: core/validators.py:255 +#: core/validators.py:292 msgid "Please enter something for at least one field." msgstr "請在至少一個欄位裡進行輸入。" -#: core/validators.py:264 core/validators.py:275 +#: core/validators.py:301 core/validators.py:312 msgid "Please enter both fields or leave them both empty." msgstr "請輸入兩個欄位或全部留空。" -#: core/validators.py:282 +#: core/validators.py:320 #, python-format msgid "This field must be given if %(field)s is %(value)s" msgstr "如果 %(field)s 是 %(value)s 則此欄位必須給定。" -#: core/validators.py:294 +#: core/validators.py:333 #, python-format msgid "This field must be given if %(field)s is not %(value)s" msgstr "如果 %(field)s 不是 %(value)s 則此欄位必須給定。" -#: core/validators.py:313 +#: core/validators.py:352 msgid "Duplicate values are not allowed." msgstr "不允許重複值。" -#: core/validators.py:336 +#: core/validators.py:367 +#, python-format +msgid "This value must be between %(lower)s and %(upper)s." +msgstr "這個值必須介於 %(lower)s 和 %(upper)s 之間。" + +#: core/validators.py:369 +#, python-format +msgid "This value must be at least %s." +msgstr "這個值需要至少為 %s 。" + +#: core/validators.py:371 +#, python-format + +msgid "This value must be no more than %s." +msgstr "這個值不能大於 %s 。" + +#: core/validators.py:407 #, python-format msgid "This value must be a power of %s." msgstr "此值必須是 %s 的乘方。" -#: core/validators.py:347 +#: core/validators.py:418 msgid "Please enter a valid decimal number." msgstr "請輸入有效的小數。" -#: core/validators.py:349 +#: core/validators.py:422 #, python-format msgid "Please enter a valid decimal number with at most %s total digit." msgid_plural "" @@ -1776,7 +2596,17 @@ msgid_plural "" msgstr[0] "請輸入最長 %s 位的有效小數。" msgstr[1] "請輸入最長 %s 位的有效小數。" -#: core/validators.py:352 +#: core/validators.py:425 +#, python-format +msgid "" +"Please enter a valid decimal number with a whole part of at most %s digit." +msgid_plural "" +"Please enter a valid decimal number with a whole part of at most %s digits." +msgstr[0] "請輸入最長 %s 位的有效小數。" +msgstr[1] "請輸入最長 %s 位的有效小數。" + + +#: core/validators.py:428 #, python-format msgid "Please enter a valid decimal number with at most %s decimal place." msgid_plural "" @@ -1784,36 +2614,36 @@ msgid_plural "" msgstr[0] "請輸入小數最長 %s 位的有效小數。" msgstr[1] "請輸入小數最長 %s 位的有效小數。" -#: core/validators.py:362 +#: core/validators.py:438 #, python-format msgid "Make sure your uploaded file is at least %s bytes big." msgstr "確定你上傳的檔案至少有 %s 位元組。" -#: core/validators.py:363 +#: core/validators.py:439 #, python-format msgid "Make sure your uploaded file is at most %s bytes big." msgstr "確定你上傳的檔案最多是 %s 位元組。" -#: core/validators.py:376 +#: core/validators.py:456 msgid "The format for this field is wrong." msgstr "此欄位的格式錯誤。" -#: core/validators.py:391 +#: core/validators.py:471 msgid "This field is invalid." -msgstr "此欄位無效。" +msgstr "此欄位錯誤" -#: core/validators.py:426 +#: core/validators.py:507 #, python-format msgid "Could not retrieve anything from %s." msgstr "無法從 %s 取得任何東西。" -#: core/validators.py:429 +#: core/validators.py:510 #, python-format msgid "" "The URL %(url)s returned the invalid Content-Type header '%(contenttype)s'." msgstr "URL %(url)s 傳回了無效的 Content-Type 標頭 '%(contenttype)s'。" -#: core/validators.py:462 +#: core/validators.py:543 #, python-format msgid "" "Please close the unclosed %(tag)s tag from line %(line)s. (Line starts with " @@ -1822,153 +2652,39 @@ msgstr "" "請將第 %(line)s 行開始未封閉的 %(tag)s 標籤封閉起來 (行開始於 \"%(start)s" "\")。" -#: core/validators.py:466 +#: core/validators.py:547 #, python-format msgid "" "Some text starting on line %(line)s is not allowed in that context. (Line " "starts with \"%(start)s\".)" msgstr "該內容不允許某些從第 %(line)s 開始的文字 (行開始於 \"%(start)s\")。" -#: core/validators.py:471 +#: core/validators.py:552 #, python-format msgid "" "\"%(attr)s\" on line %(line)s is an invalid attribute. (Line starts with \"%" "(start)s\".)" msgstr "" -"第 \"%(line)s\" 的 \"%(attr)s\" 是無效的屬性 (行開始顧 \"%(start)s\")。" +"第 \"%(line)s\" 的 \"%(attr)s\" 是無效的屬性 (行開始於 \"%(start)s\")。" -#: core/validators.py:476 +#: core/validators.py:557 #, python-format msgid "" "\"<%(tag)s>\" on line %(line)s is an invalid tag. (Line starts with \"%" "(start)s\".)" msgstr "第 %(line)s 行的 \"<%(tag)s>\" 是無效的標籤 (行開始於 \"%(start)s\")。" -#: core/validators.py:480 +#: core/validators.py:561 #, python-format msgid "" "A tag on line %(line)s is missing one or more required attributes. (Line " "starts with \"%(start)s\".)" msgstr "" -"第 %(line)s 有一個標籤缺少了一個或更多必需的屬性 (行開始顧 \"%(start)s\")。" +"第 %(line)s 有一個標籤缺少了一個或更多必需的屬性 (行開始於 \"%(start)s\")。" -#: core/validators.py:485 +#: core/validators.py:566 #, python-format msgid "" "The \"%(attr)s\" attribute on line %(line)s has an invalid value. (Line " "starts with \"%(start)s\".)" -msgstr "" -"在第 %(line)s 的 \"%(attr)s\" 屬性有一個無效值 (行開始於 \"%(start)s\")。" - -#: db/models/manipulators.py:302 -#, python-format -msgid "%(object)s with this %(type)s already exists for the given %(field)s." -msgstr "此 %(type)s 的 %(object)s 已存在於給定的 %(field)s 裡。" - -#: db/models/fields/__init__.py:40 -#, python-format -msgid "%(optname)s with this %(fieldname)s already exists." -msgstr "此 %(fieldname)s 欄位的 %(optname)s 已經存在。" - -#: db/models/fields/__init__.py:114 db/models/fields/__init__.py:265 -#: db/models/fields/__init__.py:542 db/models/fields/__init__.py:553 -#: forms/__init__.py:346 -msgid "This field is required." -msgstr "此欄位是必需的。" - -#: db/models/fields/__init__.py:337 -#, fuzzy -msgid "This value must be an integer." -msgstr "此值必須是 %s 的乘方。" - -#: db/models/fields/__init__.py:369 -#, fuzzy -msgid "This value must be either True or False." -msgstr "此值必須是 %s 的乘方。" - -#: db/models/fields/__init__.py:385 -#, fuzzy -msgid "This field cannot be null." -msgstr "此欄位無效。" - -#: db/models/fields/__init__.py:562 -msgid "Enter a valid filename." -msgstr "輸入有效的檔名。" - -#: db/models/fields/related.py:43 -#, python-format -msgid "Please enter a valid %s." -msgstr "請輸入一個有效的 %s。" - -#: db/models/fields/related.py:579 -#, fuzzy -msgid "Separate multiple IDs with commas." -msgstr " 以逗號分隔多個 ID。" - -#: db/models/fields/related.py:581 -#, fuzzy -msgid "" -"Hold down \"Control\", or \"Command\" on a Mac, to select more than one." -msgstr " 押下 \"Control\" 或 Mac 下的 \"Command\",以進行多重選擇。" - -#: db/models/fields/related.py:625 -#, python-format -msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid." -msgid_plural "" -"Please enter valid %(self)s IDs. The values %(value)r are invalid." -msgstr[0] "請輸入有效的 %(self)s ID。%(value)r 是無效的。" -msgstr[1] "請輸入有效的 %(self)s ID。%(value)r 是無效的。" - -#: forms/__init__.py:380 -#, python-format -msgid "Ensure your text is less than %s character." -msgid_plural "Ensure your text is less than %s characters." -msgstr[0] "確定你的文字少於 %s 個字元。" -msgstr[1] "確定你的文字少於 %s 個字元。" - -#: forms/__init__.py:385 -msgid "Line breaks are not allowed here." -msgstr "此處不允許斷行。" - -#: forms/__init__.py:480 forms/__init__.py:551 forms/__init__.py:589 -#, python-format -msgid "Select a valid choice; '%(data)s' is not in %(choices)s." -msgstr "進行有效的選擇: '%(data)s' 不在 %(choices)s 裡。" - -#: forms/__init__.py:645 -msgid "The submitted file is empty." -msgstr "提交的檔案是空的。" - -#: forms/__init__.py:699 -msgid "Enter a whole number between -32,768 and 32,767." -msgstr "輸入一個介於 -32,768 與 32,767 之間的整數。" - -#: forms/__init__.py:708 -msgid "Enter a positive number." -msgstr "輸入一個正數。" - -#: forms/__init__.py:717 -msgid "Enter a whole number between 0 and 32,767." -msgstr "輸入一個介於 0 與 32,767 之間的整數。" - -#: template/defaultfilters.py:379 -msgid "yes,no,maybe" -msgstr "是,否,也許" - -#~ msgid "Comment" -#~ msgstr "評論" - -#~ msgid "Comments" -#~ msgstr "評論" - -#~ msgid "String (up to 50)" -#~ msgstr "字串 (最長到 50)" - -#~ msgid "label" -#~ msgstr "標示" - -#~ msgid "package" -#~ msgstr "套件" - -#~ msgid "packages" -#~ msgstr "套件" +msgstr "第 \"%(line)s\" 的 \"%(attr)s\" 屬性是無效的值 (行開始於 \"%(start)s\")。" diff --git a/django/contrib/admin/filterspecs.py b/django/contrib/admin/filterspecs.py index 5aa950cbf4..a4f92c986a 100644 --- a/django/contrib/admin/filterspecs.py +++ b/django/contrib/admin/filterspecs.py @@ -9,6 +9,8 @@ certain test -- e.g. being a DateField or ForeignKey. from django.db import models from django.utils.encoding import smart_unicode, iri_to_uri from django.utils.translation import ugettext as _ +from django.utils.html import escape +from django.utils.safestring import mark_safe import datetime class FilterSpec(object): @@ -39,7 +41,7 @@ class FilterSpec(object): def output(self, cl): t = [] if self.has_output(): - t.append(_(u'

        By %s:

        \n
          \n') % self.title()) + t.append(_(u'

          By %s:

          \n
            \n') % escape(self.title())) for choice in self.choices(cl): t.append(u'%s\n' % \ @@ -47,7 +49,7 @@ class FilterSpec(object): iri_to_uri(choice['query_string']), choice['display'])) t.append('
          \n\n') - return "".join(t) + return mark_safe("".join(t)) class RelatedFilterSpec(FilterSpec): def __init__(self, f, request, params, model): diff --git a/django/contrib/admin/models.py b/django/contrib/admin/models.py index ffa2740d5e..23c8661336 100644 --- a/django/contrib/admin/models.py +++ b/django/contrib/admin/models.py @@ -3,6 +3,7 @@ from django.contrib.contenttypes.models import ContentType from django.contrib.auth.models import User from django.utils.translation import ugettext_lazy as _ from django.utils.encoding import smart_unicode +from django.utils.safestring import mark_safe ADDITION = 1 CHANGE = 2 @@ -49,4 +50,4 @@ class LogEntry(models.Model): Returns the admin URL to edit the object represented by this log entry. This is relative to the Django admin index page. """ - return u"%s/%s/%s/" % (self.content_type.app_label, self.content_type.model, self.object_id) + return mark_safe(u"%s/%s/%s/" % (self.content_type.app_label, self.content_type.model, self.object_id)) diff --git a/django/contrib/admin/templates/admin/base_site.html b/django/contrib/admin/templates/admin/base_site.html index 2bc7310873..b867bd29bd 100644 --- a/django/contrib/admin/templates/admin/base_site.html +++ b/django/contrib/admin/templates/admin/base_site.html @@ -1,7 +1,7 @@ {% extends "admin/base.html" %} {% load i18n %} -{% block title %}{{ title|escape }} | {% trans 'Django site admin' %}{% endblock %} +{% block title %}{{ title }} | {% trans 'Django site admin' %}{% endblock %} {% block branding %}

          {% trans 'Django administration' %}

          diff --git a/django/contrib/admin/templates/admin/change_form.html b/django/contrib/admin/templates/admin/change_form.html index 49e709caf6..2e10ec19fd 100644 --- a/django/contrib/admin/templates/admin/change_form.html +++ b/django/contrib/admin/templates/admin/change_form.html @@ -10,8 +10,8 @@ {% block breadcrumbs %}{% if not is_popup %} {% endif %}{% endblock %} {% block content %}
          diff --git a/django/contrib/admin/templates/admin/date_hierarchy.html b/django/contrib/admin/templates/admin/date_hierarchy.html index d2d69616c7..005851051c 100644 --- a/django/contrib/admin/templates/admin/date_hierarchy.html +++ b/django/contrib/admin/templates/admin/date_hierarchy.html @@ -1,9 +1,9 @@ {% if show %}

          diff --git a/django/contrib/admin/templates/admin/delete_confirmation.html b/django/contrib/admin/templates/admin/delete_confirmation.html index ac9634a9e9..f2126882fa 100644 --- a/django/contrib/admin/templates/admin/delete_confirmation.html +++ b/django/contrib/admin/templates/admin/delete_confirmation.html @@ -3,7 +3,7 @@ {% block breadcrumbs %} @@ -13,7 +13,7 @@

          {% blocktrans with object|escape as escaped_object %}Deleting the {{ object_name }} '{{ escaped_object }}' would result in deleting related objects, but your account doesn't have permission to delete the following types of objects:{% endblocktrans %}

            {% for obj in perms_lacking %} -
          • {{ obj|escape }}
          • +
          • {{ obj }}
          • {% endfor %}
          {% else %} diff --git a/django/contrib/admin/templates/admin/edit_inline_stacked.html b/django/contrib/admin/templates/admin/edit_inline_stacked.html index 48ecc698d9..45aa0a4f58 100644 --- a/django/contrib/admin/templates/admin/edit_inline_stacked.html +++ b/django/contrib/admin/templates/admin/edit_inline_stacked.html @@ -1,7 +1,7 @@ {% load admin_modify %}
          {% for fcw in bound_related_object.form_field_collection_wrappers %} -

          {{ bound_related_object.relation.opts.verbose_name|capfirst|escape }} #{{ forloop.counter }}

          +

          {{ bound_related_object.relation.opts.verbose_name|capfirst }} #{{ forloop.counter }}

          {% if bound_related_object.show_url %}{% if fcw.obj.original %}

          View on site

          {% endif %}{% endif %} diff --git a/django/contrib/admin/templates/admin/edit_inline_tabular.html b/django/contrib/admin/templates/admin/edit_inline_tabular.html index 3d059c8b3d..e2dbcbaed2 100644 --- a/django/contrib/admin/templates/admin/edit_inline_tabular.html +++ b/django/contrib/admin/templates/admin/edit_inline_tabular.html @@ -1,10 +1,10 @@ {% load admin_modify %}
          -

          {{ bound_related_object.relation.opts.verbose_name_plural|capfirst|escape }}

          +

          {{ bound_related_object.relation.opts.verbose_name_plural|capfirst }}

          {% for fw in bound_related_object.field_wrapper_list %} {% if fw.needs_header %} - {{ fw.field.verbose_name|capfirst|escape }} + {{ fw.field.verbose_name|capfirst }} {% endif %} {% endfor %} diff --git a/django/contrib/admin/templates/admin/index.html b/django/contrib/admin/templates/admin/index.html index 3626c89cb2..2f406a1754 100644 --- a/django/contrib/admin/templates/admin/index.html +++ b/django/contrib/admin/templates/admin/index.html @@ -19,9 +19,9 @@ {% for model in app.models %} {% if model.perms.change %} - + {% else %} - + {% endif %} {% if model.perms.add %} @@ -58,7 +58,7 @@ {% else %} {% endif %} diff --git a/django/contrib/admin/templates/admin/invalid_setup.html b/django/contrib/admin/templates/admin/invalid_setup.html index 1d7d61f0d2..1fa0d32358 100644 --- a/django/contrib/admin/templates/admin/invalid_setup.html +++ b/django/contrib/admin/templates/admin/invalid_setup.html @@ -1,7 +1,7 @@ {% extends "admin/base_site.html" %} {% load i18n %} -{% block breadcrumbs %}{% endblock %} +{% block breadcrumbs %}{% endblock %} {% block content %} diff --git a/django/contrib/admin/templates/admin/object_history.html b/django/contrib/admin/templates/admin/object_history.html index 34529f69db..00e47259bf 100644 --- a/django/contrib/admin/templates/admin/object_history.html +++ b/django/contrib/admin/templates/admin/object_history.html @@ -1,7 +1,7 @@ {% extends "admin/base_site.html" %} {% load i18n %} {% block breadcrumbs %} - + {% endblock %} {% block content %} @@ -23,8 +23,8 @@ {% for action in action_list %} - - + + {% endfor %} diff --git a/django/contrib/admin/templates/admin/pagination.html b/django/contrib/admin/templates/admin/pagination.html index e1c09b2932..0640a4690b 100644 --- a/django/contrib/admin/templates/admin/pagination.html +++ b/django/contrib/admin/templates/admin/pagination.html @@ -6,6 +6,6 @@ {% paginator_number cl i %} {% endfor %} {% endif %} -{{ cl.result_count }} {% ifequal cl.result_count 1 %}{{ cl.opts.verbose_name|escape }}{% else %}{{ cl.opts.verbose_name_plural|escape }}{% endifequal %} +{{ cl.result_count }} {% ifequal cl.result_count 1 %}{{ cl.opts.verbose_name|escape }}{% else %}{{ cl.opts.verbose_name_plural }}{% endifequal %} {% if show_all_url %}  {% trans 'Show all' %}{% endif %}

          diff --git a/django/contrib/admin/templates/admin_doc/model_detail.html b/django/contrib/admin/templates/admin_doc/model_detail.html index e19e31d891..81bf87db15 100644 --- a/django/contrib/admin/templates/admin_doc/model_detail.html +++ b/django/contrib/admin/templates/admin_doc/model_detail.html @@ -8,16 +8,16 @@ {% endblock %} -{% block breadcrumbs %}{% endblock %} +{% block breadcrumbs %}{% endblock %} -{% block title %}Model: {{ name|escape }}{% endblock %} +{% block title %}Model: {{ name }}{% endblock %} {% block content %}
          -

          {{ summary|escape }}

          +

          {{ summary }}

          {% if description %} -

          {% filter escape|linebreaksbr %}{% trans description %}{% endfilter %}

          +

          {% filter linebreaksbr %}{% trans description %}{% endfilter %}

          {% endif %}
          diff --git a/django/contrib/admin/templates/widget/foreign.html b/django/contrib/admin/templates/widget/foreign.html index 301f5214db..6b43d044bd 100644 --- a/django/contrib/admin/templates/widget/foreign.html +++ b/django/contrib/admin/templates/widget/foreign.html @@ -15,6 +15,6 @@ {{ bound_field.original_value }} {% endif %} {% if bound_field.raw_id_admin %} - {% if bound_field.existing_display %} {{ bound_field.existing_display|truncatewords:"14"|escape }}{% endif %} + {% if bound_field.existing_display %} {{ bound_field.existing_display|truncatewords:"14" }}{% endif %} {% endif %} {% endif %} diff --git a/django/contrib/admin/templates/widget/one_to_one.html b/django/contrib/admin/templates/widget/one_to_one.html index efd0117bf2..a79a12314f 100644 --- a/django/contrib/admin/templates/widget/one_to_one.html +++ b/django/contrib/admin/templates/widget/one_to_one.html @@ -1,2 +1,2 @@ {% if add %}{% include "widget/foreign.html" %}{% endif %} -{% if change %}{% if bound_field.existing_display %} {{ bound_field.existing_display|truncatewords:"14"|escape }}{% endif %}{% endif %} +{% if change %}{% if bound_field.existing_display %} {{ bound_field.existing_display|truncatewords:"14" }}{% endif %}{% endif %} diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py index 0e96f28191..b23013becd 100644 --- a/django/contrib/admin/templatetags/admin_list.py +++ b/django/contrib/admin/templatetags/admin_list.py @@ -4,8 +4,9 @@ from django.contrib.admin.views.main import ORDER_VAR, ORDER_TYPE_VAR, PAGE_VAR, from django.core.exceptions import ObjectDoesNotExist from django.db import models from django.utils import dateformat -from django.utils.html import escape +from django.utils.html import escape, conditional_escape from django.utils.text import capfirst +from django.utils.safestring import mark_safe from django.utils.translation import get_date_formats, get_partial_date_formats, ugettext as _ from django.utils.encoding import smart_unicode, smart_str, force_unicode from django.template import Library @@ -19,9 +20,9 @@ def paginator_number(cl,i): if i == DOT: return u'... ' elif i == cl.page_num: - return u'%d ' % (i+1) + return mark_safe(u'%d ' % (i+1)) else: - return u'%d ' % (cl.get_query_string({PAGE_VAR: i}), (i == cl.paginator.pages-1 and ' class="end"' or ''), i+1) + return mark_safe(u'%d ' % (cl.get_query_string({PAGE_VAR: i}), (i == cl.paginator.pages-1 and ' class="end"' or ''), i+1)) paginator_number = register.simple_tag(paginator_number) def pagination(cl): @@ -117,7 +118,7 @@ def result_headers(cl): def _boolean_icon(field_val): BOOLEAN_MAPPING = {True: 'yes', False: 'no', None: 'unknown'} - return u'%s' % (settings.ADMIN_MEDIA_PREFIX, BOOLEAN_MAPPING[field_val], field_val) + return mark_safe(u'%s' % (settings.ADMIN_MEDIA_PREFIX, BOOLEAN_MAPPING[field_val], field_val)) def items_for_result(cl, result): first = True @@ -193,10 +194,10 @@ def items_for_result(cl, result): # Convert the pk to something that can be used in Javascript. # Problem cases are long ints (23L) and non-ASCII strings. result_id = repr(force_unicode(getattr(result, pk)))[1:] - yield (u'<%s%s>%s' % \ - (table_tag, row_class, url, (cl.is_popup and ' onclick="opener.dismissRelatedLookupPopup(window, %s); return false;"' % result_id or ''), result_repr, table_tag)) + yield mark_safe(u'<%s%s>%s' % \ + (table_tag, row_class, url, (cl.is_popup and ' onclick="opener.dismissRelatedLookupPopup(window, %s); return false;"' % result_id or ''), conditional_escape(result_repr), table_tag)) else: - yield (u'%s' % (row_class, result_repr)) + yield mark_safe(u'%s' % (row_class, conditional_escape(result_repr))) def results(cl): for res in cl.result_list: @@ -220,7 +221,7 @@ def date_hierarchy(cl): day_lookup = cl.params.get(day_field) year_month_format, month_day_format = get_partial_date_formats() - link = lambda d: cl.get_query_string(d, [field_generic]) + link = lambda d: mark_safe(cl.get_query_string(d, [field_generic])) if year_lookup and month_lookup and day_lookup: day = datetime.date(int(year_lookup), int(month_lookup), int(day_lookup)) diff --git a/django/contrib/admin/templatetags/admin_modify.py b/django/contrib/admin/templatetags/admin_modify.py index 9ce6aa77ae..e5f31ba723 100644 --- a/django/contrib/admin/templatetags/admin_modify.py +++ b/django/contrib/admin/templatetags/admin_modify.py @@ -3,6 +3,8 @@ from django.contrib.admin.views.main import AdminBoundField from django.template import loader from django.utils.text import capfirst from django.utils.encoding import force_unicode +from django.utils.safestring import mark_safe +from django.utils.html import escape from django.db import models from django.db.models.fields import Field from django.db.models.related import BoundRelatedObject @@ -32,7 +34,8 @@ def include_admin_script(script_path): """ if not absolute_url_re.match(script_path): script_path = '%s%s' % (settings.ADMIN_MEDIA_PREFIX, script_path) - return u'' % script_path + return mark_safe(u'' + % script_path) include_admin_script = register.simple_tag(include_admin_script) def submit_row(context): @@ -63,8 +66,10 @@ def field_label(bound_field): class_names.append('inline') colon = ":" class_str = class_names and u' class="%s"' % u' '.join(class_names) or u'' - return u' ' % (bound_field.element_id, class_str, \ - force_unicode(capfirst(bound_field.field.verbose_name)), colon) + return mark_safe(u' ' % + (bound_field.element_id, class_str, + escape(force_unicode(capfirst(bound_field.field.verbose_name))), + colon)) field_label = register.simple_tag(field_label) class FieldWidgetNode(template.Node): @@ -193,15 +198,15 @@ def auto_populated_field_script(auto_pop_fields, change = False): ' var e = document.getElementById("id_%s");' \ ' if(!e._changed) { e.value = URLify(%s, %s);} }; ' % ( f, field.name, add_values, field.max_length)) - return u''.join(t) + return mark_safe(u''.join(t)) auto_populated_field_script = register.simple_tag(auto_populated_field_script) def filter_interface_script_maybe(bound_field): f = bound_field.field if f.rel and isinstance(f.rel, models.ManyToManyRel) and f.rel.filter_interface: - return u'\n' % ( - f.name, f.verbose_name.replace('"', '\\"'), f.rel.filter_interface-1, settings.ADMIN_MEDIA_PREFIX) + f.name, escape(f.verbose_name.replace('"', '\\"')), f.rel.filter_interface-1, settings.ADMIN_MEDIA_PREFIX)) else: return '' filter_interface_script_maybe = register.simple_tag(filter_interface_script_maybe) diff --git a/django/contrib/admin/templatetags/adminapplist.py b/django/contrib/admin/templatetags/adminapplist.py index 673b77a1e0..bf394e2099 100644 --- a/django/contrib/admin/templatetags/adminapplist.py +++ b/django/contrib/admin/templatetags/adminapplist.py @@ -1,6 +1,7 @@ from django import template from django.db.models import get_models from django.utils.encoding import force_unicode +from django.utils.safestring import mark_safe register = template.Library() @@ -38,7 +39,7 @@ class AdminApplistNode(template.Node): if True in perms.values(): model_list.append({ 'name': force_unicode(capfirst(m._meta.verbose_name_plural)), - 'admin_url': u'%s/%s/' % (force_unicode(app_label), m.__name__.lower()), + 'admin_url': mark_safe(u'%s/%s/' % (force_unicode(app_label), m.__name__.lower())), 'perms': perms, }) diff --git a/django/contrib/admin/utils.py b/django/contrib/admin/utils.py index 9adf09b6a5..4a45a622b2 100644 --- a/django/contrib/admin/utils.py +++ b/django/contrib/admin/utils.py @@ -3,6 +3,7 @@ import re from email.Parser import HeaderParser from email.Errors import HeaderParseError +from django.utils.safestring import mark_safe try: import docutils.core import docutils.nodes @@ -66,7 +67,7 @@ def parse_rst(text, default_reference_context, thing_being_parsed=None, link_bas parts = docutils.core.publish_parts(text, source_path=thing_being_parsed, destination_path=None, writer_name='html', settings_overrides=overrides) - return parts['fragment'] + return mark_safe(parts['fragment']) # # reST roles diff --git a/django/contrib/admin/views/decorators.py b/django/contrib/admin/views/decorators.py index 9ae0170b54..4f6662c970 100644 --- a/django/contrib/admin/views/decorators.py +++ b/django/contrib/admin/views/decorators.py @@ -4,6 +4,7 @@ from django.contrib.auth.models import User from django.contrib.auth import authenticate, login from django.shortcuts import render_to_response from django.utils.translation import ugettext_lazy, ugettext as _ +from django.utils.safestring import mark_safe import base64, datetime, md5 import cPickle as pickle @@ -22,7 +23,7 @@ def _display_login_form(request, error_message=''): post_data = _encode_post_data({}) return render_to_response('admin/login.html', { 'title': _('Log in'), - 'app_path': request.path, + 'app_path': mark_safe(request.path), 'post_data': post_data, 'error_message': error_message }, context_instance=template.RequestContext(request)) diff --git a/django/contrib/admin/views/doc.py b/django/contrib/admin/views/doc.py index 2ebd0cd282..44a27d6cc3 100644 --- a/django/contrib/admin/views/doc.py +++ b/django/contrib/admin/views/doc.py @@ -10,6 +10,7 @@ from django.core import urlresolvers from django.contrib.admin import utils from django.contrib.sites.models import Site from django.utils.translation import ugettext as _ +from django.utils.safestring import mark_safe import inspect, os, re # Exclude methods starting with these strings from documentation @@ -29,7 +30,7 @@ def bookmarklets(request): # Hack! This couples this view to the URL it lives at. admin_root = request.path[:-len('doc/bookmarklets/')] return render_to_response('admin_doc/bookmarklets.html', { - 'admin_url': "%s://%s%s" % (request.is_secure() and 'https' or 'http', request.get_host(), admin_root), + 'admin_url': mark_safe("%s://%s%s" % (request.is_secure() and 'https' or 'http', request.get_host(), admin_root)), }, context_instance=RequestContext(request)) bookmarklets = staff_member_required(bookmarklets) diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py index 7fa870e222..fa314803a3 100644 --- a/django/contrib/admin/views/main.py +++ b/django/contrib/admin/views/main.py @@ -14,6 +14,7 @@ from django.utils.html import escape from django.utils.text import capfirst, get_text_list from django.utils.encoding import force_unicode, smart_str from django.utils.translation import ugettext as _ +from django.utils.safestring import mark_safe import operator try: @@ -136,7 +137,9 @@ class AdminBoundField(object): self._repr_filled = False if field.rel: - self.related_url = u'../../../%s/%s/' % (field.rel.to._meta.app_label, field.rel.to._meta.object_name.lower()) + self.related_url = mark_safe(u'../../../%s/%s/' + % (field.rel.to._meta.app_label, + field.rel.to._meta.object_name.lower())) def original_value(self): if self.original: @@ -216,7 +219,7 @@ def render_change_form(model, manipulator, context, add=False, change=False, for 'javascript_imports': get_javascript_imports(opts, auto_populated_fields, field_sets), 'ordered_objects': ordered_objects, 'inline_related_objects': inline_related_objects, - 'form_url': form_url, + 'form_url': mark_safe(form_url), 'opts': opts, 'content_type_id': ContentType.objects.get_for_model(model).id, } @@ -436,12 +439,14 @@ def _get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current if related.field.rel.edit_inline or not related.opts.admin: # Don't display link to edit, because it either has no # admin or is edited inline. - nh(deleted_objects, current_depth, [u'%s: %s' % (force_unicode(capfirst(related.opts.verbose_name)), sub_obj), []]) + nh(deleted_objects, current_depth, [mark_safe(u'%s: %s' % (force_unicode(capfirst(related.opts.verbose_name)), sub_obj)), []]) else: # Display a link to the admin page. - nh(deleted_objects, current_depth, [u'%s: %s' % \ - (force_unicode(capfirst(related.opts.verbose_name)), related.opts.app_label, related.opts.object_name.lower(), - sub_obj._get_pk_val(), sub_obj), []]) + nh(deleted_objects, current_depth, [mark_safe(u'%s: %s' % + (escape(force_unicode(capfirst(related.opts.verbose_name))), + related.opts.app_label, + related.opts.object_name.lower(), + sub_obj._get_pk_val(), sub_obj)), []]) _get_deleted_objects(deleted_objects, perms_needed, user, sub_obj, related.opts, current_depth+2) else: has_related_objs = False @@ -453,8 +458,8 @@ def _get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current nh(deleted_objects, current_depth, [u'%s: %s' % (force_unicode(capfirst(related.opts.verbose_name)), escape(sub_obj)), []]) else: # Display a link to the admin page. - nh(deleted_objects, current_depth, [u'%s: %s' % \ - (force_unicode(capfirst(related.opts.verbose_name)), related.opts.app_label, related.opts.object_name.lower(), sub_obj._get_pk_val(), escape(sub_obj)), []]) + nh(deleted_objects, current_depth, [mark_safe(u'%s: %s' % \ + (escape(force_unicode(capfirst(related.opts.verbose_name))), related.opts.app_label, related.opts.object_name.lower(), sub_obj._get_pk_val(), escape(sub_obj))), []]) _get_deleted_objects(deleted_objects, perms_needed, user, sub_obj, related.opts, current_depth+2) # If there were related objects, and the user doesn't have # permission to delete them, add the missing perm to perms_needed. @@ -485,9 +490,9 @@ def _get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current else: # Display a link to the admin page. nh(deleted_objects, current_depth, [ - (_('One or more %(fieldname)s in %(name)s:') % {'fieldname': force_unicode(related.field.verbose_name), 'name': force_unicode(related.opts.verbose_name)}) + \ + mark_safe((_('One or more %(fieldname)s in %(name)s:') % {'fieldname': escape(force_unicode(related.field.verbose_name)), 'name': escape(force_unicode(related.opts.verbose_name))}) + \ (u' %s' % \ - (related.opts.app_label, related.opts.module_name, sub_obj._get_pk_val(), escape(sub_obj))), []]) + (related.opts.app_label, related.opts.module_name, sub_obj._get_pk_val(), escape(sub_obj)))), []]) # If there were related objects, and the user doesn't have # permission to change them, add the missing perm to perms_needed. if related.opts.admin and has_related_objs: @@ -507,7 +512,7 @@ def delete_stage(request, app_label, model_name, object_id): # Populate deleted_objects, a data structure of all related objects that # will also be deleted. - deleted_objects = [u'%s: %s' % (force_unicode(capfirst(opts.verbose_name)), force_unicode(object_id), escape(obj)), []] + deleted_objects = [mark_safe(u'%s: %s' % (escape(force_unicode(capfirst(opts.verbose_name))), force_unicode(object_id), escape(obj))), []] perms_needed = set() _get_deleted_objects(deleted_objects, perms_needed, request.user, obj, opts, 1) @@ -604,7 +609,7 @@ class ChangeList(object): del p[k] elif v is not None: p[k] = v - return '?' + '&'.join([u'%s=%s' % (k, v) for k, v in p.items()]).replace(' ', '%20') + return mark_safe('?' + '&'.join([u'%s=%s' % (k, v) for k, v in p.items()]).replace(' ', '%20')) def get_results(self, request): paginator = ObjectPaginator(self.query_set, self.lookup_opts.admin.list_per_page) diff --git a/django/contrib/auth/decorators.py b/django/contrib/auth/decorators.py index b6481ea52c..f3f7f530ef 100644 --- a/django/contrib/auth/decorators.py +++ b/django/contrib/auth/decorators.py @@ -8,19 +8,9 @@ def user_passes_test(test_func, login_url=None, redirect_field_name=REDIRECT_FIE redirecting to the log-in page if necessary. The test should be a callable that takes the user object and returns True if the user passes. """ - if not login_url: - from django.conf import settings - login_url = settings.LOGIN_URL - def _dec(view_func): - def _checklogin(request, *args, **kwargs): - if test_func(request.user): - return view_func(request, *args, **kwargs) - return HttpResponseRedirect('%s?%s=%s' % (login_url, redirect_field_name, urlquote(request.get_full_path()))) - _checklogin.__doc__ = view_func.__doc__ - _checklogin.__dict__ = view_func.__dict__ - - return _checklogin - return _dec + def decorate(view_func): + return _CheckLogin(view_func, test_func, login_url, redirect_field_name) + return decorate def login_required(function=None, redirect_field_name=REDIRECT_FIELD_NAME): """ @@ -42,3 +32,34 @@ def permission_required(perm, login_url=None): """ return user_passes_test(lambda u: u.has_perm(perm), login_url=login_url) +class _CheckLogin(object): + """ + Class that checks that the user passes the given test, redirecting to + the log-in page if necessary. If the test is passed, the view function + is invoked. The test should be a callable that takes the user object + and returns True if the user passes. + + We use a class here so that we can define __get__. This way, when a + _CheckLogin object is used as a method decorator, the view function + is properly bound to its instance. + """ + def __init__(self, view_func, test_func, login_url=None, redirect_field_name=REDIRECT_FIELD_NAME): + if not login_url: + from django.conf import settings + login_url = settings.LOGIN_URL + self.view_func = view_func + self.test_func = test_func + self.login_url = login_url + self.redirect_field_name = redirect_field_name + self.__name__ = view_func.__name__ + + def __get__(self, obj, cls=None): + view_func = self.view_func.__get__(obj, cls) + return _CheckLogin(view_func, self.test_func, self.login_url, self.redirect_field_name) + + def __call__(self, request, *args, **kwargs): + if self.test_func(request.user): + return self.view_func(request, *args, **kwargs) + path = urlquote(request.get_full_path()) + tup = self.login_url, self.redirect_field_name, path + return HttpResponseRedirect('%s?%s=%s' % tup) diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py index 16ee0289a2..47a974cacd 100644 --- a/django/contrib/auth/forms.py +++ b/django/contrib/auth/forms.py @@ -104,7 +104,7 @@ class PasswordResetForm(oldforms.Manipulator): 'site_name': site_name, 'user': user, } - send_mail('Password reset on %s' % site_name, t.render(Context(c)), None, [user.email]) + send_mail(_('Password reset on %s') % site_name, t.render(Context(c)), None, [user.email]) class PasswordChangeForm(oldforms.Manipulator): "A form that lets a user change his password." diff --git a/django/contrib/csrf/middleware.py b/django/contrib/csrf/middleware.py index 1ef0e6cefd..1a75a5d6ab 100644 --- a/django/contrib/csrf/middleware.py +++ b/django/contrib/csrf/middleware.py @@ -7,11 +7,12 @@ against request forgeries from other sites. """ from django.conf import settings from django.http import HttpResponseForbidden +from django.utils.safestring import mark_safe import md5 import re import itertools -_ERROR_MSG = '

          403 Forbidden

          Cross Site Request Forgery detected. Request aborted.

          ' +_ERROR_MSG = mark_safe('

          403 Forbidden

          Cross Site Request Forgery detected. Request aborted.

          ') _POST_FORM_RE = \ re.compile(r'(]*\bmethod=(\'|"|)POST(\'|"|)\b[^>]*>)', re.IGNORECASE) @@ -82,10 +83,10 @@ class CsrfMiddleware(object): itertools.repeat('')) def add_csrf_field(match): """Returns the matched
          tag plus the added element""" - return match.group() + "
          " + \ + return mark_safe(match.group() + "
          " + \ "
          " + "' />
          ") # Modify any POST forms response.content = _POST_FORM_RE.sub(add_csrf_field, response.content) diff --git a/django/contrib/databrowse/datastructures.py b/django/contrib/databrowse/datastructures.py index 0a26db18b1..4b0947eb34 100644 --- a/django/contrib/databrowse/datastructures.py +++ b/django/contrib/databrowse/datastructures.py @@ -8,6 +8,7 @@ from django.utils import dateformat from django.utils.text import capfirst from django.utils.translation import get_date_formats from django.utils.encoding import smart_unicode, smart_str, iri_to_uri +from django.utils.safestring import mark_safe from django.db.models.query import QuerySet EMPTY_VALUE = '(None)' @@ -28,7 +29,7 @@ class EasyModel(object): return self.site.registry[self.model] def url(self): - return '%s%s/%s/' % (self.site.root_url, self.model._meta.app_label, self.model._meta.module_name) + return mark_safe('%s%s/%s/' % (self.site.root_url, self.model._meta.app_label, self.model._meta.module_name)) def objects(self, **kwargs): return self.get_query_set().filter(**kwargs) @@ -68,9 +69,9 @@ class EasyField(object): def url(self): if self.field.choices: - return '%s%s/%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name, self.field.name) + return mark_safe('%s%s/%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name, self.field.name)) elif self.field.rel: - return '%s%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name) + return mark_safe('%s%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name)) class EasyChoice(object): def __init__(self, easy_model, field, value, label): @@ -81,7 +82,7 @@ class EasyChoice(object): return smart_str(u'' % (self.model.model._meta.object_name, self.field.name)) def url(self): - return '%s%s/%s/%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name, self.field.field.name, iri_to_uri(self.value)) + return mark_safe('%s%s/%s/%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name, self.field.field.name, iri_to_uri(self.value))) class EasyInstance(object): def __init__(self, easy_model, instance): @@ -184,14 +185,14 @@ class EasyInstanceField(object): if self.field.rel.to in self.model.model_list: lst = [] for value in self.values(): - url = '%s%s/%s/objects/%s/' % (self.model.site.root_url, m.model._meta.app_label, m.model._meta.module_name, iri_to_uri(value._get_pk_val())) + url = mark_safe('%s%s/%s/objects/%s/' % (self.model.site.root_url, m.model._meta.app_label, m.model._meta.module_name, iri_to_uri(value._get_pk_val()))) lst.append((smart_unicode(value), url)) else: lst = [(value, None) for value in self.values()] elif self.field.choices: lst = [] for value in self.values(): - url = '%s%s/%s/fields/%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name, self.field.name, iri_to_uri(self.raw_value)) + url = mark_safe('%s%s/%s/fields/%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name, self.field.name, iri_to_uri(self.raw_value))) lst.append((value, url)) elif isinstance(self.field, models.URLField): val = self.values()[0] diff --git a/django/contrib/databrowse/plugins/calendars.py b/django/contrib/databrowse/plugins/calendars.py index 1b1a197290..5d5a020e6d 100644 --- a/django/contrib/databrowse/plugins/calendars.py +++ b/django/contrib/databrowse/plugins/calendars.py @@ -5,8 +5,9 @@ from django.contrib.databrowse.sites import DatabrowsePlugin from django.shortcuts import render_to_response from django.utils.text import capfirst from django.utils.translation import get_date_formats -from django.views.generic import date_based from django.utils.encoding import force_unicode +from django.utils.safestring import mark_safe +from django.views.generic import date_based import datetime import time @@ -29,16 +30,17 @@ class CalendarPlugin(DatabrowsePlugin): fields = self.field_dict(model) if not fields: return u'' - return u'

          View calendar by: %s

          ' % \ - u', '.join(['%s' % (f.name, force_unicode(capfirst(f.verbose_name))) for f in fields.values()]) + return mark_safe(u'

          View calendar by: %s

          ' % \ + u', '.join(['%s' % (f.name, force_unicode(capfirst(f.verbose_name))) for f in fields.values()])) def urls(self, plugin_name, easy_instance_field): if isinstance(easy_instance_field.field, models.DateField): - return [u'%s%s/%s/%s/%s/%s/' % (easy_instance_field.model.url(), + return [mark_safe(u'%s%s/%s/%s/%s/%s/' % ( + easy_instance_field.model.url(), plugin_name, easy_instance_field.field.name, easy_instance_field.raw_value.year, easy_instance_field.raw_value.strftime('%b').lower(), - easy_instance_field.raw_value.day)] + easy_instance_field.raw_value.day))] def model_view(self, request, model_databrowse, url): self.model, self.site = model_databrowse.model, model_databrowse.site diff --git a/django/contrib/databrowse/plugins/fieldchoices.py b/django/contrib/databrowse/plugins/fieldchoices.py index a5dd07c639..dea6bac700 100644 --- a/django/contrib/databrowse/plugins/fieldchoices.py +++ b/django/contrib/databrowse/plugins/fieldchoices.py @@ -5,6 +5,7 @@ from django.contrib.databrowse.sites import DatabrowsePlugin from django.shortcuts import render_to_response from django.utils.text import capfirst from django.utils.encoding import smart_str, force_unicode +from django.utils.safestring import mark_safe from django.views.generic import date_based import datetime import time @@ -32,15 +33,16 @@ class FieldChoicePlugin(DatabrowsePlugin): fields = self.field_dict(model) if not fields: return u'' - return u'

          View by: %s

          ' % \ - u', '.join(['%s' % (f.name, force_unicode(capfirst(f.verbose_name))) for f in fields.values()]) + return mark_safe(u'

          View by: %s

          ' % \ + u', '.join(['%s' % (f.name, force_unicode(capfirst(f.verbose_name))) for f in fields.values()])) def urls(self, plugin_name, easy_instance_field): if easy_instance_field.field in self.field_dict(easy_instance_field.model.model).values(): field_value = smart_str(easy_instance_field.raw_value) - return [u'%s%s/%s/%s/' % (easy_instance_field.model.url(), + return [mark_safe(u'%s%s/%s/%s/' % ( + easy_instance_field.model.url(), plugin_name, easy_instance_field.field.name, - urllib.quote(field_value, safe=''))] + urllib.quote(field_value, safe='')))] def model_view(self, request, model_databrowse, url): self.model, self.site = model_databrowse.model, model_databrowse.site diff --git a/django/contrib/databrowse/sites.py b/django/contrib/databrowse/sites.py index c07ec2129b..5d8c1c8b49 100644 --- a/django/contrib/databrowse/sites.py +++ b/django/contrib/databrowse/sites.py @@ -2,6 +2,7 @@ from django import http from django.db import models from django.contrib.databrowse.datastructures import EasyModel, EasyChoice from django.shortcuts import render_to_response +from django.utils.safestring import mark_safe class AlreadyRegistered(Exception): pass @@ -60,7 +61,7 @@ class ModelDatabrowse(object): def main_view(self, request): easy_model = EasyModel(self.site, self.model) - html_snippets = u'\n'.join([p.model_index_html(request, self.model, self.site) for p in self.plugins.values()]) + html_snippets = mark_safe(u'\n'.join([p.model_index_html(request, self.model, self.site) for p in self.plugins.values()])) return render_to_response('databrowse/model_detail.html', { 'model': easy_model, 'root_url': self.site.root_url, diff --git a/django/contrib/flatpages/views.py b/django/contrib/flatpages/views.py index f386a52101..1e451bdecc 100644 --- a/django/contrib/flatpages/views.py +++ b/django/contrib/flatpages/views.py @@ -4,6 +4,7 @@ from django.shortcuts import get_object_or_404 from django.http import HttpResponse from django.conf import settings from django.core.xheaders import populate_xheaders +from django.utils.safestring import mark_safe DEFAULT_TEMPLATE = 'flatpages/default.html' @@ -30,6 +31,13 @@ def flatpage(request, url): t = loader.select_template((f.template_name, DEFAULT_TEMPLATE)) else: t = loader.get_template(DEFAULT_TEMPLATE) + + # To avoid having to always use the "|safe" filter in flatpage templates, + # mark the title and content as already safe (since they are raw HTML + # content in the first place). + f.title = mark_safe(f.title) + f.content = mark_safe(f.content) + c = RequestContext(request, { 'flatpage': f, }) diff --git a/django/contrib/humanize/templatetags/humanize.py b/django/contrib/humanize/templatetags/humanize.py index 19591606f9..4aa5a32e3f 100644 --- a/django/contrib/humanize/templatetags/humanize.py +++ b/django/contrib/humanize/templatetags/humanize.py @@ -21,6 +21,7 @@ def ordinal(value): if value % 100 in (11, 12, 13): # special case return u"%d%s" % (value, t[0]) return u'%d%s' % (value, t[value % 10]) +ordinal.is_safe = True register.filter(ordinal) def intcomma(value): @@ -34,6 +35,7 @@ def intcomma(value): return new else: return intcomma(new) +intcomma.is_safe = True register.filter(intcomma) def intword(value): @@ -55,6 +57,7 @@ def intword(value): new_value = value / 1000000000000.0 return ungettext('%(value).1f trillion', '%(value).1f trillion', new_value) % {'value': new_value} return value +intword.is_safe = False register.filter(intword) def apnumber(value): @@ -69,6 +72,7 @@ def apnumber(value): if not 0 < value < 10: return value return (_('one'), _('two'), _('three'), _('four'), _('five'), _('six'), _('seven'), _('eight'), _('nine'))[value-1] +apnumber.is_safe = True register.filter(apnumber) def naturalday(value, arg=None): diff --git a/django/contrib/markup/templatetags/markup.py b/django/contrib/markup/templatetags/markup.py index 5d1f0ff1fb..13708fd26d 100644 --- a/django/contrib/markup/templatetags/markup.py +++ b/django/contrib/markup/templatetags/markup.py @@ -17,6 +17,7 @@ silently fail and return the un-marked-up text. from django import template from django.conf import settings from django.utils.encoding import smart_str, force_unicode +from django.utils.safestring import mark_safe register = template.Library() @@ -28,7 +29,8 @@ def textile(value): raise template.TemplateSyntaxError, "Error in {% textile %} filter: The Python textile library isn't installed." return force_unicode(value) else: - return force_unicode(textile.textile(smart_str(value), encoding='utf-8', output='utf-8')) + return mark_safe(force_unicode(textile.textile(smart_str(value), encoding='utf-8', output='utf-8'))) +textile.is_safe = True def markdown(value): try: @@ -38,7 +40,8 @@ def markdown(value): raise template.TemplateSyntaxError, "Error in {% markdown %} filter: The Python markdown library isn't installed." return force_unicode(value) else: - return force_unicode(markdown.markdown(smart_str(value))) + return mark_safe(force_unicode(markdown.markdown(smart_str(value)))) +markdown.is_safe = True def restructuredtext(value): try: @@ -50,7 +53,8 @@ def restructuredtext(value): else: docutils_settings = getattr(settings, "RESTRUCTUREDTEXT_FILTER_SETTINGS", {}) parts = publish_parts(source=smart_str(value), writer_name="html4css1", settings_overrides=docutils_settings) - return force_unicode(parts["fragment"]) + return mark_safe(force_unicode(parts["fragment"])) +restructuredtext.is_safe = True register.filter(textile) register.filter(markdown) diff --git a/django/contrib/markup/tests.py b/django/contrib/markup/tests.py index bd3f52b9dd..6e7beaeb2f 100644 --- a/django/contrib/markup/tests.py +++ b/django/contrib/markup/tests.py @@ -1,9 +1,11 @@ # Quick tests for the markup templatetags (django.contrib.markup) -from django.template import Template, Context, add_to_builtins import re import unittest +from django.template import Template, Context, add_to_builtins +from django.utils.html import escape + add_to_builtins('django.contrib.markup.templatetags.markup') class Templates(unittest.TestCase): @@ -24,7 +26,7 @@ Paragraph 2 with "quotes" and @code@"""

          Paragraph 2 with “quotes” and code

          """) else: - self.assertEqual(rendered, textile_content) + self.assertEqual(rendered, escape(textile_content)) def test_markdown(self): try: diff --git a/django/contrib/sessions/middleware.py b/django/contrib/sessions/middleware.py index 7b6c826805..2af2312e76 100644 --- a/django/contrib/sessions/middleware.py +++ b/django/contrib/sessions/middleware.py @@ -1,8 +1,8 @@ +import time + from django.conf import settings from django.utils.cache import patch_vary_headers -from email.Utils import formatdate -import datetime -import time +from django.utils.http import cookie_date TEST_COOKIE_NAME = 'testcookie' TEST_COOKIE_VALUE = 'worked' @@ -10,8 +10,9 @@ TEST_COOKIE_VALUE = 'worked' class SessionMiddleware(object): def process_request(self, request): - engine = __import__(settings.SESSION_ENGINE, {}, {}, ['']) - request.session = engine.SessionStore(request.COOKIES.get(settings.SESSION_COOKIE_NAME, None)) + engine = __import__(settings.SESSION_ENGINE, {}, {}, ['']) + session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME, None) + request.session = engine.SessionStore(session_key) def process_response(self, request, response): # If request.session was modified, or if response.session was set, save @@ -30,13 +31,8 @@ class SessionMiddleware(object): expires = None else: max_age = settings.SESSION_COOKIE_AGE - rfcdate = formatdate(time.time() + settings.SESSION_COOKIE_AGE) - - # Fixed length date must have '-' separation in the format - # DD-MMM-YYYY for compliance with Netscape cookie standard - expires = datetime.datetime.strftime(datetime.datetime.utcnow() + \ - datetime.timedelta(seconds=settings.SESSION_COOKIE_AGE), "%a, %d-%b-%Y %H:%M:%S GMT") - + expires_time = time.time() + settings.SESSION_COOKIE_AGE + expires = cookie_date(expires_time) # Save the seesion data and refresh the client cookie. request.session.save() response.set_cookie(settings.SESSION_COOKIE_NAME, diff --git a/django/contrib/sitemaps/templates/sitemap.xml b/django/contrib/sitemaps/templates/sitemap.xml index 16d9a0bbe0..c0d529d62e 100644 --- a/django/contrib/sitemaps/templates/sitemap.xml +++ b/django/contrib/sitemaps/templates/sitemap.xml @@ -1,4 +1,4 @@ - +{% autoescape off %} {% spaceless %} {% for url in urlset %} @@ -11,3 +11,4 @@ {% endfor %} {% endspaceless %} +{% endautoescape %} diff --git a/django/contrib/sitemaps/templates/sitemap_index.xml b/django/contrib/sitemaps/templates/sitemap_index.xml index a2bcce85dc..497ae8bb81 100644 --- a/django/contrib/sitemaps/templates/sitemap_index.xml +++ b/django/contrib/sitemaps/templates/sitemap_index.xml @@ -1,4 +1,5 @@ - +{% autoescape off %} {% for location in sitemaps %}{{ location|escape }}{% endfor %} +{% endautoescape %} diff --git a/django/core/handlers/base.py b/django/core/handlers/base.py index 13c7f4193f..1796cae8ea 100644 --- a/django/core/handlers/base.py +++ b/django/core/handlers/base.py @@ -4,6 +4,10 @@ from django import http import sys class BaseHandler(object): + # Changes that are always applied to a response (in this order). + response_fixes = [http.fix_location_header, + http.conditional_content_removal] + def __init__(self): self._request_middleware = self._view_middleware = self._response_middleware = self._exception_middleware = None @@ -50,10 +54,6 @@ class BaseHandler(object): def get_response(self, request): "Returns an HttpResponse object for the given HttpRequest" - response = self._real_get_response(request) - return fix_location_header(request, response) - - def _real_get_response(self, request): from django.core import exceptions, urlresolvers from django.core.mail import mail_admins from django.conf import settings @@ -134,15 +134,13 @@ class BaseHandler(object): import traceback return '\n'.join(traceback.format_exception(*(exc_info or sys.exc_info()))) -def fix_location_header(request, response): - """ - Ensure that we always use an absolute URI in any location header in the - response. This is required by RFC 2616, section 14.30. - - Code constructing response objects is free to insert relative paths and - this function converts them to absolute paths. - """ - if 'Location' in response and request.get_host(): - response['Location'] = request.build_absolute_uri(response['Location']) - return response + def apply_response_fixes(self, request, response): + """ + Applies each of the functions in self.response_fixes to the request and + response, modifying the response in the process. Returns the new + response. + """ + for func in self.response_fixes: + response = func(request, response) + return response diff --git a/django/core/handlers/modpython.py b/django/core/handlers/modpython.py index 2a3e03f3dd..e81a65be4d 100644 --- a/django/core/handlers/modpython.py +++ b/django/core/handlers/modpython.py @@ -162,6 +162,7 @@ class ModPythonHandler(BaseHandler): # Apply response middleware for middleware_method in self._response_middleware: response = middleware_method(request, response) + response = self.apply_response_fixes(request, response) finally: dispatcher.send(signal=signals.request_finished) diff --git a/django/core/handlers/wsgi.py b/django/core/handlers/wsgi.py index d06eee73f2..94575ca369 100644 --- a/django/core/handlers/wsgi.py +++ b/django/core/handlers/wsgi.py @@ -207,6 +207,7 @@ class WSGIHandler(BaseHandler): # Apply response middleware for middleware_method in self._response_middleware: response = middleware_method(request, response) + response = self.apply_response_fixes(request, response) finally: dispatcher.send(signal=signals.request_finished) @@ -220,3 +221,4 @@ class WSGIHandler(BaseHandler): response_headers.append(('Set-Cookie', str(c.output(header='')))) start_response(status, response_headers) return response + diff --git a/django/core/management/__init__.py b/django/core/management/__init__.py index f706fa3c7e..dce2fd493d 100644 --- a/django/core/management/__init__.py +++ b/django/core/management/__init__.py @@ -242,6 +242,8 @@ def setup_environ(settings_mod): """ Configures the runtime environment. This can also be used by external scripts wanting to set up a similar environment to manage.py. + Returns the project directory (assuming the passed settings module is + directly in the project directory). """ # Add this project to sys.path so that it's importable in the conventional # way. For example, if this file (manage.py) lives in a directory diff --git a/django/core/management/commands/startapp.py b/django/core/management/commands/startapp.py index c238e74a08..a3d517dd7a 100644 --- a/django/core/management/commands/startapp.py +++ b/django/core/management/commands/startapp.py @@ -1,8 +1,10 @@ -from django.core.management.base import copy_helper, CommandError, LabelCommand import os +from django.core.management.base import copy_helper, CommandError, LabelCommand + class Command(LabelCommand): - help = "Creates a Django app directory structure for the given app name in the current directory." + help = ("Creates a Django app directory structure for the given app name" + " in the current directory.") args = "[appname]" label = 'application name' @@ -14,17 +16,18 @@ class Command(LabelCommand): def handle_label(self, app_name, directory=None, **options): if directory is None: directory = os.getcwd() - # Determine the project_name a bit naively -- by looking at the name of - # the parent directory. - project_dir = os.path.normpath(os.path.join(directory, os.pardir)) - parent_dir = os.path.basename(project_dir) + # Determine the project_name by using the basename of directory, + # which should be the full path of the project directory (or the + # current directory if no directory was passed). project_name = os.path.basename(directory) if app_name == project_name: - raise CommandError("You cannot create an app with the same name (%r) as your project." % app_name) - copy_helper(self.style, 'app', app_name, directory, parent_dir) + raise CommandError("You cannot create an app with the same name" + " (%r) as your project." % app_name) + copy_helper(self.style, 'app', app_name, directory, project_name) class ProjectCommand(Command): - help = "Creates a Django app directory structure for the given app name in this project's directory." + help = ("Creates a Django app directory structure for the given app name" + " in this project's directory.") def __init__(self, project_directory): super(ProjectCommand, self).__init__() diff --git a/django/core/management/sql.py b/django/core/management/sql.py index d69d83e44e..49f4a80732 100644 --- a/django/core/management/sql.py +++ b/django/core/management/sql.py @@ -252,6 +252,7 @@ def sql_model_create(model, style, known_models=set()): table_output = [] pending_references = {} qn = connection.ops.quote_name + inline_references = connection.features.inline_fk_references for f in opts.fields: col_type = f.db_type() tablespace = f.db_tablespace or opts.db_tablespace @@ -272,7 +273,7 @@ def sql_model_create(model, style, known_models=set()): # won't be generating a CREATE INDEX statement for this field. field_output.append(connection.ops.tablespace_sql(tablespace, inline=True)) if f.rel: - if f.rel.to in known_models: + if inline_references and f.rel.to in known_models: field_output.append(style.SQL_KEYWORD('REFERENCES') + ' ' + \ style.SQL_TABLE(qn(f.rel.to._meta.db_table)) + ' (' + \ style.SQL_FIELD(qn(f.rel.to._meta.get_field(f.rel.field_name).column)) + ')' + @@ -341,10 +342,12 @@ def sql_for_pending_references(model, style, pending_references): def many_to_many_sql_for_model(model, style): from django.db import connection, models from django.contrib.contenttypes import generic + from django.db.backends.util import truncate_name opts = model._meta final_output = [] qn = connection.ops.quote_name + inline_references = connection.features.inline_fk_references for f in opts.many_to_many: if not isinstance(f.rel, generic.GenericRel): tablespace = f.db_tablespace or opts.db_tablespace @@ -354,26 +357,43 @@ def many_to_many_sql_for_model(model, style): tablespace_sql = '' table_output = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + \ style.SQL_TABLE(qn(f.m2m_db_table())) + ' ('] - table_output.append(' %s %s %s%s,' % \ + table_output.append(' %s %s %s%s,' % (style.SQL_FIELD(qn('id')), style.SQL_COLTYPE(models.AutoField(primary_key=True).db_type()), style.SQL_KEYWORD('NOT NULL PRIMARY KEY'), tablespace_sql)) - table_output.append(' %s %s %s %s (%s)%s,' % \ - (style.SQL_FIELD(qn(f.m2m_column_name())), - style.SQL_COLTYPE(models.ForeignKey(model).db_type()), - style.SQL_KEYWORD('NOT NULL REFERENCES'), - style.SQL_TABLE(qn(opts.db_table)), - style.SQL_FIELD(qn(opts.pk.column)), - connection.ops.deferrable_sql())) - table_output.append(' %s %s %s %s (%s)%s,' % \ - (style.SQL_FIELD(qn(f.m2m_reverse_name())), - style.SQL_COLTYPE(models.ForeignKey(f.rel.to).db_type()), - style.SQL_KEYWORD('NOT NULL REFERENCES'), - style.SQL_TABLE(qn(f.rel.to._meta.db_table)), - style.SQL_FIELD(qn(f.rel.to._meta.pk.column)), - connection.ops.deferrable_sql())) - table_output.append(' %s (%s, %s)%s' % \ + if inline_references: + deferred = [] + table_output.append(' %s %s %s %s (%s)%s,' % + (style.SQL_FIELD(qn(f.m2m_column_name())), + style.SQL_COLTYPE(models.ForeignKey(model).db_type()), + style.SQL_KEYWORD('NOT NULL REFERENCES'), + style.SQL_TABLE(qn(opts.db_table)), + style.SQL_FIELD(qn(opts.pk.column)), + connection.ops.deferrable_sql())) + table_output.append(' %s %s %s %s (%s)%s,' % + (style.SQL_FIELD(qn(f.m2m_reverse_name())), + style.SQL_COLTYPE(models.ForeignKey(f.rel.to).db_type()), + style.SQL_KEYWORD('NOT NULL REFERENCES'), + style.SQL_TABLE(qn(f.rel.to._meta.db_table)), + style.SQL_FIELD(qn(f.rel.to._meta.pk.column)), + connection.ops.deferrable_sql())) + else: + table_output.append(' %s %s %s,' % + (style.SQL_FIELD(qn(f.m2m_column_name())), + style.SQL_COLTYPE(models.ForeignKey(model).db_type()), + style.SQL_KEYWORD('NOT NULL'))) + table_output.append(' %s %s %s,' % + (style.SQL_FIELD(qn(f.m2m_reverse_name())), + style.SQL_COLTYPE(models.ForeignKey(f.rel.to).db_type()), + style.SQL_KEYWORD('NOT NULL'))) + deferred = [ + (f.m2m_db_table(), f.m2m_column_name(), opts.db_table, + opts.pk.column), + ( f.m2m_db_table(), f.m2m_reverse_name(), + f.rel.to._meta.db_table, f.rel.to._meta.pk.column) + ] + table_output.append(' %s (%s, %s)%s' % (style.SQL_KEYWORD('UNIQUE'), style.SQL_FIELD(qn(f.m2m_column_name())), style.SQL_FIELD(qn(f.m2m_reverse_name())), @@ -385,6 +405,15 @@ def many_to_many_sql_for_model(model, style): table_output.append(';') final_output.append('\n'.join(table_output)) + for r_table, r_col, table, col in deferred: + r_name = '%s_refs_%s_%x' % (r_col, col, + abs(hash((r_table, table)))) + final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' % + (qn(r_table), + truncate_name(r_name, connection.ops.max_name_length()), + qn(r_col), qn(table), qn(col), + connection.ops.deferrable_sql())) + # Add any extra SQL needed to support auto-incrementing PKs autoinc_sql = connection.ops.autoinc_sql(f.m2m_db_table(), 'id') if autoinc_sql: diff --git a/django/core/servers/basehttp.py b/django/core/servers/basehttp.py index 411068b2f5..380e571b00 100644 --- a/django/core/servers/basehttp.py +++ b/django/core/servers/basehttp.py @@ -9,14 +9,14 @@ been reviewed for security issues. Don't use it for production use. from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer from types import ListType, StringType -from email.Utils import formatdate import mimetypes import os import re import sys -import time import urllib +from django.utils.http import http_date + __version__ = "0.1" __all__ = ['WSGIServer','WSGIRequestHandler','demo_app'] @@ -376,7 +376,7 @@ class ServerHandler(object): self._write('HTTP/%s %s\r\n' % (self.http_version,self.status)) if 'Date' not in self.headers: self._write( - 'Date: %s\r\n' % (formatdate()[:26] + "GMT") + 'Date: %s\r\n' % http_date() ) if self.server_software and 'Server' not in self.headers: self._write('Server: %s\r\n' % self.server_software) diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py index dd50229461..be1776e65f 100644 --- a/django/db/backends/__init__.py +++ b/django/db/backends/__init__.py @@ -43,6 +43,7 @@ class BaseDatabaseFeatures(object): allows_group_by_ordinal = True allows_unique_and_pk = True autoindexes_primary_keys = True + inline_fk_references = True needs_datetime_string_cast = True needs_upper_for_iops = False supports_constraints = True diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py index f729629751..ba3e9efda8 100644 --- a/django/db/backends/mysql/base.py +++ b/django/db/backends/mysql/base.py @@ -61,6 +61,7 @@ server_version_re = re.compile(r'(\d{1,2})\.(\d{1,2})\.(\d{1,2})') class DatabaseFeatures(BaseDatabaseFeatures): autoindexes_primary_keys = False + inline_fk_references = False class DatabaseOperations(BaseDatabaseOperations): def date_extract_sql(self, lookup_type, field_name): diff --git a/django/db/backends/mysql_old/base.py b/django/db/backends/mysql_old/base.py index 0777c1dc93..c22094b968 100644 --- a/django/db/backends/mysql_old/base.py +++ b/django/db/backends/mysql_old/base.py @@ -65,6 +65,7 @@ class MysqlDebugWrapper: class DatabaseFeatures(BaseDatabaseFeatures): autoindexes_primary_keys = False + inline_fk_references = False class DatabaseOperations(BaseDatabaseOperations): def date_extract_sql(self, lookup_type, field_name): diff --git a/django/db/models/__init__.py b/django/db/models/__init__.py index 4c712a0dc2..86763d99f9 100644 --- a/django/db/models/__init__.py +++ b/django/db/models/__init__.py @@ -7,6 +7,7 @@ from django.db.models.query import Q from django.db.models.manager import Manager from django.db.models.base import Model, AdminOptions from django.db.models.fields import * +from django.db.models.fields.subclassing import SubfieldBase from django.db.models.fields.related import ForeignKey, OneToOneField, ManyToManyField, ManyToOneRel, ManyToManyRel, OneToOneRel, TABULAR, STACKED from django.db.models import signals from django.utils.functional import curry diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index e1506ce293..5abb73ead8 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -147,6 +147,8 @@ class Field(object): # exactly which wacky database column type you want to use. data_types = get_creation_module().DATA_TYPES internal_type = self.get_internal_type() + if internal_type not in data_types: + return None return data_types[internal_type] % self.__dict__ def validate_full(self, field_data, all_data): diff --git a/django/db/models/fields/subclassing.py b/django/db/models/fields/subclassing.py new file mode 100644 index 0000000000..1e4c8ca2e0 --- /dev/null +++ b/django/db/models/fields/subclassing.py @@ -0,0 +1,53 @@ +""" +Convenience routines for creating non-trivial Field subclasses. + +Add SubfieldBase as the __metaclass__ for your Field subclass, implement +to_python() and the other necessary methods and everything will work seamlessly. +""" + +from django.utils.maxlength import LegacyMaxlength + +class SubfieldBase(LegacyMaxlength): + """ + A metaclass for custom Field subclasses. This ensures the model's attribute + has the descriptor protocol attached to it. + """ + def __new__(cls, base, name, attrs): + new_class = super(SubfieldBase, cls).__new__(cls, base, name, attrs) + new_class.contribute_to_class = make_contrib( + attrs.get('contribute_to_class')) + return new_class + +class Creator(object): + """ + A placeholder class that provides a way to set the attribute on the model. + """ + def __init__(self, field): + self.field = field + + def __get__(self, obj, type=None): + if obj is None: + raise AttributeError('Can only be accessed via an instance.') + return self.value + + def __set__(self, obj, value): + self.value = self.field.to_python(value) + +def make_contrib(func=None): + """ + Returns a suitable contribute_to_class() method for the Field subclass. + + If 'func' is passed in, it is the existing contribute_to_class() method on + the subclass and it is called before anything else. It is assumed in this + case that the existing contribute_to_class() calls all the necessary + superclass methods. + """ + def contribute_to_class(self, cls, name): + if func: + func(self, cls, name) + else: + super(self.__class__, self).contribute_to_class(cls, name) + setattr(cls, self.name, Creator(self)) + + return contribute_to_class + diff --git a/django/http/__init__.py b/django/http/__init__.py index 51744a0866..47f9736ce2 100644 --- a/django/http/__init__.py +++ b/django/http/__init__.py @@ -5,6 +5,7 @@ from urllib import urlencode from urlparse import urljoin from django.utils.datastructures import MultiValueDict, FileDict from django.utils.encoding import smart_str, iri_to_uri, force_unicode +from utils import * RESERVED_CHARS="!*'();:@&=+$,/?%#[]" diff --git a/django/http/utils.py b/django/http/utils.py new file mode 100644 index 0000000000..d08a9e0237 --- /dev/null +++ b/django/http/utils.py @@ -0,0 +1,34 @@ +""" +Functions that modify an HTTP request or response in some way. +""" + +# This group of functions are run as part of the response handling, after +# everything else, including all response middleware. Think of them as +# "compulsory response middleware". Be careful about what goes here, because +# it's a little fiddly to override this behaviour, so they should be truly +# universally applicable. + +def fix_location_header(request, response): + """ + Ensures that we always use an absolute URI in any location header in the + response. This is required by RFC 2616, section 14.30. + + Code constructing response objects is free to insert relative paths and + this function converts them to absolute paths. + """ + if 'Location' in response and request.get_host(): + response['Location'] = request.build_absolute_uri(response['Location']) + return response + +def conditional_content_removal(request, response): + """ + Removes the content of responses for HEAD requests, 1xx, 204 and 304 + responses. Ensures compliance with RFC 2616, section 4.3. + """ + if 100 <= response.status_code < 200 or response.status_code in (204, 304): + response.content = '' + response['Content-Length'] = 0 + if request.method == 'HEAD': + response.content = '' + return response + diff --git a/django/middleware/http.py b/django/middleware/http.py index 71cdf7aa5d..2ef46c6b61 100644 --- a/django/middleware/http.py +++ b/django/middleware/http.py @@ -1,4 +1,4 @@ -from email.Utils import formatdate +from django.utils.http import http_date class ConditionalGetMiddleware(object): """ @@ -6,32 +6,27 @@ class ConditionalGetMiddleware(object): Last-Modified header, and the request has If-None-Match or If-Modified-Since, the response is replaced by an HttpNotModified. - Removes the content from any response to a HEAD request. - Also sets the Date and Content-Length response-headers. """ def process_response(self, request, response): - response['Date'] = formatdate()[:26] + "GMT" + response['Date'] = http_date() if not response.has_header('Content-Length'): response['Content-Length'] = str(len(response.content)) if response.has_header('ETag'): if_none_match = request.META.get('HTTP_IF_NONE_MATCH', None) if if_none_match == response['ETag']: - response.status_code = 304 - response.content = '' - response['Content-Length'] = '0' + # Setting the status is enough here. The response handling path + # automatically removes content for this status code (in + # http.conditional_content_removal()). + response.status = 304 if response.has_header('Last-Modified'): - last_mod = response['Last-Modified'] if_modified_since = request.META.get('HTTP_IF_MODIFIED_SINCE', None) if if_modified_since == response['Last-Modified']: - response.status_code = 304 - response.content = '' - response['Content-Length'] = '0' - - if request.method == 'HEAD': - response.content = '' + # Setting the status code is enough here (same reasons as + # above). + response.status = 304 return response diff --git a/django/newforms/fields.py b/django/newforms/fields.py index 11e6f51779..9bb2ced583 100644 --- a/django/newforms/fields.py +++ b/django/newforms/fields.py @@ -1,30 +1,35 @@ """ -Field classes +Field classes. """ import copy import datetime import re import time +# Python 2.3 fallbacks +try: + from decimal import Decimal, DecimalException +except ImportError: + from django.utils._decimal import Decimal, DecimalException +try: + set +except NameError: + from sets import Set as set -from django.utils.translation import ugettext +from django.utils.translation import ugettext_lazy as _ from django.utils.encoding import StrAndUnicode, smart_unicode from util import ErrorList, ValidationError from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, FileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, DateTimeInput -try: - from decimal import Decimal, DecimalException -except ImportError: - from django.utils._decimal import Decimal, DecimalException __all__ = ( 'Field', 'CharField', 'IntegerField', 'DEFAULT_DATE_INPUT_FORMATS', 'DateField', 'DEFAULT_TIME_INPUT_FORMATS', 'TimeField', 'DEFAULT_DATETIME_INPUT_FORMATS', 'DateTimeField', - 'RegexField', 'EmailField', 'FileField', 'ImageField', 'URLField', 'BooleanField', - 'ChoiceField', 'NullBooleanField', 'MultipleChoiceField', + 'RegexField', 'EmailField', 'FileField', 'ImageField', 'URLField', + 'BooleanField', 'NullBooleanField', 'ChoiceField', 'MultipleChoiceField', 'ComboField', 'MultiValueField', 'FloatField', 'DecimalField', 'SplitDateTimeField', 'IPAddressField', ) @@ -32,24 +37,20 @@ __all__ = ( # These values, if given to to_python(), will trigger the self.required check. EMPTY_VALUES = (None, '') -try: - set -except NameError: - from sets import Set as set # Python 2.3 fallback - -try: - from decimal import Decimal -except ImportError: - from django.utils._decimal import Decimal # Python 2.3 fallback class Field(object): widget = TextInput # Default widget to use when rendering this type of Field. hidden_widget = HiddenInput # Default widget to use when rendering this as "hidden". + default_error_messages = { + 'required': _(u'This field is required.'), + 'invalid': _(u'Enter a valid value.'), + } # Tracks each time a Field instance is created. Used to retain order. creation_counter = 0 - def __init__(self, required=True, widget=None, label=None, initial=None, help_text=None): + def __init__(self, required=True, widget=None, label=None, initial=None, + help_text=None, error_messages=None): # required -- Boolean that specifies whether the field is required. # True by default. # widget -- A Widget class, or instance of a Widget class, that should @@ -82,6 +83,22 @@ class Field(object): self.creation_counter = Field.creation_counter Field.creation_counter += 1 + self.error_messages = self._build_error_messages(error_messages) + + def _build_error_messages(self, extra_error_messages): + error_messages = {} + + def get_default_error_messages(klass): + for base_class in klass.__bases__: + get_default_error_messages(base_class) + if hasattr(klass, 'default_error_messages'): + error_messages.update(klass.default_error_messages) + + get_default_error_messages(self.__class__) + if extra_error_messages: + error_messages.update(extra_error_messages) + return error_messages + def clean(self, value): """ Validates the given value and returns its "cleaned" value as an @@ -90,7 +107,7 @@ class Field(object): Raises ValidationError for any errors. """ if self.required and value in EMPTY_VALUES: - raise ValidationError(ugettext(u'This field is required.')) + raise ValidationError(self.error_messages['required']) return value def widget_attrs(self, widget): @@ -108,6 +125,11 @@ class Field(object): return result class CharField(Field): + default_error_messages = { + 'max_length': _(u'Ensure this value has at most %(max)d characters (it has %(length)d).'), + 'min_length': _(u'Ensure this value has at least %(min)d characters (it has %(length)d).'), + } + def __init__(self, max_length=None, min_length=None, *args, **kwargs): self.max_length, self.min_length = max_length, min_length super(CharField, self).__init__(*args, **kwargs) @@ -120,9 +142,9 @@ class CharField(Field): value = smart_unicode(value) value_length = len(value) if self.max_length is not None and value_length > self.max_length: - raise ValidationError(ugettext(u'Ensure this value has at most %(max)d characters (it has %(length)d).') % {'max': self.max_length, 'length': value_length}) + raise ValidationError(self.error_messages['max_length'] % {'max': self.max_length, 'length': value_length}) if self.min_length is not None and value_length < self.min_length: - raise ValidationError(ugettext(u'Ensure this value has at least %(min)d characters (it has %(length)d).') % {'min': self.min_length, 'length': value_length}) + raise ValidationError(self.error_messages['min_length'] % {'min': self.min_length, 'length': value_length}) return value def widget_attrs(self, widget): @@ -131,6 +153,12 @@ class CharField(Field): return {'maxlength': str(self.max_length)} class IntegerField(Field): + default_error_messages = { + 'invalid': _(u'Enter a whole number.'), + 'max_value': _(u'Ensure this value is less than or equal to %s.'), + 'min_value': _(u'Ensure this value is greater than or equal to %s.'), + } + def __init__(self, max_value=None, min_value=None, *args, **kwargs): self.max_value, self.min_value = max_value, min_value super(IntegerField, self).__init__(*args, **kwargs) @@ -146,14 +174,20 @@ class IntegerField(Field): try: value = int(str(value)) except (ValueError, TypeError): - raise ValidationError(ugettext(u'Enter a whole number.')) + raise ValidationError(self.error_messages['invalid']) if self.max_value is not None and value > self.max_value: - raise ValidationError(ugettext(u'Ensure this value is less than or equal to %s.') % self.max_value) + raise ValidationError(self.error_messages['max_value'] % self.max_value) if self.min_value is not None and value < self.min_value: - raise ValidationError(ugettext(u'Ensure this value is greater than or equal to %s.') % self.min_value) + raise ValidationError(self.error_messages['min_value'] % self.min_value) return value class FloatField(Field): + default_error_messages = { + 'invalid': _(u'Enter a number.'), + 'max_value': _(u'Ensure this value is less than or equal to %s.'), + 'min_value': _(u'Ensure this value is greater than or equal to %s.'), + } + def __init__(self, max_value=None, min_value=None, *args, **kwargs): self.max_value, self.min_value = max_value, min_value Field.__init__(self, *args, **kwargs) @@ -169,14 +203,23 @@ class FloatField(Field): try: value = float(value) except (ValueError, TypeError): - raise ValidationError(ugettext('Enter a number.')) + raise ValidationError(self.error_messages['invalid']) if self.max_value is not None and value > self.max_value: - raise ValidationError(ugettext('Ensure this value is less than or equal to %s.') % self.max_value) + raise ValidationError(self.error_messages['max_value'] % self.max_value) if self.min_value is not None and value < self.min_value: - raise ValidationError(ugettext('Ensure this value is greater than or equal to %s.') % self.min_value) + raise ValidationError(self.error_messages['min_value'] % self.min_value) return value class DecimalField(Field): + default_error_messages = { + 'invalid': _(u'Enter a number.'), + 'max_value': _(u'Ensure this value is less than or equal to %s.'), + 'min_value': _(u'Ensure this value is greater than or equal to %s.'), + 'max_digits': _('Ensure that there are no more than %s digits in total.'), + 'max_decimal_places': _('Ensure that there are no more than %s decimal places.'), + 'max_whole_digits': _('Ensure that there are no more than %s digits before the decimal point.') + } + def __init__(self, max_value=None, min_value=None, max_digits=None, decimal_places=None, *args, **kwargs): self.max_value, self.min_value = max_value, min_value self.max_digits, self.decimal_places = max_digits, decimal_places @@ -196,20 +239,20 @@ class DecimalField(Field): try: value = Decimal(value) except DecimalException: - raise ValidationError(ugettext('Enter a number.')) + raise ValidationError(self.error_messages['invalid']) pieces = str(value).lstrip("-").split('.') decimals = (len(pieces) == 2) and len(pieces[1]) or 0 digits = len(pieces[0]) if self.max_value is not None and value > self.max_value: - raise ValidationError(ugettext('Ensure this value is less than or equal to %s.') % self.max_value) + raise ValidationError(self.error_messages['max_value'] % self.max_value) if self.min_value is not None and value < self.min_value: - raise ValidationError(ugettext('Ensure this value is greater than or equal to %s.') % self.min_value) + raise ValidationError(self.error_messages['min_value'] % self.min_value) if self.max_digits is not None and (digits + decimals) > self.max_digits: - raise ValidationError(ugettext('Ensure that there are no more than %s digits in total.') % self.max_digits) + raise ValidationError(self.error_messages['max_digits'] % self.max_digits) if self.decimal_places is not None and decimals > self.decimal_places: - raise ValidationError(ugettext('Ensure that there are no more than %s decimal places.') % self.decimal_places) + raise ValidationError(self.error_messages['max_decimal_places'] % self.decimal_places) if self.max_digits is not None and self.decimal_places is not None and digits > (self.max_digits - self.decimal_places): - raise ValidationError(ugettext('Ensure that there are no more than %s digits before the decimal point.') % (self.max_digits - self.decimal_places)) + raise ValidationError(self.error_messages['max_whole_digits'] % (self.max_digits - self.decimal_places)) return value DEFAULT_DATE_INPUT_FORMATS = ( @@ -221,6 +264,10 @@ DEFAULT_DATE_INPUT_FORMATS = ( ) class DateField(Field): + default_error_messages = { + 'invalid': _(u'Enter a valid date.'), + } + def __init__(self, input_formats=None, *args, **kwargs): super(DateField, self).__init__(*args, **kwargs) self.input_formats = input_formats or DEFAULT_DATE_INPUT_FORMATS @@ -242,7 +289,7 @@ class DateField(Field): return datetime.date(*time.strptime(value, format)[:3]) except ValueError: continue - raise ValidationError(ugettext(u'Enter a valid date.')) + raise ValidationError(self.error_messages['invalid']) DEFAULT_TIME_INPUT_FORMATS = ( '%H:%M:%S', # '14:30:59' @@ -250,6 +297,10 @@ DEFAULT_TIME_INPUT_FORMATS = ( ) class TimeField(Field): + default_error_messages = { + 'invalid': _(u'Enter a valid time.') + } + def __init__(self, input_formats=None, *args, **kwargs): super(TimeField, self).__init__(*args, **kwargs) self.input_formats = input_formats or DEFAULT_TIME_INPUT_FORMATS @@ -269,7 +320,7 @@ class TimeField(Field): return datetime.time(*time.strptime(value, format)[3:6]) except ValueError: continue - raise ValidationError(ugettext(u'Enter a valid time.')) + raise ValidationError(self.error_messages['invalid']) DEFAULT_DATETIME_INPUT_FORMATS = ( '%Y-%m-%d %H:%M:%S', # '2006-10-25 14:30:59' @@ -285,6 +336,9 @@ DEFAULT_DATETIME_INPUT_FORMATS = ( class DateTimeField(Field): widget = DateTimeInput + default_error_messages = { + 'invalid': _(u'Enter a valid date/time.'), + } def __init__(self, input_formats=None, *args, **kwargs): super(DateTimeField, self).__init__(*args, **kwargs) @@ -306,14 +360,14 @@ class DateTimeField(Field): # Input comes from a SplitDateTimeWidget, for example. So, it's two # components: date and time. if len(value) != 2: - raise ValidationError(ugettext(u'Enter a valid date/time.')) + raise ValidationError(self.error_messages['invalid']) value = '%s %s' % tuple(value) for format in self.input_formats: try: return datetime.datetime(*time.strptime(value, format)[:6]) except ValueError: continue - raise ValidationError(ugettext(u'Enter a valid date/time.')) + raise ValidationError(self.error_messages['invalid']) class RegexField(CharField): def __init__(self, regex, max_length=None, min_length=None, error_message=None, *args, **kwargs): @@ -322,11 +376,15 @@ class RegexField(CharField): error_message is an optional error message to use, if 'Enter a valid value' is too generic for you. """ + # error_message is just kept for backwards compatibility: + if error_message: + error_messages = kwargs.get('error_messages') or {} + error_messages['invalid'] = error_message + kwargs['error_messages'] = error_messages super(RegexField, self).__init__(max_length, min_length, *args, **kwargs) if isinstance(regex, basestring): regex = re.compile(regex) self.regex = regex - self.error_message = error_message or ugettext(u'Enter a valid value.') def clean(self, value): """ @@ -337,7 +395,7 @@ class RegexField(CharField): if value == u'': return value if not self.regex.search(value): - raise ValidationError(self.error_message) + raise ValidationError(self.error_messages['invalid']) return value email_re = re.compile( @@ -346,9 +404,13 @@ email_re = re.compile( r')@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}$', re.IGNORECASE) # domain class EmailField(RegexField): + default_error_messages = { + 'invalid': _(u'Enter a valid e-mail address.'), + } + def __init__(self, max_length=None, min_length=None, *args, **kwargs): - RegexField.__init__(self, email_re, max_length, min_length, - ugettext(u'Enter a valid e-mail address.'), *args, **kwargs) + RegexField.__init__(self, email_re, max_length, min_length, *args, + **kwargs) try: from django.conf import settings @@ -372,6 +434,12 @@ class UploadedFile(StrAndUnicode): class FileField(Field): widget = FileInput + default_error_messages = { + 'invalid': _(u"No file was submitted. Check the encoding type on the form."), + 'missing': _(u"No file was submitted."), + 'empty': _(u"The submitted file is empty."), + } + def __init__(self, *args, **kwargs): super(FileField, self).__init__(*args, **kwargs) @@ -382,14 +450,18 @@ class FileField(Field): try: f = UploadedFile(data['filename'], data['content']) except TypeError: - raise ValidationError(ugettext(u"No file was submitted. Check the encoding type on the form.")) + raise ValidationError(self.error_messages['invalid']) except KeyError: - raise ValidationError(ugettext(u"No file was submitted.")) + raise ValidationError(self.error_messages['missing']) if not f.content: - raise ValidationError(ugettext(u"The submitted file is empty.")) + raise ValidationError(self.error_messages['empty']) return f class ImageField(FileField): + default_error_messages = { + 'invalid_image': _(u"Upload a valid image. The file you uploaded was either not an image or a corrupted image."), + } + def clean(self, data): """ Checks that the file-upload field data contains a valid image (GIF, JPG, @@ -410,7 +482,7 @@ class ImageField(FileField): trial_image = Image.open(StringIO(f.content)) trial_image.verify() except Exception: # Python Imaging Library doesn't recognize it as an image - raise ValidationError(ugettext(u"Upload a valid image. The file you uploaded was either not an image or a corrupted image.")) + raise ValidationError(self.error_messages['invalid_image']) return f url_re = re.compile( @@ -422,9 +494,15 @@ url_re = re.compile( r'(?:/?|/\S+)$', re.IGNORECASE) class URLField(RegexField): + default_error_messages = { + 'invalid': _(u'Enter a valid URL.'), + 'invalid_link': _(u'This URL appears to be a broken link.'), + } + def __init__(self, max_length=None, min_length=None, verify_exists=False, validator_user_agent=URL_VALIDATOR_USER_AGENT, *args, **kwargs): - super(URLField, self).__init__(url_re, max_length, min_length, ugettext(u'Enter a valid URL.'), *args, **kwargs) + super(URLField, self).__init__(url_re, max_length, min_length, *args, + **kwargs) self.verify_exists = verify_exists self.user_agent = validator_user_agent @@ -449,9 +527,9 @@ class URLField(RegexField): req = urllib2.Request(value, None, headers) u = urllib2.urlopen(req) except ValueError: - raise ValidationError(ugettext(u'Enter a valid URL.')) + raise ValidationError(self.error_messages['invalid']) except: # urllib2.URLError, httplib.InvalidURL, etc. - raise ValidationError(ugettext(u'This URL appears to be a broken link.')) + raise ValidationError(self.error_messages['invalid_link']) return value class BooleanField(Field): @@ -478,9 +556,14 @@ class NullBooleanField(BooleanField): class ChoiceField(Field): widget = Select + default_error_messages = { + 'invalid_choice': _(u'Select a valid choice. That choice is not one of the available choices.'), + } - def __init__(self, choices=(), required=True, widget=None, label=None, initial=None, help_text=None): - super(ChoiceField, self).__init__(required, widget, label, initial, help_text) + def __init__(self, choices=(), required=True, widget=None, label=None, + initial=None, help_text=None, *args, **kwargs): + super(ChoiceField, self).__init__(required, widget, label, initial, + help_text, *args, **kwargs) self.choices = choices def _get_choices(self): @@ -506,29 +589,33 @@ class ChoiceField(Field): return value valid_values = set([smart_unicode(k) for k, v in self.choices]) if value not in valid_values: - raise ValidationError(ugettext(u'Select a valid choice. That choice is not one of the available choices.')) + raise ValidationError(self.error_messages['invalid_choice'] % {'value': value}) return value class MultipleChoiceField(ChoiceField): hidden_widget = MultipleHiddenInput widget = SelectMultiple + default_error_messages = { + 'invalid_choice': _(u'Select a valid choice. %(value)s is not one of the available choices.'), + 'invalid_list': _(u'Enter a list of values.'), + } def clean(self, value): """ Validates that the input is a list or tuple. """ if self.required and not value: - raise ValidationError(ugettext(u'This field is required.')) + raise ValidationError(self.error_messages['required']) elif not self.required and not value: return [] if not isinstance(value, (list, tuple)): - raise ValidationError(ugettext(u'Enter a list of values.')) + raise ValidationError(self.error_messages['invalid_list']) new_value = [smart_unicode(val) for val in value] # Validate that each value in the value list is 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(ugettext(u'Select a valid choice. %s is not one of the available choices.') % val) + raise ValidationError(self.error_messages['invalid_choice'] % {'value': val}) return new_value class ComboField(Field): @@ -571,6 +658,10 @@ class MultiValueField(Field): You'll probably want to use this with MultiWidget. """ + default_error_messages = { + 'invalid': _(u'Enter a list of values.'), + } + def __init__(self, fields=(), *args, **kwargs): super(MultiValueField, self).__init__(*args, **kwargs) # Set 'required' to False on the individual fields, because the @@ -594,18 +685,18 @@ class MultiValueField(Field): if not value or isinstance(value, (list, tuple)): if not value or not [v for v in value if v not in EMPTY_VALUES]: if self.required: - raise ValidationError(ugettext(u'This field is required.')) + raise ValidationError(self.error_messages['required']) else: return self.compress([]) else: - raise ValidationError(ugettext(u'Enter a list of values.')) + raise ValidationError(self.error_messages['invalid']) for i, field in enumerate(self.fields): try: field_value = value[i] except IndexError: field_value = None if self.required and field_value in EMPTY_VALUES: - raise ValidationError(ugettext(u'This field is required.')) + raise ValidationError(self.error_messages['required']) try: clean_data.append(field.clean(field_value)) except ValidationError, e: @@ -629,8 +720,19 @@ class MultiValueField(Field): raise NotImplementedError('Subclasses must implement this method.') class SplitDateTimeField(MultiValueField): + default_error_messages = { + 'invalid_date': _(u'Enter a valid date.'), + 'invalid_time': _(u'Enter a valid time.'), + } + def __init__(self, *args, **kwargs): - fields = (DateField(), TimeField()) + errors = self.default_error_messages.copy() + if 'error_messages' in kwargs: + errors.update(kwargs['error_messages']) + fields = ( + DateField(error_messages={'invalid': errors['invalid_date']}), + TimeField(error_messages={'invalid': errors['invalid_time']}), + ) super(SplitDateTimeField, self).__init__(fields, *args, **kwargs) def compress(self, data_list): @@ -638,16 +740,18 @@ class SplitDateTimeField(MultiValueField): # Raise a validation error if time or date is empty # (possible if SplitDateTimeField has required=False). if data_list[0] in EMPTY_VALUES: - raise ValidationError(ugettext(u'Enter a valid date.')) + raise ValidationError(self.error_messages['invalid_date']) if data_list[1] in EMPTY_VALUES: - raise ValidationError(ugettext(u'Enter a valid time.')) + raise ValidationError(self.error_messages['invalid_time']) return datetime.datetime.combine(*data_list) return None ipv4_re = re.compile(r'^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$') class IPAddressField(RegexField): + default_error_messages = { + 'invalid': _(u'Enter a valid IPv4 address.'), + } + def __init__(self, *args, **kwargs): - RegexField.__init__(self, ipv4_re, - error_message=ugettext(u'Enter a valid IPv4 address.'), - *args, **kwargs) + super(IPAddressField, self).__init__(ipv4_re, *args, **kwargs) diff --git a/django/newforms/forms.py b/django/newforms/forms.py index 3196db339e..556c00a777 100644 --- a/django/newforms/forms.py +++ b/django/newforms/forms.py @@ -2,11 +2,12 @@ Form classes """ -import copy +from copy import deepcopy from django.utils.datastructures import SortedDict from django.utils.html import escape from django.utils.encoding import StrAndUnicode, smart_unicode, force_unicode +from django.utils.safestring import mark_safe from fields import Field from widgets import TextInput, Textarea @@ -21,18 +22,6 @@ 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)) - - def copy(self): - return SortedDictFromList([(k, copy.deepcopy(v)) for k, v in self.items()]) - class DeclarativeFieldsMetaclass(type): """ Metaclass that converts Field attributes to a dictionary called @@ -49,7 +38,7 @@ class DeclarativeFieldsMetaclass(type): if hasattr(base, 'base_fields'): fields = base.base_fields.items() + fields - attrs['base_fields'] = SortedDictFromList(fields) + attrs['base_fields'] = SortedDict(fields) return type.__new__(cls, name, bases, attrs) class BaseForm(StrAndUnicode): @@ -74,7 +63,7 @@ class BaseForm(StrAndUnicode): # alter self.fields, we create self.fields here by copying base_fields. # Instances should always modify self.fields; they should not modify # self.base_fields. - self.fields = self.base_fields.copy() + self.fields = deepcopy(self.base_fields) def __unicode__(self): return self.as_table() @@ -130,7 +119,8 @@ class BaseForm(StrAndUnicode): output.append(error_row % force_unicode(bf_errors)) if bf.label: label = escape(force_unicode(bf.label)) - # Only add the suffix if the label does not end in punctuation. + # Only add the suffix if the label does not end in + # punctuation. if self.label_suffix: if label[-1] not in ':?.!': label += self.label_suffix @@ -148,11 +138,14 @@ class BaseForm(StrAndUnicode): str_hidden = u''.join(hidden_fields) if output: last_row = output[-1] - # Chop off the trailing row_ender (e.g. '') and insert the hidden fields. + # Chop off the trailing row_ender (e.g. '') 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. + else: + # If there aren't any rows in the output, just append the + # hidden fields. output.append(str_hidden) - return u'\n'.join(output) + return mark_safe(u'\n'.join(output)) def as_table(self): "Returns this form rendered as HTML
          s -- excluding the
          {{ model.name|escape }}{{ model.name }}{{ model.name|escape }}{{ model.name }}
          {{ action.action_time|date:_("DATE_WITH_TIME_FULL") }}{{ action.user.username }}{% if action.user.first_name %} ({{ action.user.first_name|escape }} {{ action.user.last_name|escape }}){% endif %}{{ action.change_message|escape }}{{ action.user.username }}{% if action.user.first_name %} ({{ action.user.first_name }} {{ action.user.last_name }}){% endif %}{{ action.change_message }}
          ." @@ -315,7 +308,7 @@ class BoundField(StrAndUnicode): if id_: attrs = attrs and flatatt(attrs) or '' contents = '' % (widget.id_for_label(id_), attrs, contents) - return contents + return mark_safe(contents) def _is_hidden(self): "Returns True if this BoundField's widget is hidden." diff --git a/django/newforms/models.py b/django/newforms/models.py index 247a0eea6b..c86b9b3a42 100644 --- a/django/newforms/models.py +++ b/django/newforms/models.py @@ -5,10 +5,10 @@ and database field objects. from django.utils.translation import ugettext from django.utils.encoding import smart_unicode - +from django.utils.datastructures import SortedDict from util import ValidationError -from forms import BaseForm, SortedDictFromList +from forms import BaseForm from fields import Field, ChoiceField from widgets import Select, SelectMultiple, MultipleHiddenInput @@ -17,7 +17,8 @@ __all__ = ( 'ModelChoiceField', 'ModelMultipleChoiceField' ) -def save_instance(form, instance, fields=None, fail_message='saved', commit=True): +def save_instance(form, instance, fields=None, fail_message='saved', + commit=True): """ Saves bound Form ``form``'s cleaned_data into model instance ``instance``. @@ -27,15 +28,17 @@ def save_instance(form, instance, fields=None, fail_message='saved', commit=True from django.db import models opts = instance.__class__._meta if form.errors: - raise ValueError("The %s could not be %s because the data didn't validate." % (opts.object_name, fail_message)) + raise ValueError("The %s could not be %s because the data didn't" + " validate." % (opts.object_name, fail_message)) cleaned_data = form.cleaned_data for f in opts.fields: - if not f.editable or isinstance(f, models.AutoField) or not f.name in cleaned_data: + if not f.editable or isinstance(f, models.AutoField) \ + or not f.name in cleaned_data: continue if fields and f.name not in fields: continue - f.save_form_data(instance, cleaned_data[f.name]) - # Wrap up the saving of m2m data as a function + f.save_form_data(instance, cleaned_data[f.name]) + # Wrap up the saving of m2m data as a function. def save_m2m(): opts = instance.__class__._meta cleaned_data = form.cleaned_data @@ -45,28 +48,29 @@ def save_instance(form, instance, fields=None, fail_message='saved', commit=True if f.name in cleaned_data: f.save_form_data(instance, cleaned_data[f.name]) if commit: - # If we are committing, save the instance and the m2m data immediately + # If we are committing, save the instance and the m2m data immediately. instance.save() save_m2m() else: - # We're not committing. Add a method to the form to allow deferred - # saving of m2m data + # We're not committing. Add a method to the form to allow deferred + # saving of m2m data. form.save_m2m = save_m2m return instance def make_model_save(model, fields, fail_message): - "Returns the save() method for a Form." + """Returns the save() method for a Form.""" def save(self, commit=True): return save_instance(self, model(), fields, fail_message, commit) return save - + def make_instance_save(instance, fields, fail_message): - "Returns the save() method for a Form." + """Returns the save() method for a Form.""" def save(self, commit=True): return save_instance(self, instance, fields, fail_message, commit) return save -def form_for_model(model, form=BaseForm, fields=None, formfield_callback=lambda f: f.formfield()): +def form_for_model(model, form=BaseForm, fields=None, + formfield_callback=lambda f: f.formfield()): """ Returns a Form class for the given Django model class. @@ -86,11 +90,13 @@ def form_for_model(model, form=BaseForm, fields=None, formfield_callback=lambda formfield = formfield_callback(f) if formfield: field_list.append((f.name, formfield)) - base_fields = SortedDictFromList(field_list) - return type(opts.object_name + 'Form', (form,), - {'base_fields': base_fields, '_model': model, 'save': make_model_save(model, fields, 'created')}) + base_fields = SortedDict(field_list) + return type(opts.object_name + 'Form', (form,), + {'base_fields': base_fields, '_model': model, + 'save': make_model_save(model, fields, 'created')}) -def form_for_instance(instance, form=BaseForm, fields=None, formfield_callback=lambda f, **kwargs: f.formfield(**kwargs)): +def form_for_instance(instance, form=BaseForm, fields=None, + formfield_callback=lambda f, **kwargs: f.formfield(**kwargs)): """ Returns a Form class for the given Django model instance. @@ -113,18 +119,24 @@ def form_for_instance(instance, form=BaseForm, fields=None, formfield_callback=l formfield = formfield_callback(f, initial=current_value) if formfield: field_list.append((f.name, formfield)) - base_fields = SortedDictFromList(field_list) + base_fields = SortedDict(field_list) return type(opts.object_name + 'InstanceForm', (form,), - {'base_fields': base_fields, '_model': model, 'save': make_instance_save(instance, fields, 'changed')}) + {'base_fields': base_fields, '_model': model, + 'save': make_instance_save(instance, fields, 'changed')}) def form_for_fields(field_list): - "Returns a Form class for the given list of Django database field instances." - fields = SortedDictFromList([(f.name, f.formfield()) for f in field_list if f.editable]) + """ + Returns a Form class for the given list of Django database field instances. + """ + fields = SortedDict([(f.name, f.formfield()) + for f in field_list if f.editable]) return type('FormForFields', (BaseForm,), {'base_fields': fields}) class QuerySetIterator(object): def __init__(self, queryset, empty_label, cache_choices): - self.queryset, self.empty_label, self.cache_choices = queryset, empty_label, cache_choices + self.queryset = queryset + self.empty_label = empty_label + self.cache_choices = cache_choices def __iter__(self): if self.empty_label is not None: @@ -136,19 +148,29 @@ class QuerySetIterator(object): self.queryset._result_cache = None class ModelChoiceField(ChoiceField): - "A ChoiceField whose choices are a model QuerySet." + """A ChoiceField whose choices are a model QuerySet.""" # This class is a subclass of ChoiceField for purity, but it doesn't # actually use any of ChoiceField's implementation. + def __init__(self, queryset, empty_label=u"---------", cache_choices=False, - required=True, widget=Select, label=None, initial=None, help_text=None): - self.queryset = queryset + required=True, widget=Select, label=None, initial=None, + help_text=None): self.empty_label = empty_label self.cache_choices = cache_choices # Call Field instead of ChoiceField __init__() because we don't need # ChoiceField.__init__(). Field.__init__(self, required, widget, label, initial, help_text) + self.queryset = queryset + + def _get_queryset(self): + return self._queryset + + def _set_queryset(self, queryset): + self._queryset = queryset self.widget.choices = self.choices + queryset = property(_get_queryset, _set_queryset) + def _get_choices(self): # If self._choices is set, then somebody must have manually set # the property self.choices. In this case, just return self._choices. @@ -160,7 +182,8 @@ class ModelChoiceField(ChoiceField): # *each* time _get_choices() is called (and, thus, each time # self.choices is accessed) so that we can ensure the QuerySet has not # been consumed. - return QuerySetIterator(self.queryset, self.empty_label, self.cache_choices) + return QuerySetIterator(self.queryset, self.empty_label, + self.cache_choices) def _set_choices(self, value): # This method is copied from ChoiceField._set_choices(). It's necessary @@ -175,18 +198,22 @@ class ModelChoiceField(ChoiceField): if value in ('', None): return None try: - value = self.queryset.model._default_manager.get(pk=value) + value = self.queryset.get(pk=value) except self.queryset.model.DoesNotExist: - raise ValidationError(ugettext(u'Select a valid choice. That choice is not one of the available choices.')) + raise ValidationError(ugettext(u'Select a valid choice. That' + u' choice is not one of the' + u' available choices.')) return value class ModelMultipleChoiceField(ModelChoiceField): - "A MultipleChoiceField whose choices are a model QuerySet." + """A MultipleChoiceField whose choices are a model QuerySet.""" hidden_widget = MultipleHiddenInput + def __init__(self, queryset, cache_choices=False, required=True, - widget=SelectMultiple, label=None, initial=None, help_text=None): - super(ModelMultipleChoiceField, self).__init__(queryset, None, cache_choices, - required, widget, label, initial, help_text) + widget=SelectMultiple, label=None, initial=None, + help_text=None): + super(ModelMultipleChoiceField, self).__init__(queryset, None, + cache_choices, required, widget, label, initial, help_text) def clean(self, value): if self.required and not value: @@ -198,9 +225,11 @@ class ModelMultipleChoiceField(ModelChoiceField): final_values = [] for val in value: try: - obj = self.queryset.model._default_manager.get(pk=val) + obj = self.queryset.get(pk=val) except self.queryset.model.DoesNotExist: - raise ValidationError(ugettext(u'Select a valid choice. %s is not one of the available choices.') % val) + raise ValidationError(ugettext(u'Select a valid choice. %s is' + u' not one of the available' + u' choices.') % val) else: final_values.append(obj) return final_values diff --git a/django/newforms/util.py b/django/newforms/util.py index e19d894397..b3edf41adf 100644 --- a/django/newforms/util.py +++ b/django/newforms/util.py @@ -1,6 +1,7 @@ from django.utils.html import escape from django.utils.encoding import smart_unicode, StrAndUnicode, force_unicode from django.utils.functional import Promise +from django.utils.safestring import mark_safe def flatatt(attrs): """ @@ -22,7 +23,9 @@ class ErrorDict(dict, StrAndUnicode): def as_ul(self): if not self: return u'' - return u'
            %s
          ' % ''.join([u'
        • %s%s
        • ' % (k, force_unicode(v)) for k, v in self.items()]) + return mark_safe(u'
            %s
          ' + % ''.join([u'
        • %s%s
        • ' % (k, force_unicode(v)) + for k, v in self.items()])) def as_text(self): return u'\n'.join([u'* %s\n%s' % (k, u'\n'.join([u' * %s' % force_unicode(i) for i in v])) for k, v in self.items()]) @@ -36,19 +39,25 @@ class ErrorList(list, StrAndUnicode): def as_ul(self): if not self: return u'' - return u'
            %s
          ' % ''.join([u'
        • %s
        • ' % force_unicode(e) for e in self]) + return mark_safe(u'
            %s
          ' + % ''.join([u'
        • %s
        • ' % force_unicode(e) for e in self])) def as_text(self): if not self: return u'' return u'\n'.join([u'* %s' % force_unicode(e) for e in self]) + def __repr__(self): + return repr([force_unicode(e) for e in self]) + class ValidationError(Exception): def __init__(self, message): - "ValidationError can be passed a string or a list." + """ + ValidationError can be passed any object that can be printed (usually + a string) or a list of objects. + """ if isinstance(message, list): self.messages = ErrorList([smart_unicode(msg) for msg in message]) else: - assert isinstance(message, (basestring, Promise)), ("%s should be a basestring or lazy translation" % repr(message)) message = smart_unicode(message) self.messages = ErrorList([message]) @@ -58,4 +67,3 @@ class ValidationError(Exception): # AttributeError: ValidationError instance has no attribute 'args' # See http://www.python.org/doc/current/tut/node10.html#handling return repr(self.messages) - diff --git a/django/newforms/widgets.py b/django/newforms/widgets.py index 076845427d..350b878af9 100644 --- a/django/newforms/widgets.py +++ b/django/newforms/widgets.py @@ -14,6 +14,7 @@ from django.utils.datastructures import MultiValueDict from django.utils.html import escape from django.utils.translation import ugettext from django.utils.encoding import StrAndUnicode, force_unicode +from django.utils.safestring import mark_safe from util import flatatt __all__ = ( @@ -86,8 +87,10 @@ class Input(Widget): def render(self, name, value, attrs=None): if value is None: value = '' final_attrs = self.build_attrs(attrs, type=self.input_type, name=name) - if value != '': final_attrs['value'] = force_unicode(value) # Only add the 'value' attribute if a value is non-empty. - return u'' % flatatt(final_attrs) + if value != '': + # Only add the 'value' attribute if a value is non-empty. + final_attrs['value'] = force_unicode(value) + return mark_safe(u'' % flatatt(final_attrs)) class TextInput(Input): input_type = 'text' @@ -120,7 +123,9 @@ class MultipleHiddenInput(HiddenInput): def render(self, name, value, attrs=None, choices=()): if value is None: value = [] final_attrs = self.build_attrs(attrs, type=self.input_type, name=name) - return u'\n'.join([(u'' % flatatt(dict(value=force_unicode(v), **final_attrs))) for v in value]) + return mark_safe(u'\n'.join([(u'' % + flatatt(dict(value=force_unicode(v), **final_attrs))) + for v in value])) def value_from_datadict(self, data, files, name): if isinstance(data, MultiValueDict): @@ -149,7 +154,8 @@ class Textarea(Widget): if value is None: value = '' value = force_unicode(value) final_attrs = self.build_attrs(attrs, name=name) - return u'%s' % (flatatt(final_attrs), escape(value)) + return mark_safe(u'%s' % (flatatt(final_attrs), + escape(value))) class DateTimeInput(Input): input_type = 'text' @@ -183,8 +189,9 @@ class CheckboxInput(Widget): if result: final_attrs['checked'] = 'checked' if value not in ('', True, False, None): - final_attrs['value'] = force_unicode(value) # Only add the 'value' attribute if a value is non-empty. - return u'' % flatatt(final_attrs) + # Only add the 'value' attribute if a value is non-empty. + final_attrs['value'] = force_unicode(value) + return mark_safe(u'' % flatatt(final_attrs)) def value_from_datadict(self, data, files, name): if name not in data: @@ -205,13 +212,14 @@ class Select(Widget): if value is None: value = '' final_attrs = self.build_attrs(attrs, name=name) output = [u'' % flatatt(final_attrs)] - str_value = force_unicode(value) # Normalize to string. + # Normalize to string. + str_value = force_unicode(value) for option_value, option_label in chain(self.choices, choices): option_value = force_unicode(option_value) selected_html = (option_value == str_value) and u' selected="selected"' or '' output.append(u'' % (escape(option_value), selected_html, escape(force_unicode(option_label)))) output.append(u'') - return u'\n'.join(output) + return mark_safe(u'\n'.join(output)) class NullBooleanSelect(Select): """ @@ -248,7 +256,7 @@ class SelectMultiple(Widget): selected_html = (option_value in str_values) and ' selected="selected"' or '' output.append(u'' % (escape(option_value), selected_html, escape(force_unicode(option_label)))) output.append(u'') - return u'\n'.join(output) + return mark_safe(u'\n'.join(output)) def value_from_datadict(self, data, files, name): if isinstance(data, MultiValueDict): @@ -269,7 +277,8 @@ class RadioInput(StrAndUnicode): self.index = index def __unicode__(self): - return u'' % (self.tag(), self.choice_label) + return mark_safe(u'' % (self.tag(), + self.choice_label)) def is_checked(self): return self.value == self.choice_value @@ -280,7 +289,7 @@ class RadioInput(StrAndUnicode): final_attrs = dict(self.attrs, type='radio', name=self.name, value=self.choice_value) if self.is_checked(): final_attrs['checked'] = 'checked' - return u'' % flatatt(final_attrs) + return mark_safe(u'' % flatatt(final_attrs)) class RadioFieldRenderer(StrAndUnicode): """ @@ -304,7 +313,8 @@ class RadioFieldRenderer(StrAndUnicode): def render(self): """Outputs a
            for this set of radio fields.""" - return u'
              \n%s\n
            ' % u'\n'.join([u'
          • %s
          • ' % force_unicode(w) for w in self]) + return mark_safe(u'
              \n%s\n
            ' % u'\n'.join([u'
          • %s
          • ' + % force_unicode(w) for w in self])) class RadioSelect(Select): @@ -341,7 +351,8 @@ class CheckboxSelectMultiple(SelectMultiple): has_id = attrs and 'id' in attrs final_attrs = self.build_attrs(attrs, name=name) output = [u'
              '] - str_values = set([force_unicode(v) for v in value]) # Normalize to strings. + # Normalize to strings + str_values = set([force_unicode(v) for v in value]) for i, (option_value, option_label) in enumerate(chain(self.choices, choices)): # If an ID attribute was given, add a numeric index as a suffix, # so that the checkboxes don't all have the same ID attribute. @@ -352,7 +363,7 @@ class CheckboxSelectMultiple(SelectMultiple): rendered_cb = cb.render(name, option_value) output.append(u'
            • ' % (rendered_cb, escape(force_unicode(option_label)))) output.append(u'
            ') - return u'\n'.join(output) + return mark_safe(u'\n'.join(output)) def id_for_label(self, id_): # See the comment for RadioSelect.id_for_label() @@ -450,3 +461,4 @@ class SplitDateTimeWidget(MultiWidget): if value: return [value.date(), value.time().replace(microsecond=0)] return [None, None] + diff --git a/django/oldforms/__init__.py b/django/oldforms/__init__.py index 5f7b9fc2ad..fc8727185f 100644 --- a/django/oldforms/__init__.py +++ b/django/oldforms/__init__.py @@ -1,6 +1,7 @@ from django.core import validators from django.core.exceptions import PermissionDenied from django.utils.html import escape +from django.utils.safestring import mark_safe from django.conf import settings from django.utils.translation import ugettext, ungettext from django.utils.encoding import smart_unicode, force_unicode @@ -189,9 +190,9 @@ class FormFieldWrapper(object): def html_error_list(self): if self.errors(): - return '
            • %s
            ' % '
          • '.join([escape(e) for e in self.errors()]) + return mark_safe('
            • %s
            ' % '
          • '.join([escape(e) for e in self.errors()])) else: - return '' + return mark_safe('') def get_id(self): return self.formfield.get_id() @@ -226,7 +227,7 @@ class FormFieldCollection(FormFieldWrapper): return bool(len(self.errors())) def html_combined_error_list(self): - return ''.join([field.html_error_list() for field in self.formfield_dict.values() if hasattr(field, 'errors')]) + return mark_safe(''.join([field.html_error_list() for field in self.formfield_dict.values() if hasattr(field, 'errors')])) class InlineObjectCollection(object): "An object that acts like a sparse list of form field collections." @@ -418,9 +419,9 @@ class TextField(FormField): max_length = u'' if self.max_length: max_length = u'maxlength="%s" ' % self.max_length - return u'' % \ + return mark_safe(u'' % \ (self.input_type, self.get_id(), self.__class__.__name__, self.is_required and u' required' or '', - self.field_name, self.length, escape(data), max_length) + self.field_name, self.length, escape(data), max_length)) def html2python(data): return data @@ -442,9 +443,9 @@ class LargeTextField(TextField): def render(self, data): if data is None: data = '' - return u'' % \ + return mark_safe(u'' % \ (self.get_id(), self.__class__.__name__, self.is_required and u' required' or u'', - self.field_name, self.rows, self.cols, escape(data)) + self.field_name, self.rows, self.cols, escape(data))) class HiddenField(FormField): def __init__(self, field_name, is_required=False, validator_list=None, max_length=None): @@ -453,8 +454,8 @@ class HiddenField(FormField): self.validator_list = validator_list[:] def render(self, data): - return u'' % \ - (self.get_id(), self.field_name, escape(data)) + return mark_safe(u'' % \ + (self.get_id(), self.field_name, escape(data))) class CheckboxField(FormField): def __init__(self, field_name, checked_by_default=False, validator_list=None, is_required=False): @@ -468,9 +469,9 @@ class CheckboxField(FormField): checked_html = '' if data or (data is '' and self.checked_by_default): checked_html = ' checked="checked"' - return u'' % \ + return mark_safe(u'' % \ (self.get_id(), self.__class__.__name__, - self.field_name, checked_html) + self.field_name, checked_html)) def html2python(data): "Convert value from browser ('on' or '') to a Python boolean" @@ -502,7 +503,7 @@ class SelectField(FormField): selected_html = u' selected="selected"' output.append(u' ' % (escape(value), selected_html, force_unicode(escape(display_name)))) output.append(u' ') - return u'\n'.join(output) + return mark_safe(u'\n'.join(output)) def isValidChoice(self, data, form): str_data = smart_unicode(data) @@ -556,7 +557,7 @@ class RadioSelectField(FormField): output = [u'' % (self.ul_class and u' class="%s"' % self.ul_class or u'')] output.extend([u'
          • %s %s
          • ' % (d['field'], d['label']) for d in self.datalist]) output.append(u'
          ') - return u''.join(output) + return mark_safe(u''.join(output)) def __iter__(self): for d in self.datalist: yield d @@ -571,11 +572,11 @@ class RadioSelectField(FormField): datalist.append({ 'value': value, 'name': display_name, - 'field': u'' % \ - (self.get_id() + u'_' + unicode(i), self.field_name, value, selected_html), - 'label': u'' % \ + 'field': mark_safe(u'' % \ + (self.get_id() + u'_' + unicode(i), self.field_name, value, selected_html)), + 'label': mark_safe(u'' % \ (self.get_id() + u'_' + unicode(i), display_name), - }) + )}) return RadioFieldRenderer(datalist, self.ul_class) def isValidChoice(self, data, form): @@ -614,7 +615,7 @@ class SelectMultipleField(SelectField): selected_html = u' selected="selected"' output.append(u' ' % (escape(value), selected_html, force_unicode(escape(choice)))) output.append(u' ') - return u'\n'.join(output) + return mark_safe(u'\n'.join(output)) def isValidChoice(self, field_data, all_data): # data is something like ['1', '2', '3'] @@ -667,7 +668,7 @@ class CheckboxSelectMultipleField(SelectMultipleField): (self.get_id() + escape(value), self.__class__.__name__, field_name, checked_html, self.get_id() + escape(value), choice)) output.append(u'
        ') - return u'\n'.join(output) + return mark_safe(u'\n'.join(output)) #################### # FILE UPLOADS # @@ -688,8 +689,8 @@ class FileUploadField(FormField): raise validators.CriticalValidationError, ugettext("The submitted file is empty.") def render(self, data): - return u'' % \ - (self.get_id(), self.__class__.__name__, self.field_name) + return mark_safe(u'' % \ + (self.get_id(), self.__class__.__name__, self.field_name)) def html2python(data): if data is None: diff --git a/django/template/__init__.py b/django/template/__init__.py index 1cfd85be06..1fd3171c12 100644 --- a/django/template/__init__.py +++ b/django/template/__init__.py @@ -57,6 +57,8 @@ from django.utils.functional import curry, Promise from django.utils.text import smart_split from django.utils.encoding import smart_unicode, force_unicode from django.utils.translation import ugettext as _ +from django.utils.safestring import SafeData, EscapeData, mark_safe, mark_for_escaping +from django.utils.html import escape __all__ = ('Template', 'Context', 'RequestContext', 'compile_string') @@ -595,7 +597,16 @@ class FilterExpression(object): arg_vals.append(arg) else: arg_vals.append(arg.resolve(context)) - obj = func(obj, *arg_vals) + if getattr(func, 'needs_autoescape', False): + new_obj = func(obj, autoescape=context.autoescape, *arg_vals) + else: + new_obj = func(obj, *arg_vals) + if getattr(func, 'is_safe', False) and isinstance(obj, SafeData): + obj = mark_safe(new_obj) + elif isinstance(obj, EscapeData): + obj = mark_for_escaping(new_obj) + else: + obj = new_obj return obj def args_check(name, func, provided): @@ -637,7 +648,7 @@ def resolve_variable(path, context): """ Returns the resolved variable, which may contain attribute syntax, within the given context. - + Deprecated; use the Variable class instead. """ return Variable(path).resolve(context) @@ -647,7 +658,7 @@ class Variable(object): A template variable, resolvable against a given context. The variable may be a hard-coded string (if it begins and ends with single or double quote marks):: - + >>> c = {'article': {'section':'News'}} >>> Variable('article.section').resolve(c) u'News' @@ -662,25 +673,25 @@ class Variable(object): (The example assumes VARIABLE_ATTRIBUTE_SEPARATOR is '.') """ - + def __init__(self, var): self.var = var self.literal = None self.lookups = None - + try: # First try to treat this variable as a number. # - # Note that this could cause an OverflowError here that we're not + # Note that this could cause an OverflowError here that we're not # catching. Since this should only happen at compile time, that's # probably OK. self.literal = float(var) - + # So it's a float... is it an int? If the original value contained a # dot or an "e" then it was a float, not an int. if '.' not in var and 'e' not in var.lower(): self.literal = int(self.literal) - + # "2." is invalid if var.endswith('.'): raise ValueError @@ -691,12 +702,12 @@ class Variable(object): # we're also dealing with a literal. if var[0] in "\"'" and var[0] == var[-1]: self.literal = var[1:-1] - + else: # Otherwise we'll set self.lookups so that resolve() knows we're # dealing with a bonafide variable self.lookups = tuple(var.split(VARIABLE_ATTRIBUTE_SEPARATOR)) - + def resolve(self, context): """Resolve this variable against a given context.""" if self.lookups is not None: @@ -705,18 +716,18 @@ class Variable(object): else: # We're dealing with a literal, so it's already been "resolved" return self.literal - + def __repr__(self): return "<%s: %r>" % (self.__class__.__name__, self.var) - + def __str__(self): return self.var def _resolve_lookup(self, context): """ Performs resolution of a real variable (i.e. not a literal) against the - given context. - + given context. + As indicated by the method's name, this method is an implementation detail and shouldn't be called by external code. Use Variable.resolve() instead. @@ -757,14 +768,7 @@ class Variable(object): current = settings.TEMPLATE_STRING_IF_INVALID else: raise - - if isinstance(current, (basestring, Promise)): - try: - current = force_unicode(current) - except UnicodeDecodeError: - # Failing to convert to unicode can happen sometimes (e.g. debug - # tracebacks). So we allow it in this particular instance. - pass + return current class Node(object): @@ -838,16 +842,31 @@ class VariableNode(Node): return "" % self.filter_expression def render(self, context): - return self.filter_expression.resolve(context) + try: + output = force_unicode(self.filter_expression.resolve(context)) + except UnicodeDecodeError: + # Unicode conversion can fail sometimes for reasons out of our + # control (e.g. exception rendering). In that case, we fail quietly. + return '' + if (context.autoescape and not isinstance(output, SafeData)) or isinstance(output, EscapeData): + return force_unicode(escape(output)) + else: + return force_unicode(output) class DebugVariableNode(VariableNode): def render(self, context): try: - return self.filter_expression.resolve(context) + output = force_unicode(self.filter_expression.resolve(context)) except TemplateSyntaxError, e: if not hasattr(e, 'source'): e.source = self.source raise + except UnicodeDecodeError: + return '' + if (context.autoescape and not isinstance(output, SafeData)) or isinstance(output, EscapeData): + return escape(output) + else: + return output def generic_tag_compiler(params, defaults, name, node_class, parser, token): "Returns a template.Node subclass." @@ -961,7 +980,8 @@ class Library(object): else: t = get_template(file_name) self.nodelist = t.nodelist - return self.nodelist.render(context_class(dict)) + return self.nodelist.render(context_class(dict, + autoescape=context.autoescape)) compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, InclusionNode) compile_func.__doc__ = func.__doc__ diff --git a/django/template/context.py b/django/template/context.py index 51cd88b7e9..017d2d84b1 100644 --- a/django/template/context.py +++ b/django/template/context.py @@ -9,9 +9,11 @@ class ContextPopException(Exception): class Context(object): "A stack container for variable context" - def __init__(self, dict_=None): + + def __init__(self, dict_=None, autoescape=True): dict_ = dict_ or {} self.dicts = [dict_] + self.autoescape = autoescape def __repr__(self): return repr(self.dicts) @@ -97,3 +99,4 @@ class RequestContext(Context): processors = tuple(processors) for processor in get_standard_processors() + processors: self.update(processor(request)) + diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py index f1fcf5fe90..7d4a72efb3 100644 --- a/django/template/defaultfilters.py +++ b/django/template/defaultfilters.py @@ -1,11 +1,13 @@ -"Default variable filters" +"""Default variable filters.""" + +import re +import random as random_module from django.template import Variable, Library from django.conf import settings from django.utils.translation import ugettext, ungettext -from django.utils.encoding import force_unicode, smart_str, iri_to_uri -import re -import random as random_module +from django.utils.encoding import force_unicode, iri_to_uri +from django.utils.safestring import mark_safe, SafeData register = Library() @@ -28,6 +30,9 @@ def stringfilter(func): # Include a reference to the real function (used to check original # arguments by the template parser). _dec._decorated_function = getattr(func, '_decorated_function', func) + for attr in ('is_safe', 'needs_autoescape'): + if hasattr(func, attr): + setattr(_dec, attr, getattr(func, attr)) return _dec ################### @@ -36,39 +41,51 @@ def stringfilter(func): def addslashes(value): - "Adds slashes - useful for passing strings to JavaScript, for example." + """Adds slashes - useful for passing strings to JavaScript, for example.""" return value.replace('\\', '\\\\').replace('"', '\\"').replace("'", "\\'") +addslashes.is_safe = True addslashes = stringfilter(addslashes) def capfirst(value): - "Capitalizes the first character of the value" + """Capitalizes the first character of the value.""" return value and value[0].upper() + value[1:] +capfirst.is_safe=True capfirst = stringfilter(capfirst) def fix_ampersands(value): - "Replaces ampersands with ``&`` entities" + """Replaces ampersands with ``&`` entities.""" from django.utils.html import fix_ampersands return fix_ampersands(value) +fix_ampersands.is_safe=True fix_ampersands = stringfilter(fix_ampersands) def floatformat(text, arg=-1): """ - If called without an argument, displays a floating point - number as 34.2 -- but only if there's a point to be displayed. - With a positive numeric argument, it displays that many decimal places - always. - With a negative numeric argument, it will display that many decimal - places -- but only if there's places to be displayed. - Examples: + Displays a float to a specified number of decimal places. + + If called without an argument, it displays the floating point number with + one decimal place -- but only if there's a decimal place to be displayed: * num1 = 34.23234 * num2 = 34.00000 - * num1|floatformat results in 34.2 - * num2|floatformat is 34 - * num1|floatformat:3 is 34.232 - * num2|floatformat:3 is 34.000 - * num1|floatformat:-3 is 34.232 - * num2|floatformat:-3 is 34 + * num3 = 34.26000 + * {{ num1|floatformat }} displays "34.2" + * {{ num2|floatformat }} displays "34" + * {{ num3|floatformat }} displays "34.3" + + If arg is positive, it will always display exactly arg number of decimal + places: + + * {{ num1|floatformat:3 }} displays "34.232" + * {{ num2|floatformat:3 }} displays "34.000" + * {{ num3|floatformat:3 }} displays "34.260" + + If arg is negative, it will display arg number of decimal places -- but + only if there are places to be displayed: + + * {{ num1|floatformat:"-3" }} displays "34.232" + * {{ num2|floatformat:"-3" }} displays "34" + * {{ num3|floatformat:"-3" }} displays "34.260" """ try: f = float(text) @@ -80,54 +97,68 @@ def floatformat(text, arg=-1): return force_unicode(f) m = f - int(f) if not m and d < 0: - return u'%d' % int(f) + return mark_safe(u'%d' % int(f)) else: formatstr = u'%%.%df' % abs(d) - return formatstr % f + return mark_safe(formatstr % f) +floatformat.is_safe = True def iriencode(value): - "Escapes an IRI value for use in a URL" + """Escapes an IRI value for use in a URL.""" return force_unicode(iri_to_uri(value)) iriencode = stringfilter(iriencode) -def linenumbers(value): - "Displays text with line numbers" +def linenumbers(value, autoescape=None): + """Displays text with line numbers.""" from django.utils.html import escape lines = value.split(u'\n') - # Find the maximum width of the line count, for use with zero padding string format command + # Find the maximum width of the line count, for use with zero padding + # string format command width = unicode(len(unicode(len(lines)))) - for i, line in enumerate(lines): - lines[i] = (u"%0" + width + u"d. %s") % (i + 1, escape(line)) - return u'\n'.join(lines) + if not autoescape or isinstance(value, SafeData): + for i, line in enumerate(lines): + lines[i] = (u"%0" + width + u"d. %s") % (i + 1, line) + else: + for i, line in enumerate(lines): + lines[i] = (u"%0" + width + u"d. %s") % (i + 1, escape(line)) + return mark_safe(u'\n'.join(lines)) +linenumbers.is_safe = True +linenumbers.needs_autoescape = True linenumbers = stringfilter(linenumbers) def lower(value): - "Converts a string into all lowercase" + """Converts a string into all lowercase.""" return value.lower() +lower.is_safe = True lower = stringfilter(lower) def make_list(value): """ - Returns the value turned into a list. For an integer, it's a list of - digits. For a string, it's a list of characters. + Returns the value turned into a list. + + For an integer, it's a list of digits. + For a string, it's a list of characters. """ return list(value) +make_list.is_safe = False make_list = stringfilter(make_list) def slugify(value): """ - Normalizes string, converts to lowercase, removes non-alpha chars and - converts spaces to hyphens. + Normalizes string, converts to lowercase, removes non-alpha characters, + and converts spaces to hyphens. """ import unicodedata value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore') value = unicode(re.sub('[^\w\s-]', '', value).strip().lower()) - return re.sub('[-\s]+', '-', value) + return mark_safe(re.sub('[-\s]+', '-', value)) +slugify.is_safe = True slugify = stringfilter(slugify) def stringformat(value, arg): """ - Formats the variable according to the argument, a string formatting specifier. + Formats the variable according to the arg, a string formatting specifier. + This specifier uses Python string formating syntax, with the exception that the leading "%" is dropped. @@ -138,31 +169,34 @@ def stringformat(value, arg): return (u"%" + unicode(arg)) % value except (ValueError, TypeError): return u"" +stringformat.is_safe = True def title(value): - "Converts a string into titlecase" + """Converts a string into titlecase.""" return re.sub("([a-z])'([A-Z])", lambda m: m.group(0).lower(), value.title()) +title.is_safe = True title = stringfilter(title) def truncatewords(value, arg): """ - Truncates a string after a certain number of words + Truncates a string after a certain number of words. - Argument: Number of words to truncate after + Argument: Number of words to truncate after. """ from django.utils.text import truncate_words try: length = int(arg) - except ValueError: # invalid literal for int() + except ValueError: # Invalid literal for int(). return value # Fail silently. return truncate_words(value, length) +truncatewords.is_safe = True truncatewords = stringfilter(truncatewords) def truncatewords_html(value, arg): """ - Truncates HTML after a certain number of words + Truncates HTML after a certain number of words. - Argument: Number of words to truncate after + Argument: Number of words to truncate after. """ from django.utils.text import truncate_html_words try: @@ -170,77 +204,94 @@ def truncatewords_html(value, arg): except ValueError: # invalid literal for int() return value # Fail silently. return truncate_html_words(value, length) +truncatewords_html.is_safe = True truncatewords_html = stringfilter(truncatewords_html) def upper(value): - "Converts a string into all uppercase" + """Converts a string into all uppercase.""" return value.upper() +upper.is_safe = False upper = stringfilter(upper) def urlencode(value): - "Escapes a value for use in a URL" + """Escapes a value for use in a URL.""" from django.utils.http import urlquote return urlquote(value) +urlencode.is_safe = False urlencode = stringfilter(urlencode) -def urlize(value): - "Converts URLs in plain text into clickable links" +def urlize(value, autoescape=None): + """Converts URLs in plain text into clickable links.""" from django.utils.html import urlize - return urlize(value, nofollow=True) + return mark_safe(urlize(value, nofollow=True, autoescape=autoescape)) +urlize.is_safe=True +urlize.needs_autoescape = True urlize = stringfilter(urlize) def urlizetrunc(value, limit): """ - Converts URLs into clickable links, truncating URLs to the given character limit, - and adding 'rel=nofollow' attribute to discourage spamming. + Converts URLs into clickable links, truncating URLs to the given character + limit, and adding 'rel=nofollow' attribute to discourage spamming. Argument: Length to truncate URLs to. """ from django.utils.html import urlize - return urlize(value, trim_url_limit=int(limit), nofollow=True) + return mark_safe(urlize(value, trim_url_limit=int(limit), nofollow=True)) +urlizetrunc.is_safe = True urlizetrunc = stringfilter(urlizetrunc) def wordcount(value): - "Returns the number of words" + """Returns the number of words.""" return len(value.split()) +wordcount.is_safe = False wordcount = stringfilter(wordcount) def wordwrap(value, arg): """ - Wraps words at specified line length + Wraps words at specified line length. Argument: number of characters to wrap the text at. """ from django.utils.text import wrap return wrap(value, int(arg)) +wordwrap.is_safe = True wordwrap = stringfilter(wordwrap) def ljust(value, arg): """ - Left-aligns the value in a field of a given width + Left-aligns the value in a field of a given width. - Argument: field size + Argument: field size. """ return value.ljust(int(arg)) +ljust.is_safe = True ljust = stringfilter(ljust) def rjust(value, arg): """ - Right-aligns the value in a field of a given width + Right-aligns the value in a field of a given width. - Argument: field size + Argument: field size. """ return value.rjust(int(arg)) +rjust.is_safe = True rjust = stringfilter(rjust) def center(value, arg): - "Centers the value in a field of a given width" + """Centers the value in a field of a given width.""" return value.center(int(arg)) +center.is_safe = True center = stringfilter(center) def cut(value, arg): - "Removes all values of arg from the given string" - return value.replace(arg, u'') + """ + Removes all values of arg from the given string. + """ + safe = isinstance(value, SafeData) + value = value.replace(arg, u'') + if safe and arg != ';': + return mark_safe(value) + return value cut = stringfilter(cut) ################### @@ -248,31 +299,62 @@ cut = stringfilter(cut) ################### def escape(value): - "Escapes a string's HTML" - from django.utils.html import escape - return escape(value) + """ + Marks the value as a string that should not be auto-escaped. + """ + from django.utils.safestring import mark_for_escaping + return mark_for_escaping(value) +escape.is_safe = True escape = stringfilter(escape) -def linebreaks(value): +def force_escape(value): + """ + Escapes a string's HTML. This returns a new string containing the escaped + characters (as opposed to "escape", which marks the content for later + possible escaping). + """ + from django.utils.html import escape + return mark_safe(escape(value)) +escape = stringfilter(escape) +force_escape.is_safe = True + +def linebreaks(value, autoescape=None): """ Replaces line breaks in plain text with appropriate HTML; a single newline becomes an HTML line break (``
        ``) and a new line followed by a blank line becomes a paragraph break (``

        ``). """ from django.utils.html import linebreaks - return linebreaks(value) + autoescape = autoescape and not isinstance(value, SafeData) + return mark_safe(linebreaks(value, autoescape)) +linebreaks.is_safe = True +linebreaks.needs_autoescape = True linebreaks = stringfilter(linebreaks) -def linebreaksbr(value): +def linebreaksbr(value, autoescape=None): """ Converts all newlines in a piece of plain text to HTML line breaks (``
        ``). """ - return value.replace('\n', '
        ') + if autoescape and not isinstance(value, SafeData): + from django.utils.html import escape + value = escape(value) + return mark_safe(value.replace('\n', '
        ')) +linebreaksbr.is_safe = True +linebreaksbr.needs_autoescape = True linebreaksbr = stringfilter(linebreaksbr) +def safe(value): + """ + Marks the value as a string that should not be auto-escaped. + """ + from django.utils.safestring import mark_safe + return mark_safe(value) +safe.is_safe = True +safe = stringfilter(safe) + def removetags(value, tags): - "Removes a space separated list of [X]HTML tags from the output" + """Removes a space separated list of [X]HTML tags from the output.""" tags = [re.escape(tag) for tag in tags.split()] tags_re = u'(%s)' % u'|'.join(tags) starttag_re = re.compile(ur'<%s(/?>|(\s+[^>]*>))' % tags_re, re.U) @@ -280,12 +362,14 @@ def removetags(value, tags): value = starttag_re.sub(u'', value) value = endtag_re.sub(u'', value) return value +removetags.is_safe = True removetags = stringfilter(removetags) def striptags(value): - "Strips all [X]HTML tags" + """Strips all [X]HTML tags.""" from django.utils.html import strip_tags return strip_tags(value) +striptags.is_safe = True striptags = stringfilter(striptags) ################### @@ -301,6 +385,7 @@ def dictsort(value, arg): decorated = [(var_resolve(item), item) for item in value] decorated.sort() return [item[1] for item in decorated] +dictsort.is_safe = False def dictsortreversed(value, arg): """ @@ -312,32 +397,44 @@ def dictsortreversed(value, arg): decorated.sort() decorated.reverse() return [item[1] for item in decorated] +dictsortreversed.is_safe = False def first(value): - "Returns the first item in a list" + """Returns the first item in a list.""" try: return value[0] except IndexError: return u'' +first.is_safe = True def join(value, arg): - "Joins a list with a string, like Python's ``str.join(list)``" + """Joins a list with a string, like Python's ``str.join(list)``.""" try: - return arg.join(map(force_unicode, value)) + data = arg.join(map(force_unicode, value)) except AttributeError: # fail silently but nicely return value + safe_args = reduce(lambda lhs, rhs: lhs and isinstance(rhs, SafeData), + value, True) + if safe_args: + return mark_safe(data) + else: + return data +join.is_safe = True def length(value): - "Returns the length of the value - useful for lists" + """Returns the length of the value - useful for lists.""" return len(value) +length.is_safe = True def length_is(value, arg): - "Returns a boolean of whether the value's length is the argument" + """Returns a boolean of whether the value's length is the argument.""" return len(value) == int(arg) +length_is.is_safe = True def random(value): - "Returns a random item from the list" + """Returns a random item from the list.""" return random_module.choice(value) +random.is_safe = True def slice_(value, arg): """ @@ -358,8 +455,9 @@ def slice_(value, arg): except (ValueError, TypeError): return value # Fail silently. +slice_.is_safe = True -def unordered_list(value): +def unordered_list(value, autoescape=None): """ Recursively takes a self-nested list and returns an HTML unordered list -- WITHOUT opening and closing
          tags. @@ -380,6 +478,11 @@ def unordered_list(value):
        """ + if autoescape: + from django.utils.html import conditional_escape + escaper = conditional_escape + else: + escaper = lambda x: x def convert_old_style_list(list_): """ Converts old style lists to the new easier to understand format. @@ -416,7 +519,7 @@ def unordered_list(value): sublist = '' sublist_item = None if isinstance(title, (list, tuple)): - sublist_item = title + sublist_item = title title = '' elif i < list_length - 1: next_item = list_[i+1] @@ -424,25 +527,28 @@ def unordered_list(value): # The next item is a sub-list. sublist_item = next_item # We've processed the next item now too. - i += 1 + i += 1 if sublist_item: sublist = _helper(sublist_item, tabs+1) sublist = '\n%s
          \n%s\n%s
        \n%s' % (indent, sublist, indent, indent) - output.append('%s
      • %s%s
      • ' % (indent, force_unicode(title), - sublist)) + output.append('%s
      • %s%s
      • ' % (indent, + escaper(force_unicode(title)), sublist)) i += 1 return '\n'.join(output) - value, converted = convert_old_style_list(value) - return _helper(value) + value, converted = convert_old_style_list(value) + return mark_safe(_helper(value)) +unordered_list.is_safe = True +unordered_list.needs_autoescape = True ################### # INTEGERS # ################### def add(value, arg): - "Adds the arg to the value" + """Adds the arg to the value.""" return int(value) + int(arg) +add.is_safe = False def get_digit(value, arg): """ @@ -462,40 +568,44 @@ def get_digit(value, arg): return int(str(value)[-arg]) except IndexError: return 0 +get_digit.is_safe = False ################### # DATES # ################### def date(value, arg=None): - "Formats a date according to the given format" + """Formats a date according to the given format.""" from django.utils.dateformat import format if not value: return u'' if arg is None: arg = settings.DATE_FORMAT return format(value, arg) +date.is_safe = False def time(value, arg=None): - "Formats a time according to the given format" + """Formats a time according to the given format.""" from django.utils.dateformat import time_format if value in (None, u''): return u'' if arg is None: arg = settings.TIME_FORMAT return time_format(value, arg) +time.is_safe = False def timesince(value, arg=None): - 'Formats a date as the time since that date (i.e. "4 days, 6 hours")' + """Formats a date as the time since that date (i.e. "4 days, 6 hours").""" from django.utils.timesince import timesince if not value: return u'' if arg: return timesince(arg, value) return timesince(value) +timesince.is_safe = False def timeuntil(value, arg=None): - 'Formats a date as the time until that date (i.e. "4 days, 6 hours")' + """Formats a date as the time until that date (i.e. "4 days, 6 hours").""" from django.utils.timesince import timesince from datetime import datetime if not value: @@ -503,24 +613,28 @@ def timeuntil(value, arg=None): if arg: return timesince(arg, value) return timesince(datetime.now(), value) +timeuntil.is_safe = False ################### # LOGIC # ################### def default(value, arg): - "If value is unavailable, use given default" + """If value is unavailable, use given default.""" return value or arg +default.is_safe = False def default_if_none(value, arg): - "If value is None, use given default" + """If value is None, use given default.""" if value is None: return arg return value +default_if_none.is_safe = False def divisibleby(value, arg): - "Returns true if the value is devisible by the argument" + """Returns True if the value is devisible by the argument.""" return int(value) % int(arg) == 0 +divisibleby.is_safe = False def yesno(value, arg=None): """ @@ -544,13 +658,15 @@ def yesno(value, arg=None): return value # Invalid arg. try: yes, no, maybe = bits - except ValueError: # unpack list of wrong size (no "maybe" value provided) + except ValueError: + # Unpack list of wrong size (no "maybe" value provided). yes, no, maybe = bits[0], bits[1], bits[1] if value is None: return maybe if value: return yes return no +yesno.is_safe = False ################### # MISC # @@ -558,8 +674,8 @@ def yesno(value, arg=None): def filesizeformat(bytes): """ - Format the value like a 'human-readable' file size (i.e. 13 KB, 4.1 MB, 102 - bytes, etc). + Formats the value like a 'human-readable' file size (i.e. 13 KB, 4.1 MB, + 102 bytes, etc). """ try: bytes = float(bytes) @@ -573,13 +689,30 @@ def filesizeformat(bytes): if bytes < 1024 * 1024 * 1024: return ugettext("%.1f MB") % (bytes / (1024 * 1024)) return ugettext("%.1f GB") % (bytes / (1024 * 1024 * 1024)) +filesizeformat.is_safe = True def pluralize(value, arg=u's'): """ - Returns a plural suffix if the value is not 1, for '1 vote' vs. '2 votes' - By default, 's' is used as a suffix; if an argument is provided, that string - is used instead. If the provided argument contains a comma, the text before - the comma is used for the singular case. + Returns a plural suffix if the value is not 1. By default, 's' is used as + the suffix: + + * If value is 0, vote{{ value|pluralize }} displays "0 votes". + * If value is 1, vote{{ value|pluralize }} displays "1 vote". + * If value is 2, vote{{ value|pluralize }} displays "2 votes". + + If an argument is provided, that string is used instead: + + * If value is 0, class{{ value|pluralize:"es" }} displays "0 classes". + * If value is 1, class{{ value|pluralize:"es" }} displays "1 class". + * If value is 2, class{{ value|pluralize:"es" }} displays "2 classes". + + If the provided argument contains a comma, the text before the comma is + used for the singular case and the text after the comma is used for the + plural case: + + * If value is 0, cand{{ value|pluralize:"y,ies" }} displays "0 candies". + * If value is 1, cand{{ value|pluralize:"y,ies" }} displays "1 candy". + * If value is 2, cand{{ value|pluralize:"y,ies" }} displays "2 candies". """ if not u',' in arg: arg = u',' + arg @@ -591,28 +724,31 @@ def pluralize(value, arg=u's'): try: if int(value) != 1: return plural_suffix - except ValueError: # invalid string that's not a number + except ValueError: # Invalid string that's not a number. pass - except TypeError: # value isn't a string or a number; maybe it's a list? + except TypeError: # Value isn't a string or a number; maybe it's a list? try: if len(value) != 1: return plural_suffix - except TypeError: # len() of unsized object + except TypeError: # len() of unsized object. pass return singular_suffix +pluralize.is_safe = False def phone2numeric(value): - "Takes a phone number and converts it in to its numerical equivalent" + """Takes a phone number and converts it in to its numerical equivalent.""" from django.utils.text import phone2numeric return phone2numeric(value) +phone2numeric.is_safe = True def pprint(value): - "A wrapper around pprint.pprint -- for debugging, really" + """A wrapper around pprint.pprint -- for debugging, really.""" from pprint import pformat try: return pformat(value) except Exception, e: return u"Error in formatting: %s" % force_unicode(e, errors="replace") +pprint.is_safe = True # Syntax: register.filter(name of filter, callback) register.filter(add) @@ -631,6 +767,7 @@ register.filter(filesizeformat) register.filter(first) register.filter(fix_ampersands) register.filter(floatformat) +register.filter(force_escape) register.filter(get_digit) register.filter(iriencode) register.filter(join) @@ -648,6 +785,7 @@ register.filter(pprint) register.filter(removetags) register.filter(random) register.filter(rjust) +register.filter(safe) register.filter('slice', slice_) register.filter(slugify) register.filter(stringformat) diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py index 2ba23a0465..d91e30bbb5 100644 --- a/django/template/defaulttags.py +++ b/django/template/defaulttags.py @@ -1,4 +1,12 @@ -"Default tags used by the template system, available to all templates." +"""Default tags used by the template system, available to all templates.""" + +import sys +import re +from itertools import cycle as itertools_cycle +try: + reversed +except NameError: + from django.utils.itercompat import reversed # Python 2.3 fallback from django.template import Node, NodeList, Template, Context, Variable from django.template import TemplateSyntaxError, VariableDoesNotExist, BLOCK_TAG_START, BLOCK_TAG_END, VARIABLE_TAG_START, VARIABLE_TAG_END, SINGLE_BRACE_START, SINGLE_BRACE_END, COMMENT_TAG_START, COMMENT_TAG_END @@ -6,30 +14,36 @@ from django.template import get_library, Library, InvalidTemplateLibrary from django.conf import settings from django.utils.encoding import smart_str, smart_unicode from django.utils.itercompat import groupby -import sys -import re - -try: - reversed -except NameError: - from django.utils.itercompat import reversed # Python 2.3 fallback +from django.utils.safestring import mark_safe register = Library() +class AutoEscapeControlNode(Node): + """Implements the actions of the autoescape tag.""" + def __init__(self, setting, nodelist): + self.setting, self.nodelist = setting, nodelist + + def render(self, context): + old_setting = context.autoescape + context.autoescape = self.setting + output = self.nodelist.render(context) + context.autoescape = old_setting + if self.setting: + return mark_safe(output) + else: + return output + class CommentNode(Node): def render(self, context): return '' class CycleNode(Node): def __init__(self, cyclevars, variable_name=None): - self.cyclevars = cyclevars - self.cyclevars_len = len(cyclevars) - self.counter = -1 + self.cycle_iter = itertools_cycle(cyclevars) self.variable_name = variable_name def render(self, context): - self.counter += 1 - value = self.cyclevars[self.counter % self.cyclevars_len] + value = self.cycle_iter.next() value = Variable(value).resolve(context) if self.variable_name: context[self.variable_name] = value @@ -49,7 +63,7 @@ class FilterNode(Node): def render(self, context): output = self.nodelist.render(context) - # apply filters + # Apply filters. context.update({'var': output}) filtered = self.filter_expr.resolve(context) context.pop() @@ -81,7 +95,8 @@ class ForNode(Node): else: reversed = '' return "" % \ - (', '.join( self.loopvars ), self.sequence, len(self.nodelist_loop), reversed) + (', '.join(self.loopvars), self.sequence, len(self.nodelist_loop), + reversed) def __iter__(self): for node in self.nodelist_loop: @@ -115,19 +130,20 @@ class ForNode(Node): unpack = len(self.loopvars) > 1 for i, item in enumerate(values): context['forloop'] = { - # shortcuts for current loop iteration number + # Shortcuts for current loop iteration number. 'counter0': i, 'counter': i+1, - # reverse counter iteration numbers + # Reverse counter iteration numbers. 'revcounter': len_values - i, 'revcounter0': len_values - i - 1, - # boolean values designating first and last times through loop + # Boolean values designating first and last times through loop. 'first': (i == 0), 'last': (i == len_values - 1), 'parentloop': parentloop, } if unpack: - # If there are multiple loop variables, unpack the item into them. + # If there are multiple loop variables, unpack the item into + # them. context.update(dict(zip(self.loopvars, item))) else: context[self.loopvars[0]] = item @@ -154,8 +170,8 @@ class IfChangedNode(Node): self._last_seen = None try: if self._varlist: - # Consider multiple parameters. - # This automatically behaves like a OR evaluation of the multiple variables. + # Consider multiple parameters. This automatically behaves + # like an OR evaluation of the multiple variables. compare_to = [var.resolve(context) for var in self._varlist] else: compare_to = self.nodelist.render(context) @@ -249,13 +265,17 @@ class RegroupNode(Node): def render(self, context): obj_list = self.target.resolve(context, True) - if obj_list == None: # target_var wasn't found in context; fail silently + if obj_list == None: + # target variable wasn't found in context; fail silently. context[self.var_name] = [] return '' - # List of dictionaries in the format + # List of dictionaries in the format: # {'grouper': 'key', 'list': [list of contents]}. - context[self.var_name] = [{'grouper':key, 'list':list(val)} for key, val in - groupby(obj_list, lambda v, f=self.expression.resolve: f(v, True))] + context[self.var_name] = [ + {'grouper': key, 'list': list(val)} + for key, val in + groupby(obj_list, lambda v, f=self.expression.resolve: f(v, True)) + ] return '' def include_is_allowed(filepath): @@ -339,13 +359,15 @@ class URLNode(Node): def render(self, context): from django.core.urlresolvers import reverse, NoReverseMatch args = [arg.resolve(context) for arg in self.args] - kwargs = dict([(smart_str(k,'ascii'), v.resolve(context)) for k, v in self.kwargs.items()]) + kwargs = dict([(smart_str(k,'ascii'), v.resolve(context)) + for k, v in self.kwargs.items()]) try: return reverse(self.view_name, args=args, kwargs=kwargs) except NoReverseMatch: try: project_name = settings.SETTINGS_MODULE.split('.')[0] - return reverse(project_name + '.' + self.view_name, args=args, kwargs=kwargs) + return reverse(project_name + '.' + self.view_name, + args=args, kwargs=kwargs) except NoReverseMatch: return '' @@ -386,10 +408,26 @@ class WithNode(Node): context.pop() return output +#@register.tag +def autoescape(parser, token): + """ + Force autoescape behaviour for this block. + """ + args = token.contents.split() + if len(args) != 2: + raise TemplateSyntaxError("'Autoescape' tag requires exactly one argument.") + arg = args[1] + if arg not in (u'on', u'off'): + raise TemplateSyntaxError("'Autoescape' argument should be 'on' or 'off'") + nodelist = parser.parse(('endautoescape',)) + parser.delete_first_token() + return AutoEscapeControlNode((arg == 'on'), nodelist) +autoescape = register.tag(autoescape) + #@register.tag def comment(parser, token): """ - Ignore everything between ``{% comment %}`` and ``{% endcomment %}`` + Ignores everything between ``{% comment %}`` and ``{% endcomment %}``. """ parser.skip_past('endcomment') return CommentNode() @@ -398,7 +436,7 @@ comment = register.tag(comment) #@register.tag def cycle(parser, token): """ - Cycle among the given strings each time this tag is encountered + Cycles among the given strings each time this tag is encountered. Within a loop, cycles among the given strings each time through the loop:: @@ -417,14 +455,14 @@ def cycle(parser, token): ... You can use any number of values, seperated by spaces. Commas can also - be used to separate values; if a comma is used, the cycle values are + be used to separate values; if a comma is used, the cycle values are interpreted as literal strings. """ - # Note: This returns the exact same node on each {% cycle name %} call; that - # is, the node object returned from {% cycle a b c as name %} and the one - # returned from {% cycle name %} are the exact same object. This shouldn't - # cause problems (heh), but if it does, now you know. + # Note: This returns the exact same node on each {% cycle name %} call; + # that is, the node object returned from {% cycle a b c as name %} and the + # one returned from {% cycle name %} are the exact same object. This + # shouldn't cause problems (heh), but if it does, now you know. # # Ugly hack warning: this stuffs the named template dict into parser so # that names are only unique within each template (as opposed to using @@ -442,10 +480,11 @@ def cycle(parser, token): args[1:2] = ['"%s"' % arg for arg in args[1].split(",")] if len(args) == 2: - # {% cycle foo %} case + # {% cycle foo %} case. name = args[1] if not hasattr(parser, '_namedCycleNodes'): - raise TemplateSyntaxError("No named cycles in template: '%s' is not defined" % name) + raise TemplateSyntaxError("No named cycles in template." + " '%s' is not defined" % name) if not name in parser._namedCycleNodes: raise TemplateSyntaxError("Named cycle '%s' does not exist" % name) return parser._namedCycleNodes[name] @@ -463,7 +502,8 @@ cycle = register.tag(cycle) def debug(parser, token): """ - Output a whole load of debugging information, including the current context and imported modules. + Outputs a whole load of debugging information, including the current + context and imported modules. Sample usage:: @@ -477,19 +517,22 @@ debug = register.tag(debug) #@register.tag(name="filter") def do_filter(parser, token): """ - Filter the contents of the blog through variable filters. + Filters the contents of the blog through variable filters. Filters can also be piped through each other, and they can have arguments -- just like in variable syntax. Sample usage:: - {% filter escape|lower %} + {% filter force_escape|lower %} This text will be HTML-escaped, and will appear in lowercase. {% endfilter %} """ _, rest = token.contents.split(None, 1) filter_expr = parser.compile_filter("var|%s" % (rest)) + for func, unused in filter_expr.filters: + if getattr(func, '_decorated_function', func).__name__ in ('escape', 'safe'): + raise TemplateSyntaxError('"filter %s" is not permitted. Use the "autoescape" tag instead.' % func.__name__) nodelist = parser.parse(('endfilter',)) parser.delete_first_token() return FilterNode(filter_expr, nodelist) @@ -526,14 +569,15 @@ def firstof(parser, token): """ bits = token.split_contents()[1:] if len(bits) < 1: - raise TemplateSyntaxError, "'firstof' statement requires at least one argument" + raise TemplateSyntaxError("'firstof' statement requires at least one" + " argument") return FirstOfNode(bits) firstof = register.tag(firstof) #@register.tag(name="for") def do_for(parser, token): """ - Loop over each item in an array. + Loops over each item in an array. For example, to display a list of athletes given ``athlete_list``:: @@ -545,9 +589,9 @@ def do_for(parser, token): You can loop over a list in reverse by using ``{% for obj in list reversed %}``. - + You can also unpack multiple values from a two-dimensional array:: - + {% for key,value in dict.items %} {{ key }}: {{ value }} {% endfor %} @@ -572,17 +616,20 @@ def do_for(parser, token): """ bits = token.contents.split() if len(bits) < 4: - raise TemplateSyntaxError, "'for' statements should have at least four words: %s" % token.contents + raise TemplateSyntaxError("'for' statements should have at least four" + " words: %s" % token.contents) reversed = bits[-1] == 'reversed' in_index = reversed and -3 or -2 if bits[in_index] != 'in': - raise TemplateSyntaxError, "'for' statements should use the format 'for x in y': %s" % token.contents + raise TemplateSyntaxError("'for' statements should use the format" + " 'for x in y': %s" % token.contents) loopvars = re.sub(r' *, *', ',', ' '.join(bits[1:in_index])).split(',') for var in loopvars: if not var or ' ' in var: - raise TemplateSyntaxError, "'for' tag received an invalid argument: %s" % token.contents + raise TemplateSyntaxError("'for' tag received an invalid argument:" + " %s" % token.contents) sequence = parser.compile_filter(bits[in_index+1]) nodelist_loop = parser.parse(('endfor',)) @@ -607,7 +654,7 @@ def do_ifequal(parser, token, negate): #@register.tag def ifequal(parser, token): """ - Output the contents of the block if the two arguments equal each other. + Outputs the contents of the block if the two arguments equal each other. Examples:: @@ -626,7 +673,10 @@ ifequal = register.tag(ifequal) #@register.tag def ifnotequal(parser, token): - """Output the contents of the block if the two arguments are not equal. See ifequal.""" + """ + Outputs the contents of the block if the two arguments are not equal. + See ifequal. + """ return do_ifequal(parser, token, True) ifnotequal = register.tag(ifnotequal) @@ -635,9 +685,7 @@ def do_if(parser, token): """ The ``{% if %}`` tag evaluates a variable, and if that variable is "true" (i.e. exists, is not empty, and is not a false boolean value) the contents - of the block are output: - - :: + of the block are output:: {% if athlete_list %} Number of athletes: {{ athlete_list|count }} @@ -648,8 +696,8 @@ def do_if(parser, token): In the above, if ``athlete_list`` is not empty, the number of athletes will be displayed by the ``{{ athlete_list|count }}`` variable. - As you can see, the ``if`` tag can take an option ``{% else %}`` clause that - will be displayed if the test fails. + As you can see, the ``if`` tag can take an option ``{% else %}`` clause + that will be displayed if the test fails. ``if`` tags may use ``or``, ``and`` or ``not`` to test a number of variables or to negate a given variable:: @@ -674,9 +722,9 @@ def do_if(parser, token): There are some athletes and absolutely no coaches. {% endif %} - ``if`` tags do not allow ``and`` and ``or`` clauses with the same - tag, because the order of logic would be ambigous. For example, - this is invalid:: + ``if`` tags do not allow ``and`` and ``or`` clauses with the same tag, + because the order of logic would be ambigous. For example, this is + invalid:: {% if athlete_list and coach_list or cheerleader_list %} @@ -692,8 +740,8 @@ def do_if(parser, token): bits = token.contents.split() del bits[0] if not bits: - raise TemplateSyntaxError, "'if' statement requires at least one argument" - # bits now looks something like this: ['a', 'or', 'not', 'b', 'or', 'c.d'] + raise TemplateSyntaxError("'if' statement requires at least one argument") + # Bits now looks something like this: ['a', 'or', 'not', 'b', 'or', 'c.d'] bitstr = ' '.join(bits) boolpairs = bitstr.split(' and ') boolvars = [] @@ -728,13 +776,13 @@ do_if = register.tag("if", do_if) #@register.tag def ifchanged(parser, token): """ - Check if a value has changed from the last iteration of a loop. + Checks if a value has changed from the last iteration of a loop. The 'ifchanged' block tag is used within a loop. It has two possible uses. 1. Checks its own rendered contents against its previous state and only - displays the content if it has changed. For example, this displays a list of - days, only displaying the month if it changes:: + displays the content if it has changed. For example, this displays a + list of days, only displaying the month if it changes::

        Archive for {{ year }}

        @@ -743,9 +791,9 @@ def ifchanged(parser, token): {{ date|date:"j" }} {% endfor %} - 2. If given a variable, check whether that variable has changed. For example, the - following shows the date every time it changes, but only shows the hour if both - the hour and the date have changed:: + 2. If given a variable, check whether that variable has changed. + For example, the following shows the date every time it changes, but + only shows the hour if both the hour and the date have changed:: {% for date in days %} {% ifchanged date.date %} {{ date.date }} {% endifchanged %} @@ -763,7 +811,7 @@ ifchanged = register.tag(ifchanged) #@register.tag def ssi(parser, token): """ - Output the contents of a given file into the page. + Outputs the contents of a given file into the page. Like a simple "include" tag, the ``ssi`` tag includes the contents of another file -- which must be specified using an absolute page -- @@ -779,21 +827,24 @@ def ssi(parser, token): bits = token.contents.split() parsed = False if len(bits) not in (2, 3): - raise TemplateSyntaxError, "'ssi' tag takes one argument: the path to the file to be included" + raise TemplateSyntaxError("'ssi' tag takes one argument: the path to" + " the file to be included") if len(bits) == 3: if bits[2] == 'parsed': parsed = True else: - raise TemplateSyntaxError, "Second (optional) argument to %s tag must be 'parsed'" % bits[0] + raise TemplateSyntaxError("Second (optional) argument to %s tag" + " must be 'parsed'" % bits[0]) return SsiNode(bits[1], parsed) ssi = register.tag(ssi) #@register.tag def load(parser, token): """ - Load a custom template tag set. + Loads a custom template tag set. - For example, to load the template tags in ``django/templatetags/news/photos.py``:: + For example, to load the template tags in + ``django/templatetags/news/photos.py``:: {% load news.photos %} """ @@ -804,14 +855,15 @@ def load(parser, token): lib = get_library("django.templatetags.%s" % taglib) parser.add_library(lib) except InvalidTemplateLibrary, e: - raise TemplateSyntaxError, "'%s' is not a valid tag library: %s" % (taglib, e) + raise TemplateSyntaxError("'%s' is not a valid tag library: %s" % + (taglib, e)) return LoadNode() load = register.tag(load) #@register.tag def now(parser, token): """ - Display the date, formatted according to the given string. + Displays the date, formatted according to the given string. Uses the same format as PHP's ``date()`` function; see http://php.net/date for all the possible values. @@ -830,7 +882,7 @@ now = register.tag(now) #@register.tag def regroup(parser, token): """ - Regroup a list of alike objects by a common attribute. + Regroups a list of alike objects by a common attribute. This complex tag is best illustrated by use of an example: say that ``people`` is a list of ``Person`` objects that have ``first_name``, @@ -868,8 +920,8 @@ def regroup(parser, token): Note that `{% regroup %}`` does not work when the list to be grouped is not sorted by the key you are grouping by! This means that if your list of - people was not sorted by gender, you'd need to make sure it is sorted before - using it, i.e.:: + people was not sorted by gender, you'd need to make sure it is sorted + before using it, i.e.:: {% regroup people|dictsort:"gender" by gender as grouped %} @@ -879,10 +931,11 @@ def regroup(parser, token): raise TemplateSyntaxError, "'regroup' tag takes five arguments" target = parser.compile_filter(firstbits[1]) if firstbits[2] != 'by': - raise TemplateSyntaxError, "second argument to 'regroup' tag must be 'by'" + raise TemplateSyntaxError("second argument to 'regroup' tag must be 'by'") lastbits_reversed = firstbits[3][::-1].split(None, 2) if lastbits_reversed[1][::-1] != 'as': - raise TemplateSyntaxError, "next-to-last argument to 'regroup' tag must be 'as'" + raise TemplateSyntaxError("next-to-last argument to 'regroup' tag must" + " be 'as'") expression = parser.compile_filter(lastbits_reversed[2][::-1]) @@ -892,8 +945,7 @@ regroup = register.tag(regroup) def spaceless(parser, token): """ - Removes whitespace between HTML tags. This includes tab - characters and newlines. + Removes whitespace between HTML tags, including tab and newline characters. Example usage:: @@ -907,8 +959,8 @@ def spaceless(parser, token):

        Foo

        - Only space between *tags* is normalized -- not space between tags and text. In - this example, the space around ``Hello`` won't be stripped:: + Only space between *tags* is normalized -- not space between tags and text. + In this example, the space around ``Hello`` won't be stripped:: {% spaceless %} @@ -924,7 +976,7 @@ spaceless = register.tag(spaceless) #@register.tag def templatetag(parser, token): """ - Output one of the bits used to compose template tags. + Outputs one of the bits used to compose template tags. Since the template system has no concept of "escaping", to display one of the bits used in template tags, you must use the ``{% templatetag %}`` tag. @@ -949,8 +1001,9 @@ def templatetag(parser, token): raise TemplateSyntaxError, "'templatetag' statement takes one argument" tag = bits[1] if tag not in TemplateTagNode.mapping: - raise TemplateSyntaxError, "Invalid templatetag argument: '%s'. Must be one of: %s" % \ - (tag, TemplateTagNode.mapping.keys()) + raise TemplateSyntaxError("Invalid templatetag argument: '%s'." + " Must be one of: %s" % + (tag, TemplateTagNode.mapping.keys())) return TemplateTagNode(tag) templatetag = register.tag(templatetag) @@ -958,7 +1011,8 @@ def url(parser, token): """ Returns an absolute URL matching given view with its parameters. - This is a way to define links that aren't tied to a particular URL configuration:: + This is a way to define links that aren't tied to a particular URL + configuration:: {% url path.to.some_view arg1,arg2,name1=value1 %} @@ -986,7 +1040,8 @@ def url(parser, token): """ bits = token.contents.split(' ', 2) if len(bits) < 2: - raise TemplateSyntaxError, "'%s' takes at least one argument (path to a view)" % bits[0] + raise TemplateSyntaxError("'%s' takes at least one argument" + " (path to a view)" % bits[0]) args = [] kwargs = {} if len(bits) > 2: @@ -1011,8 +1066,8 @@ def widthratio(parser, token): Above, if ``this_value`` is 175 and ``max_value`` is 200, the the image in - the above example will be 88 pixels wide (because 175/200 = .875; .875 * - 100 = 87.5 which is rounded up to 88). + the above example will be 88 pixels wide (because 175/200 = .875; + .875 * 100 = 87.5 which is rounded up to 88). """ bits = token.contents.split() if len(bits) != 4: @@ -1029,7 +1084,7 @@ widthratio = register.tag(widthratio) #@register.tag def do_with(parser, token): """ - Add a value to the context (inside of this block) for caching and easy + Adds a value to the context (inside of this block) for caching and easy access. For example:: @@ -1040,7 +1095,8 @@ def do_with(parser, token): """ bits = list(token.split_contents()) if len(bits) != 4 or bits[2] != "as": - raise TemplateSyntaxError, "%r expected format is 'value as name'" % bits[0] + raise TemplateSyntaxError("%r expected format is 'value as name'" % + bits[0]) var = parser.compile_filter(bits[1]) name = bits[3] nodelist = parser.parse(('endwith',)) diff --git a/django/test/client.py b/django/test/client.py index 6a05d9dd9c..bbd8239c33 100644 --- a/django/test/client.py +++ b/django/test/client.py @@ -42,7 +42,7 @@ class ClientHandler(BaseHandler): # Apply response middleware for middleware_method in self._response_middleware: response = middleware_method(request, response) - + response = self.apply_response_fixes(request, response) finally: dispatcher.send(signal=signals.request_finished) diff --git a/django/test/testcases.py b/django/test/testcases.py index 6b7714ec7b..2aa0a0783d 100644 --- a/django/test/testcases.py +++ b/django/test/testcases.py @@ -1,6 +1,6 @@ import re import unittest -from urlparse import urlsplit +from urlparse import urlsplit, urlunsplit from django.http import QueryDict from django.db import transaction @@ -74,7 +74,7 @@ class TestCase(unittest.TestCase): super(TestCase, self).__call__(result) def assertRedirects(self, response, expected_url, status_code=302, - target_status_code=200): + target_status_code=200, host=None): """Asserts that a response redirected to a specific URL, and that the redirect URL can be loaded. @@ -86,6 +86,10 @@ class TestCase(unittest.TestCase): " (expected %d)" % (response.status_code, status_code))) url = response['Location'] scheme, netloc, path, query, fragment = urlsplit(url) + e_scheme, e_netloc, e_path, e_query, e_fragment = urlsplit(expected_url) + if not (e_scheme or e_netloc): + expected_url = urlunsplit(('http', host or 'testserver', e_path, + e_query, e_fragment)) self.assertEqual(url, expected_url, "Response redirected to '%s', expected '%s'" % (url, expected_url)) @@ -146,7 +150,7 @@ class TestCase(unittest.TestCase): " context %d does not contain the" " error '%s' (actual errors: %s)" % (field, form, i, err, - list(field_errors))) + repr(field_errors))) elif field in context[form].fields: self.fail("The field '%s' on form '%s' in context %d" " contains no errors" % (field, form, i)) diff --git a/django/utils/cache.py b/django/utils/cache.py index f192e1115c..ae4de6dd87 100644 --- a/django/utils/cache.py +++ b/django/utils/cache.py @@ -13,17 +13,18 @@ into account when building its cache key. Requests with the same path but different header content for headers named in "Vary" need to get different cache keys to prevent delivery of wrong content. -A example: i18n middleware would need to distinguish caches by the +An example: i18n middleware would need to distinguish caches by the "Accept-language" header. """ import md5 import re import time -from email.Utils import formatdate + from django.conf import settings from django.core.cache import cache from django.utils.encoding import smart_str, iri_to_uri +from django.utils.http import http_date cc_delim_re = re.compile(r'\s*,\s*') @@ -40,7 +41,7 @@ def patch_cache_control(response, **kwargs): str() to it. """ def dictitem(s): - t = s.split('=',1) + t = s.split('=', 1) if len(t) > 1: return (t[0].lower(), t[1]) else: @@ -64,7 +65,7 @@ def patch_cache_control(response, **kwargs): if 'max-age' in cc and 'max_age' in kwargs: kwargs['max_age'] = min(cc['max-age'], kwargs['max_age']) - for (k,v) in kwargs.items(): + for (k, v) in kwargs.items(): cc[k.replace('_', '-')] = v cc = ', '.join([dictvalue(el) for el in cc.items()]) response['Cache-Control'] = cc @@ -88,15 +89,14 @@ def patch_response_headers(response, cache_timeout=None): if not response.has_header('ETag'): response['ETag'] = md5.new(response.content).hexdigest() if not response.has_header('Last-Modified'): - response['Last-Modified'] = formatdate()[:26] + "GMT" + response['Last-Modified'] = http_date() if not response.has_header('Expires'): - response['Expires'] = formatdate(time.time() + cache_timeout)[:26] + "GMT" + response['Expires'] = http_date(time.time() + cache_timeout) patch_cache_control(response, max_age=cache_timeout) def add_never_cache_headers(response): """ - Add headers to a response to indicate that - a page should never be cached. + Adds headers to a response to indicate that a page should never be cached. """ patch_response_headers(response, cache_timeout=-1) @@ -119,13 +119,14 @@ def patch_vary_headers(response, newheaders): response['Vary'] = ', '.join(vary) def _generate_cache_key(request, headerlist, key_prefix): - "Returns a cache key from the headers given in the header list." + """Returns a cache key from the headers given in the header list.""" ctx = md5.new() for header in headerlist: value = request.META.get(header, None) if value is not None: ctx.update(value) - return 'views.decorators.cache.cache_page.%s.%s.%s' % (key_prefix, iri_to_uri(request.path), ctx.hexdigest()) + return 'views.decorators.cache.cache_page.%s.%s.%s' % ( + key_prefix, iri_to_uri(request.path), ctx.hexdigest()) def get_cache_key(request, key_prefix=None): """ @@ -139,7 +140,8 @@ def get_cache_key(request, key_prefix=None): """ if key_prefix is None: key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX - cache_key = 'views.decorators.cache.cache_header.%s.%s' % (key_prefix, iri_to_uri(request.path)) + cache_key = 'views.decorators.cache.cache_header.%s.%s' % ( + key_prefix, iri_to_uri(request.path)) headerlist = cache.get(cache_key, None) if headerlist is not None: return _generate_cache_key(request, headerlist, key_prefix) @@ -163,9 +165,11 @@ def learn_cache_key(request, response, cache_timeout=None, key_prefix=None): key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX if cache_timeout is None: cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS - cache_key = 'views.decorators.cache.cache_header.%s.%s' % (key_prefix, iri_to_uri(request.path)) + cache_key = 'views.decorators.cache.cache_header.%s.%s' % ( + key_prefix, iri_to_uri(request.path)) if response.has_header('Vary'): - headerlist = ['HTTP_'+header.upper().replace('-', '_') for header in vary_delim_re.split(response['Vary'])] + headerlist = ['HTTP_'+header.upper().replace('-', '_') + for header in vary_delim_re.split(response['Vary'])] cache.set(cache_key, headerlist, cache_timeout) return _generate_cache_key(request, headerlist, key_prefix) else: diff --git a/django/utils/datastructures.py b/django/utils/datastructures.py index e0835b2cfc..549aa3f183 100644 --- a/django/utils/datastructures.py +++ b/django/utils/datastructures.py @@ -62,12 +62,10 @@ class SortedDict(dict): else: self.keyOrder = [key for key, value in data] - def __deepcopy__(self,memo): + def __deepcopy__(self, memo): from copy import deepcopy - obj = self.__class__() - for k, v in self.items(): - obj[k] = deepcopy(v, memo) - return obj + return self.__class__([(key, deepcopy(value, memo)) + for key, value in self.iteritems()]) def __setitem__(self, key, value): dict.__setitem__(self, key, value) diff --git a/django/utils/dateformat.py b/django/utils/dateformat.py index 9a296c3f2d..bfca46b3d5 100644 --- a/django/utils/dateformat.py +++ b/django/utils/dateformat.py @@ -170,7 +170,7 @@ class DateFormat(TimeFormat): return u"%+03d%02d" % (seconds // 3600, (seconds // 60) % 60) def r(self): - "RFC 822 formatted date; e.g. 'Thu, 21 Dec 2000 16:01:07 +0200'" + "RFC 2822 formatted date; e.g. 'Thu, 21 Dec 2000 16:01:07 +0200'" return self.format('D, j M Y H:i:s O') def S(self): diff --git a/django/utils/encoding.py b/django/utils/encoding.py index 2bd1ef6563..2ab0db7432 100644 --- a/django/utils/encoding.py +++ b/django/utils/encoding.py @@ -1,7 +1,19 @@ import types import urllib import datetime + from django.utils.functional import Promise +from django.utils.safestring import SafeData, mark_safe + +class DjangoUnicodeDecodeError(UnicodeDecodeError): + def __init__(self, obj, *args): + self.obj = obj + UnicodeDecodeError.__init__(self, *args) + + def __str__(self): + original = UnicodeDecodeError.__str__(self) + return '%s. You passed in %r (%s)' % (original, self.obj, + type(self.obj)) class StrAndUnicode(object): """ @@ -33,13 +45,19 @@ def force_unicode(s, encoding='utf-8', strings_only=False, errors='strict'): """ if strings_only and isinstance(s, (types.NoneType, int, long, datetime.datetime, datetime.date, datetime.time, float)): return s - if not isinstance(s, basestring,): - if hasattr(s, '__unicode__'): - s = unicode(s) - else: - s = unicode(str(s), encoding, errors) - elif not isinstance(s, unicode): - s = unicode(s, encoding, errors) + try: + if not isinstance(s, basestring,): + if hasattr(s, '__unicode__'): + s = unicode(s) + else: + s = unicode(str(s), encoding, errors) + elif not isinstance(s, unicode): + # Note: We use .decode() here, instead of unicode(s, encoding, + # errors), so that if s is a SafeString, it ends up being a + # SafeUnicode at the end. + s = s.decode(encoding, errors) + except UnicodeDecodeError, e: + raise DjangoUnicodeDecodeError(s, *e.args) return s def smart_str(s, encoding='utf-8', strings_only=False, errors='strict'): diff --git a/django/utils/html.py b/django/utils/html.py index ebd04d1b3c..8eeaa66330 100644 --- a/django/utils/html.py +++ b/django/utils/html.py @@ -3,6 +3,7 @@ import re import string +from django.utils.safestring import SafeData, mark_safe from django.utils.encoding import force_unicode from django.utils.functional import allow_lazy @@ -27,16 +28,28 @@ del x # Temporary variable def escape(html): "Return the given HTML with ampersands, quotes and carets encoded." - return force_unicode(html).replace('&', '&').replace('<', '<').replace('>', '>').replace('"', '"').replace("'", ''') + return mark_safe(force_unicode(html).replace('&', '&').replace('<', '<').replace('>', '>').replace('"', '"').replace("'", ''')) escape = allow_lazy(escape, unicode) -def linebreaks(value): - "Convert newlines into

        and
        s." +def conditional_escape(html): + """ + Similar to escape(), except that it doesn't operate on pre-escaped strings. + """ + if isinstance(html, SafeData): + return html + else: + return escape(html) + +def linebreaks(value, autoescape=False): + "Converts newlines into

        and
        s" value = re.sub(r'\r\n|\r|\n', '\n', force_unicode(value)) # normalize newlines paras = re.split('\n{2,}', value) - paras = [u'

        %s

        ' % p.strip().replace('\n', '
        ') for p in paras] + if autoescape: + paras = [u'

        %s

        ' % escape(p.strip()).replace('\n', '
        ') for p in paras] + else: + paras = [u'

        %s

        ' % p.strip().replace('\n', '
        ') for p in paras] return u'\n\n'.join(paras) -linebreaks = allow_lazy(linebreaks, unicode) +linebreaks = allow_lazy(linebreaks, unicode) def strip_tags(value): "Return the given HTML with all tags stripped." @@ -58,7 +71,7 @@ def fix_ampersands(value): return unencoded_ampersands_re.sub('&', force_unicode(value)) fix_ampersands = allow_lazy(fix_ampersands, unicode) -def urlize(text, trim_url_limit=None, nofollow=False): +def urlize(text, trim_url_limit=None, nofollow=False, autoescape=False): """ Convert any URLs in text into clickable links. @@ -72,13 +85,19 @@ def urlize(text, trim_url_limit=None, nofollow=False): If nofollow is True, the URLs in link text will get a rel="nofollow" attribute. """ - trim_url = lambda x, limit=trim_url_limit: limit is not None and (len(x) > limit and ('%s...' % x[:max(0, limit - 3)])) or x + if autoescape: + trim_url = lambda x, limit=trim_url_limit: conditional_escape(limit is not None and (len(x) > limit and ('%s...' % x[:max(0, limit - 3)])) or x) + else: + trim_url = lambda x, limit=trim_url_limit: limit is not None and (len(x) > limit and ('%s...' % x[:max(0, limit - 3)])) or x + safe_input = isinstance(text, SafeData) words = word_split_re.split(force_unicode(text)) nofollow_attr = nofollow and ' rel="nofollow"' or '' for i, word in enumerate(words): match = punctuation_re.match(word) if match: lead, middle, trail = match.groups() + if safe_input: + middle = mark_safe(middle) if middle.startswith('www.') or ('@' not in middle and not middle.startswith('http://') and \ len(middle) > 0 and middle[0] in string.letters + string.digits and \ (middle.endswith('.org') or middle.endswith('.net') or middle.endswith('.com'))): diff --git a/django/utils/http.py b/django/utils/http.py index 4912c9c46a..5ec6e92d28 100644 --- a/django/utils/http.py +++ b/django/utils/http.py @@ -1,4 +1,6 @@ import urllib +from email.Utils import formatdate + from django.utils.encoding import smart_str, force_unicode from django.utils.functional import allow_lazy @@ -37,3 +39,29 @@ def urlencode(query, doseq=0): for k, v in query], doseq) +def cookie_date(epoch_seconds=None): + """ + Formats the time to ensure compatibility with Netscape's cookie standard. + + Accepts a floating point number expressed in seconds since the epoch, in + UTC - such as that outputted by time.time(). If set to None, defaults to + the current time. + + Outputs a string in the format 'Wdy, DD-Mon-YYYY HH:MM:SS GMT'. + """ + rfcdate = formatdate(epoch_seconds) + return '%s-%s-%s GMT' % (rfcdate[:7], rfcdate[8:11], rfcdate[12:25]) + +def http_date(epoch_seconds=None): + """ + Formats the time to match the RFC1123 date format as specified by HTTP + RFC2616 section 3.3.1. + + Accepts a floating point number expressed in seconds since the epoch, in + UTC - such as that outputted by time.time(). If set to None, defaults to + the current time. + + Outputs a string in the format 'Wdy, DD Mon YYYY HH:MM:SS GMT'. + """ + rfcdate = formatdate(epoch_seconds) + return '%s GMT' % rfcdate[:25] diff --git a/django/utils/safestring.py b/django/utils/safestring.py new file mode 100644 index 0000000000..c7234af0f3 --- /dev/null +++ b/django/utils/safestring.py @@ -0,0 +1,124 @@ +""" +Functions for working with "safe strings": strings that can be displayed safely +without further escaping in HTML. Marking something as a "safe string" means +that the producer of the string has already turned characters that should not +be interpreted by the HTML engine (e.g. '<') into the appropriate entities. +""" +from django.utils.functional import curry, Promise + +class EscapeData(object): + pass + +class EscapeString(str, EscapeData): + """ + A string that should be HTML-escaped when output. + """ + pass + +class EscapeUnicode(unicode, EscapeData): + """ + A unicode object that should be HTML-escaped when output. + """ + pass + +class SafeData(object): + pass + +class SafeString(str, SafeData): + """ + A string subclass that has been specifically marked as "safe" (requires no + further escaping) for HTML output purposes. + """ + def __add__(self, rhs): + """ + Concatenating a safe string with another safe string or safe unicode + object is safe. Otherwise, the result is no longer safe. + """ + if isinstance(rhs, SafeUnicode): + return SafeUnicode(self + rhs) + elif isinstance(rhs, SafeString): + return SafeString(self + rhs) + else: + return super(SafeString, self).__add__(rhs) + + def __str__(self): + return self + + def _proxy_method(self, *args, **kwargs): + """ + Wrap a call to a normal unicode method up so that we return safe + results. The method that is being wrapped is passed in the 'method' + argument. + """ + method = kwargs.pop('method') + data = method(self, *args, **kwargs) + if isinstance(data, str): + return SafeString(data) + else: + return SafeUnicode(data) + + encode = curry(_proxy_method, method = str.encode) + decode = curry(_proxy_method, method = str.decode) + +class SafeUnicode(unicode, SafeData): + """ + A unicode subclass that has been specifically marked as "safe" for HTML + output purposes. + """ + def __add__(self, rhs): + """ + Concatenating a safe unicode object with another safe string or safe + unicode object is safe. Otherwise, the result is no longer safe. + """ + if isinstance(rhs, SafeData): + return SafeUnicode(self + rhs) + else: + return super(SafeUnicode, self).__add__(rhs) + + def _proxy_method(self, *args, **kwargs): + """ + Wrap a call to a normal unicode method up so that we return safe + results. The method that is being wrapped is passed in the 'method' + argument. + """ + method = kwargs.pop('method') + data = method(self, *args, **kwargs) + if isinstance(data, str): + return SafeString(data) + else: + return SafeUnicode(data) + + encode = curry(_proxy_method, method = unicode.encode) + decode = curry(_proxy_method, method = unicode.decode) + +def mark_safe(s): + """ + Explicitly mark a string as safe for (HTML) output purposes. The returned + object can be used everywhere a string or unicode object is appropriate. + + Can be called multiple times on a single string. + """ + if isinstance(s, SafeData): + return s + if isinstance(s, str) or (isinstance(s, Promise) and s._delegate_str): + return SafeString(s) + if isinstance(s, (unicode, Promise)): + return SafeUnicode(s) + return SafeString(str(s)) + +def mark_for_escaping(s): + """ + Explicitly mark a string as requiring HTML escaping upon output. Has no + effect on SafeData subclasses. + + Can be called multiple times on a single string (the resulting escaping is + only applied once). + """ + if isinstance(s, (SafeData, EscapeData)): + return s + if isinstance(s, str) or (isinstance(s, Promise) and s._delegate_str): + return EscapeString(s) + if isinstance(s, (unicode, Promise)): + return EscapeUnicode(s) + return EscapeString(str(s)) + diff --git a/django/views/debug.py b/django/views/debug.py index 717de1eb34..7c45af230a 100644 --- a/django/views/debug.py +++ b/django/views/debug.py @@ -333,7 +333,6 @@ TECHNICAL_500_TEMPLATE = """ -

        {{ exception_type }} at {{ request.path|escape }}

        {{ exception_value|escape }}

        @@ -395,7 +394,7 @@ TECHNICAL_500_TEMPLATE = """

        Template error

        In template {{ template_info.name }}, error at line {{ template_info.line }}

        -

        {{ template_info.message|escape }}

        +

        {{ template_info.message }}

        {% for source_line in template_info.source_lines %} {% ifequal source_line.0 template_info.line %} @@ -413,6 +412,7 @@ TECHNICAL_500_TEMPLATE = """

        Traceback (innermost last)


        + {% autoescape off %}
          {% for frame in frames %} @@ -422,11 +422,11 @@ TECHNICAL_500_TEMPLATE = """ {% if frame.context_line %}
          {% if frame.pre_context %} -
            {% for line in frame.pre_context %}
          1. {{ line|escape }}
          2. {% endfor %}
          +
            {% for line in frame.pre_context %}
          1. {{ line }}
          2. {% endfor %}
          {% endif %} -
          1. {{ frame.context_line|escape }} ...
          +
          1. {{ frame.context_line }} ...
          {% if frame.post_context %} -
            {% for line in frame.post_context %}
          1. {{ line|escape }}
          2. {% endfor %}
          +
            {% for line in frame.post_context %}
          1. {{ line }}
          2. {% endfor %}
          {% endif %}
          {% endif %} @@ -446,7 +446,7 @@ TECHNICAL_500_TEMPLATE = """ {% for var in frame.vars|dictsort:"0" %}
        - + {% endfor %} @@ -466,7 +466,7 @@ Traceback (most recent call last):
        {% for frame in frames %} File "{{ frame.filename }}" in {{ frame.function }}
        {% if frame.context_line %} -   {{ frame.lineno }}. {{ frame.context_line|escape }}
        +   {{ frame.lineno }}. {{ frame.context_line }}
        {% endif %} {% endfor %}
          {{ exception_type }} at {{ request.path|escape }}
        @@ -476,6 +476,7 @@ Traceback (most recent call last):
        {{ var.0 }}
        {{ var.1|pprint|escape }}
        {{ var.1|pprint }}
        + {% endautoescape %}
        @@ -494,7 +495,7 @@ Traceback (most recent call last):
        {% for var in request.GET.items %} {{ var.0 }} -
        {{ var.1|pprint|escape }}
        +
        {{ var.1|pprint }}
        {% endfor %} @@ -516,7 +517,7 @@ Traceback (most recent call last):
        {% for var in request.POST.items %} {{ var.0 }} -
        {{ var.1|pprint|escape }}
        +
        {{ var.1|pprint }}
        {% endfor %} @@ -538,7 +539,7 @@ Traceback (most recent call last):
        {% for var in request.COOKIES.items %} {{ var.0 }} -
        {{ var.1|pprint|escape }}
        +
        {{ var.1|pprint }}
        {% endfor %} @@ -559,7 +560,7 @@ Traceback (most recent call last):
        {% for var in request.META.items|dictsort:"0" %} {{ var.0 }} -
        {{ var.1|pprint|escape }}
        +
        {{ var.1|pprint }}
        {% endfor %} @@ -578,7 +579,7 @@ Traceback (most recent call last):
        {% for var in settings.items|dictsort:"0" %} {{ var.0 }} -
        {{ var.1|pprint|escape }}
        +
        {{ var.1|pprint }}
        {% endfor %} @@ -593,7 +594,6 @@ Traceback (most recent call last):
        display a standard 500 page.

        - """ @@ -645,12 +645,12 @@ TECHNICAL_404_TEMPLATE = """

          {% for pattern in urlpatterns %} -
        1. {{ pattern|escape }}
        2. +
        3. {{ pattern }}
        4. {% endfor %}

        The current URL, {{ request_path|escape }}, didn't match any of these.

        {% else %} -

        {{ reason|escape }}

        +

        {{ reason }}

        {% endif %} diff --git a/django/views/static.py b/django/views/static.py index dce45d914d..b556c60ca6 100644 --- a/django/views/static.py +++ b/django/views/static.py @@ -7,13 +7,14 @@ import mimetypes import os import posixpath import re -import rfc822 import stat import urllib +from email.Utils import parsedate_tz, mktime_tz from django.template import loader from django.http import Http404, HttpResponse, HttpResponseRedirect, HttpResponseNotModified from django.template import Template, Context, TemplateDoesNotExist +from django.utils.http import http_date def serve(request, path, document_root=None, show_indexes=False): """ @@ -60,7 +61,7 @@ def serve(request, path, document_root=None, show_indexes=False): mimetype = mimetypes.guess_type(fullpath)[0] contents = open(fullpath, 'rb').read() response = HttpResponse(contents, mimetype=mimetype) - response["Last-Modified"] = rfc822.formatdate(statobj[stat.ST_MTIME]) + response["Last-Modified"] = http_date(statobj[stat.ST_MTIME]) return response DEFAULT_DIRECTORY_INDEX_TEMPLATE = """ @@ -119,8 +120,7 @@ def was_modified_since(header=None, mtime=0, size=0): raise ValueError matches = re.match(r"^([^;]+)(; length=([0-9]+))?$", header, re.IGNORECASE) - header_mtime = rfc822.mktime_tz(rfc822.parsedate_tz( - matches.group(1))) + header_mtime = mktime_tz(parsedate_tz(matches.group(1))) header_len = matches.group(3) if header_len and int(header_len) != size: raise ValueError diff --git a/docs/cache.txt b/docs/cache.txt index d598915d1a..4f177b8c07 100644 --- a/docs/cache.txt +++ b/docs/cache.txt @@ -291,13 +291,15 @@ minutes. Template fragment caching ========================= +**New in development version**. + If you're after even more control, you can also cache template fragments using -the ``cache`` template tag. To give your template access to this tag, put ``{% -load cache %}`` near the top of your template. +the ``cache`` template tag. To give your template access to this tag, put +``{% load cache %}`` near the top of your template. The ``{% cache %}`` template tag caches the contents of the block for a given -amount of time. It takes at least two arguments: the cache timeout, in -seconds, and the name to give the cache fragment. For example:: +amount of time. It takes at least two arguments: the cache timeout, in seconds, +and the name to give the cache fragment. For example:: {% load cache %} {% cache 500 sidebar %} diff --git a/docs/custom_model_fields.txt b/docs/custom_model_fields.txt new file mode 100644 index 0000000000..c12d1844cd --- /dev/null +++ b/docs/custom_model_fields.txt @@ -0,0 +1,567 @@ +=================== +Custom Model Fields +=================== + +**New in Django development version** + +Introduction +============ + +The `model reference`_ documentation explains how to use Django's standard +field classes. For many purposes, those classes are all you'll need. Sometimes, +though, the Django version won't meet your precise requirements, or you'll want +to use a field that is entirely different from those shipped with Django. + +Django's built-in field types don't cover every possible database column type -- +only the common types, such as ``VARCHAR`` and ``INTEGER``. For more obscure +column types, such as geographic polygons or even user-created types such as +`PostgreSQL custom types`_, you can define your own Django ``Field`` subclasses. + +Alternatively, you may have a complex Python object that can somehow be +serialized to fit into a standard database column type. This is another case +where a ``Field`` subclass will help you use your object with your models. + +Our example object +------------------ + +Creating custom fields requires a bit of attention to detail. To make things +easier to follow, we'll use a consistent example throughout this document. +Suppose you have a Python object representing the deal of cards in a hand of +Bridge_. It doesn't matter if you don't know how to play Bridge. You only need +to know that 52 cards are dealt out equally to four players, who are +traditionally called *north*, *east*, *south* and *west*. Our class looks +something like this:: + + class Hand(object): + def __init__(self, north, east, south, west): + # Input parameters are lists of cards ('Ah', '9s', etc) + self.north = north + self.east = east + self.south = south + self.west = west + + # ... (other possibly useful methods omitted) ... + +This is just an ordinary Python class, nothing Django-specific about it. We +would like to be able to things like this in our models (we assume the +``hand`` attribute on the model is an instance of ``Hand``):: + + + example = MyModel.objects.get(pk=1) + print example.hand.north + + new_hand = Hand(north, east, south, west) + example.hand = new_hand + example.save() + +We assign to and retrieve from the ``hand`` attribute in our model just like +any other Python class. The trick is to tell Django how to handle saving and +loading such an object + +In order to use the ``Hand`` class in our models, we **do not** have to change +this class at all. This is ideal, because it means you can easily write +model support for existing classes where you cannot change the source code. + +.. note:: + You might only be wanting to take advantage of custom database column + types and deal with the data as standard Python types in your models; + strings, or floats, for example. This case is similar to our ``Hand`` + example and we'll note any differences as we go along. + +.. _model reference: ../model_api/ +.. _PostgreSQL custom types: http://www.postgresql.org/docs/8.2/interactive/sql-createtype.html +.. _Bridge: http://en.wikipedia.org/wiki/Contract_bridge + +Background Theory +================= + +Database storage +---------------- + +The simplest way to think of a model field is that it provides a way to take a +normal Python object -- string, boolean, ``datetime``, or something more +complex like ``Hand`` -- and convert it to and from a format that is useful +when dealing with the database (and serialization, but, as we'll see later, +that falls out fairly naturally once you have the database side under control). + +Fields in a model must somehow be converted to fit into an existing database +column type. Different databases provide different sets of valid column types, +but the rule is still the same: those are the only types you have to work +with. Anything you want to store in the database must fit into one of +those types. + +Normally, you're either writing a Django field to match a particular database +column type, or there's a fairly straightforward way to convert your data to, +say, a string. + +For our ``Hand`` example, we could convert the card data to a string of 104 +characters by concatenating all the cards together in a pre-determined order. +Say, all the *north* cards first, then the *east*, *south* and *west* cards, in +that order. So ``Hand`` objects can be saved to text or character columns in +the database + +What does a field class do? +--------------------------- + +All of Django's fields (and when we say *fields* in this document, we always +mean model fields and not `form fields`_) are subclasses of +``django.db.models.Field``. Most of the information that Django records about a +field is common to all fields -- name, help text, validator lists, uniqueness +and so forth. Storing all that information is handled by ``Field``. We'll get +into the precise details of what ``Field`` can do later on; for now, suffice it +to say that everything descends from ``Field`` and then customises key pieces +of the class behaviour. + +.. _form fields: ../newforms/#fields + +It's important to realise that a Django field class is not what is stored in +your model attributes. The model attributes contain normal Python objects. The +field classes you define in a model are actually stored in the ``Meta`` class +when the model class is created (the precise details of how this is done are +unimportant here). This is because the field classes aren't necessary when +you're just creating and modifying attributes. Instead, they provide the +machinery for converting between the attribute value and what is stored in the +database or sent to the serializer. + +Keep this in mind when creating your own custom fields. The Django ``Field`` +subclass you write provides the machinery for converting between your Python +instances and the database/serializer values in various ways (there are +differences between storing a value and using a value for lookups, for +example). If this sounds a bit tricky, don't worry. It will hopefully become +clearer in the examples below. Just remember that you will often end up +creating two classes when you want a custom field. The first class is the +Python object that your users will manipulate. They will assign it to the model +attribute, they will read from it for displaying purposes, things like that. +This is the ``Hand`` class in our example. The second class is the ``Field`` +subclass. This is the class that knows how to convert your first class back and +forth between its permanent storage form and the Python form. + +Writing a ``Field`` subclass +============================= + +When you are planning your ``Field`` subclass, first give some thought to +which existing field your new field is most similar to. Can you subclass an +existing Django field and save yourself some work? If not, you should subclass the ``Field`` class, from which everything is descended. + +Initialising your new field is a matter of separating out any arguments that +are specific to your case from the common arguments and passing the latter to +the ``__init__()`` method of ``Field`` (or your parent class). + +In our example, the Django field we create is going to be called +``HandField``. It's not a bad idea to use a similar naming scheme to Django's +fields so that our new class is identifiable and yet clearly related to the +``Hand`` class it is wrapping. It doesn't behave like any existing field, so +we'll subclass directly from ``Field``:: + + from django.db import models + + class HandField(models.Field): + def __init__(self, *args, **kwargs): + kwargs['max_length'] = 104 + super(HandField, self).__init__(*args, **kwargs) + +Our ``HandField`` will accept most of the standard field options (see the list +below), but we ensure it has a fixed length, since it only needs to hold 52 +card values plus their suits; 104 characters in total. + +.. note:: + Many of Django's model fields accept options that they don't do anything + with. For example, you can pass both ``editable`` and ``auto_now`` to a + ``DateField`` and it will simply ignore the ``editable`` parameter + (``auto_now`` being set implies ``editable=False``). No error is raised in + this case. + + This behaviour simplifies the field classes, because they don't need to + check for options that aren't necessary. They just pass all the options to + the parent class and then don't use them later on. It is up to you whether + you want your fields to be more strict about the options they select, or + to use the simpler, more permissive behaviour of the current fields. + +The ``Field.__init__()`` method takes the following parameters, in this +order: + + - ``verbose_name`` + - ``name`` + - ``primary_key`` + - ``max_length`` + - ``unique`` + - ``blank`` + - ``null`` + - ``db_index`` + - ``core`` + - ``rel``: Used for related fields (like ``ForeignKey``). For advanced use + only. + - ``default`` + - ``editable`` + - ``serialize``: If ``False``, the field will not be serialized when the + model is passed to Django's serializers_. Defaults to ``True``. + - ``prepopulate_from`` + - ``unique_for_date`` + - ``unique_for_month`` + - ``unique_for_year`` + - ``validator_list`` + - ``choices`` + - ``radio_admin`` + - ``help_text`` + - ``db_column`` + - ``db_tablespace``: Currently only used with the Oracle backend and only + for index creation. You can usually ignore this option. + +All of the options without an explanation in the above list have the same +meaning they do for normal Django fields. See the `model documentation`_ for +examples and details. + +.. _serializers: ../serialization/ +.. _model documentation: ../model-api/ + +The ``SubfieldBase`` metaclass +------------------------------ + +As we indicated in the introduction_, field subclasses are often needed for +two reasons. Either to take advantage of a custom database column type, or to +handle complex Python types. A combination of the two is obviously also +possible. If you are only working with custom database column types and your +model fields appear in Python as standard Python types direct from the +database backend, you don't need to worry about this section. + +If you are handling custom Python types, such as our ``Hand`` class, we need +to make sure that when Django initialises an instance of our model and assigns +a database value to our custom field attribute we convert that value into the +appropriate Python object. The details of how this happens internally are a +little complex. For the field writer, though, things are fairly simple. Make +sure your field subclass uses ``django.db.models.SubfieldBase`` as its +metaclass. This ensures that the ``to_python()`` method, documented below_, +will always be called when the attribute is initialised. + +Our ``HandleField`` class now looks like this:: + + class HandleField(models.Field): + __metaclass__ = models.SubfieldBase + + def __init__(self, *args, **kwargs): + # ... + +.. _below: #to-python-self-value + +Useful methods +-------------- + +Once you've created your ``Field`` subclass and setup up the +``__metaclass__``, if necessary, there are a few standard methods you need to +consider overriding. Which of these you need to implement will depend on you +particular field behaviour. The list below is in approximately decreasing +order of importance, so start from the top. + +``db_type(self)`` +~~~~~~~~~~~~~~~~~ + +Returns the database column data type for the ``Field``, taking into account +the current ``DATABASE_ENGINE`` setting. + +Say you've created a PostgreSQL custom type called ``mytype``. You can use this +field with Django by subclassing ``Field`` and implementing the ``db_type()`` +method, like so:: + + from django.db import models + + class MytypeField(models.Field): + def db_type(self): + return 'mytype' + +Once you have ``MytypeField``, you can use it in any model, just like any other +``Field`` type:: + + class Person(models.Model): + name = models.CharField(max_length=80) + gender = models.CharField(max_length=1) + something_else = MytypeField() + +If you aim to build a database-agnostic application, you should account for +differences in database column types. For example, the date/time column type +in PostgreSQL is called ``timestamp``, while the same column in MySQL is called +``datetime``. The simplest way to handle this in a ``db_type()`` method is to +import the Django settings module and check the ``DATABASE_ENGINE`` setting. +For example:: + + class MyDateField(models.Field): + def db_type(self): + from django.conf import settings + if settings.DATABASE_ENGINE == 'mysql': + return 'datetime' + else: + return 'timestamp' + +The ``db_type()`` method is only called by Django when the framework constructs +the ``CREATE TABLE`` statements for your application -- that is, when you first +create your tables. It's not called at any other time, so it can afford to +execute slightly complex code, such as the ``DATABASE_ENGINE`` check in the +above example. + +Some database column types accept parameters, such as ``CHAR(25)``, where the +parameter ``25`` represents the maximum column length. In cases like these, +it's more flexible if the parameter is specified in the model rather than being +hard-coded in the ``db_type()`` method. For example, it wouldn't make much +sense to have a ``CharMaxlength25Field``, shown here:: + + # This is a silly example of hard-coded parameters. + class CharMaxlength25Field(models.Field): + def db_type(self): + return 'char(25)' + + # In the model: + class MyModel(models.Model): + # ... + my_field = CharMaxlength25Field() + +The better way of doing this would be to make the parameter specifiable at run +time -- i.e., when the class is instantiated. To do that, just implement +``__init__()``, like so:: + + # This is a much more flexible example. + class BetterCharField(models.Field): + def __init__(self, max_length, *args, **kwargs): + self.max_length = max_length + super(BetterCharField, self).__init__(*args, **kwargs) + + def db_type(self): + return 'char(%s)' % self.max_length + + # In the model: + class MyModel(models.Model): + # ... + my_field = BetterCharField(25) + +Finally, if your column requires truly complex SQL setup, return ``None`` from +``db_type()``. This will cause Django's SQL creation code to skip over this +field. You are then responsible for creating the column in the right table in +some other way, of course, but this gives you a way to tell Django to get out +of the way. + + +``to_python(self, value)`` +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Converts between all the ways your field can receive its initial value and the +Python object you want to end up with. The default version just returns +``value``, so is useful is the database backend returns the data already in +the correct form (a Python string, for example). + +Normally, you will need to override this method. As a general rule, be +prepared to accept an instance of the right type (e.g. ``Hand`` in our ongoing +example), a string (from a deserializer, for example), and whatever the +database wrapper returns for the column type you are using. + +In our ``HandField`` class, we are storing the data in a character field in +the database, so we need to be able to process strings and ``Hand`` instances +in ``to_python()``:: + + class HandField(models.Field): + # ... + + def to_python(self, value): + if isinstance(value, Hand): + return value + + # The string case + p1 = re.compile('.{26}') + p2 = re.compile('..') + args = [p2.findall(x) for x in p1.findall(value)] + return Hand(*args) + +Notice that we always return a ``Hand`` instance from this method. That is the +Python object we want to store in the model's attribute. + +``get_db_prep_save(self, value)`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This is the reverse of ``to_python()`` when working with the database backends +(as opposed to serialization). The ``value`` parameter is the current value of +the model's attribute (a field has no reference to its containing model, so it +cannot retrieve the value itself) and the method should return data in a +format that can be used as a parameter in a query for the database backend. + +For example:: + + class HandField(models.Field): + # ... + + def get_db_prep_save(self, value): + return ''.join([''.join(l) for l in (self.north, + self.east, self.south, self.west)]) + + +``pre_save(self, model_instance, add)`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This method is called just prior to ``get_db_prep_save()`` and should return +the value of the appropriate attribute from ``model_instance`` for this field. +The attribute name is in ``self.attname`` (this is set up by ``Field``). If +the model is being saved to the database for the first time, the ``add`` +parameter will be ``True``, otherwise it will be ``False``. + +Often you won't need to override this method. However, at times it can be very +useful. For example, the Django ``DateTimeField`` uses this method to set the +attribute to the correct value before returning it in the cases when +``auto_now`` or ``auto_now_add`` are set on the field. + +If you do override this method, you must return the value of the attribute at +the end. You should also update the model's attribute if you make any changes +to the value so that code holding references to the model will always see the +correct value. + +``get_db_prep_lookup(self, lookup_type, value)`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Prepares the ``value`` for passing to the database when used in a lookup (a +``WHERE`` constraint in SQL). The ``lookup_type`` will be one of the valid +Django filter lookups: ``exact``, ``iexact``, ``contains``, ``icontains``, +``gt``, ``gte``, ``lt``, ``lte``, ``in``, ``startswith``, ``istartswith``, +``endswith``, ``iendswith``, ``range``, ``year``, ``month``, ``day``, +``isnull``, ``search``, ``regex``, and ``iregex``. + +Your method must be prepared to handle all of these ``lookup_type`` values and +should raise either a ``ValueError`` if the ``value`` is of the wrong sort (a +list when you were expecting an object, for example) or a ``TypeError`` if +your field does not support that type of lookup. For many fields, you can get +by with handling the lookup types that need special handling for your field +and pass the rest of the ``get_db_prep_lookup()`` method of the parent class. + +If you needed to implement ``get_db_prep_save()``, you will usually need to +implement ``get_db_prep_lookup()``. The usual reason is because of the +``range`` and ``in`` lookups. In these case, you will passed a list of +objects (presumably of the right type) and will need to convert them to a list +of things of the right type for passing to the database. Sometimes you can +reuse ``get_db_prep_save()``, or at least factor out some common pieces from +both methods into a help function. + +For example:: + + class HandField(models.Field): + # ... + + def get_db_prep_lookup(self, lookup_type, value): + # We only handle 'exact' and 'in'. All others are errors. + if lookup_type == 'exact': + return self.get_db_prep_save(value) + elif lookup_type == 'in': + return [self.get_db_prep_save(v) for v in value] + else: + raise TypeError('Lookup type %r not supported.' % lookup_type) + + +``formfield(self, form_class=forms.CharField, **kwargs)`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Returns the default form field to use when this field is displayed +in a model. This method is called by the `helper functions`_ +``form_for_model()`` and ``form_for_instance()``. + +All of the ``kwargs`` dictionary is passed directly to the form field's +``__init__()`` method. Normally, all you need to do is set up a good default +for the ``form_class`` argument and then delegate further handling to the +parent class. This might require you to write a custom form field (and even a +form widget). See the `forms documentation`_ for information about this. Also +have a look at ``django.contrib.localflavor`` for some examples of custom +widgets. + +Continuing our ongoing example, we can write the ``formfield()`` method as:: + + class HandField(models.Field): + # ... + + def formfield(self, **kwargs): + # This is a fairly standard way to set up some defaults + # whilst letting the caller override them. + defaults = {'form_class': MyFormField} + defaults.update(kwargs) + return super(HandField, self).formfield(**defaults) + +This assumes we have some ``MyFormField`` field class (which has its own +default widget) imported. This document doesn't cover the details of writing +custom form fields. + +.. _helper functions: ../newforms/#generating-forms-for-models +.. _forms documentation: ../newforms/ + +``get_internal_type(self)`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Returns a string giving the name of the ``Field`` subclass we are emulating at +the database level. This is used to determine the type of database column for +simple cases. + +If you have created a ``db_type()`` method, you do not need to worry about +``get_internal_type()`` -- it won't be used much. Sometimes, though, your +database storage is similar in type to some other field, so you can use that +other field's logic to create the right column. + +For example:: + + class HandField(models.Field): + # ... + + def get_internal_type(self): + return 'CharField' + +No matter which database backend we are using, this will mean that ``syncdb`` +and other SQL commands create the right column type for storing a string. + +If ``get_internal_type()`` returns a string that is not known to Django for +the database backend you are using -- that is, it doesn't appear in +``django.db.backends..creation.DATA_TYPES`` -- the string will still +be used by the serializer, but the default ``db_type()`` method will return +``None``. See the documentation of ``db_type()`` above_ for reasons why this +might be useful. Putting a descriptive string in as the type of the field for +the serializer is a useful idea if you are ever going to be using the +serializer output in some other place, outside of Django. + +.. _above: #db-type-self + +``flatten_data(self, follow, obj=None)`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. admonition:: Subject to change + + Although implementing this method is necessary to allow field + serialization, the API might change in the future. + +Returns a dictionary, mapping the field's attribute name to a flattened string +version of the data. This method has some internal uses that aren't of +interest to use here (mostly having to do with manipulators). For our +purposes, it is sufficient to return a one item dictionary that maps the +attribute name to a string. + +This method is used by the serializers to convert the field into a string for +output. You can ignore the input parameters for serialization purposes, +although calling ``Field._get_val_from_obj(obj)`` is the best way to get the +value to serialize. + +For example, since our ``HandField`` uses strings for its data storage anyway, +we can reuse some existing conversion code:: + + class HandField(models.Field): + # ... + + def flatten_data(self, follow, obj=None): + value = self._get_val_from_obj(obj) + return {self.attname: self.get_db_prep_save(value)} + +Some general advice +-------------------- + +Writing a custom field can be a tricky process sometime, particularly if you +are doing complex conversions between your Python types and your database and +serialization formats. A couple of tips to make things go more smoothly: + + 1. Look at the existing Django fields (in + ``django/db/models/fields/__init__.py``) for inspiration. Try to find a field + that is already close to what you want and extend it a little bit, in + preference to creating an entirely new field from scratch. + + 2. Put a ``__str__()`` or ``__unicode__()`` method on the class you are + wrapping up as a field. There are a lot of places where the default behaviour + of the field code is to call ``force_unicode()`` on the value (in our + examples in this document, ``value`` would be a ``Hand`` instance, not a + ``HandField``). So if your ``__unicode__()`` method automatically converts to + the string form of your Python object, you can save yourself a lot of work. + diff --git a/docs/email.txt b/docs/email.txt index effc5e24cf..55b91b1935 100644 --- a/docs/email.txt +++ b/docs/email.txt @@ -275,7 +275,7 @@ The class has the following methods: There are two ways to call ``attach()``: * You can pass it a single argument that is an - ``email.MIMBase.MIMEBase`` instance. This will be inserted directly + ``email.MIMEBase.MIMEBase`` instance. This will be inserted directly into the resulting message. * Alternatively, you can pass ``attach()`` three arguments: diff --git a/docs/form_preview.txt b/docs/form_preview.txt index 4be7b07a74..e6f9b05f25 100644 --- a/docs/form_preview.txt +++ b/docs/form_preview.txt @@ -45,7 +45,7 @@ How to use ``FormPreview`` 2. Create a ``FormPreview`` subclass that overrides the ``done()`` method:: - from django.contrib.formtools import FormPreview + from django.contrib.formtools.preview import FormPreview from myapp.models import SomeModel class SomeModelFormPreview(FormPreview): diff --git a/docs/model-api.txt b/docs/model-api.txt index b49963d8f5..ca84c84d09 100644 --- a/docs/model-api.txt +++ b/docs/model-api.txt @@ -1013,111 +1013,12 @@ See the `One-to-one relationship model example`_ for a full example. Custom field types ------------------ -**New in Django development version** +If one of the existing model fields cannot be used to fit your purposes, or if +you wish to take advantage of some less common database column types, you can +create your own field class. Full coverage of creating your own fields is +provided in the `Custom Model Fields`_ documentation. -Django's built-in field types don't cover every possible database column type -- -only the common types, such as ``VARCHAR`` and ``INTEGER``. For more obscure -column types, such as geographic polygons or even user-created types such as -`PostgreSQL custom types`_, you can define your own Django ``Field`` subclasses. - -.. _PostgreSQL custom types: http://www.postgresql.org/docs/8.2/interactive/sql-createtype.html - -.. admonition:: Experimental territory - - This is an area of Django that traditionally has not been documented, but - we're starting to include bits of documentation, one feature at a time. - Please forgive the sparseness of this section. - - If you like living on the edge and are comfortable with the risk of - unstable, undocumented APIs, see the code for the core ``Field`` class - in ``django/db/models/fields/__init__.py`` -- but if/when the innards - change, don't say we didn't warn you. - -To create a custom field type, simply subclass ``django.db.models.Field``. -Here is an incomplete list of the methods you should implement: - -``db_type()`` -~~~~~~~~~~~~~ - -Returns the database column data type for the ``Field``, taking into account -the current ``DATABASE_ENGINE`` setting. - -Say you've created a PostgreSQL custom type called ``mytype``. You can use this -field with Django by subclassing ``Field`` and implementing the ``db_type()`` -method, like so:: - - from django.db import models - - class MytypeField(models.Field): - def db_type(self): - return 'mytype' - -Once you have ``MytypeField``, you can use it in any model, just like any other -``Field`` type:: - - class Person(models.Model): - name = models.CharField(max_length=80) - gender = models.CharField(max_length=1) - something_else = MytypeField() - -If you aim to build a database-agnostic application, you should account for -differences in database column types. For example, the date/time column type -in PostgreSQL is called ``timestamp``, while the same column in MySQL is called -``datetime``. The simplest way to handle this in a ``db_type()`` method is to -import the Django settings module and check the ``DATABASE_ENGINE`` setting. -For example:: - - class MyDateField(models.Field): - def db_type(self): - from django.conf import settings - if settings.DATABASE_ENGINE == 'mysql': - return 'datetime' - else: - return 'timestamp' - -The ``db_type()`` method is only called by Django when the framework constructs -the ``CREATE TABLE`` statements for your application -- that is, when you first -create your tables. It's not called at any other time, so it can afford to -execute slightly complex code, such as the ``DATABASE_ENGINE`` check in the -above example. - -Some database column types accept parameters, such as ``CHAR(25)``, where the -parameter ``25`` represents the maximum column length. In cases like these, -it's more flexible if the parameter is specified in the model rather than being -hard-coded in the ``db_type()`` method. For example, it wouldn't make much -sense to have a ``CharMaxlength25Field``, shown here:: - - # This is a silly example of hard-coded parameters. - class CharMaxlength25Field(models.Field): - def db_type(self): - return 'char(25)' - - # In the model: - class MyModel(models.Model): - # ... - my_field = CharMaxlength25Field() - -The better way of doing this would be to make the parameter specifiable at run -time -- i.e., when the class is instantiated. To do that, just implement -``__init__()``, like so:: - - # This is a much more flexible example. - class BetterCharField(models.Field): - def __init__(self, max_length, *args, **kwargs): - self.max_length = max_length - super(BetterCharField, self).__init__(*args, **kwargs) - - def db_type(self): - return 'char(%s)' % self.max_length - - # In the model: - class MyModel(models.Model): - # ... - my_field = BetterCharField(25) - -Note that if you implement ``__init__()`` on a ``Field`` subclass, it's -important to call ``Field.__init__()`` -- i.e., the parent class' -``__init__()`` method. +.. _Custom Model Fields: ../custom_model_fields/ Meta options ============ diff --git a/docs/modpython.txt b/docs/modpython.txt index 5b20046168..c739997ce4 100644 --- a/docs/modpython.txt +++ b/docs/modpython.txt @@ -150,7 +150,7 @@ mess things up. Use the ``PythonInterpreter`` directive to give different SetEnv DJANGO_SETTINGS_MODULE mysite.other_settings - PythonInterpreter mysite_other + PythonInterpreter othersite diff --git a/docs/newforms.txt b/docs/newforms.txt index 5e33a478ee..593e9216d7 100644 --- a/docs/newforms.txt +++ b/docs/newforms.txt @@ -1078,6 +1078,30 @@ fields. We've specified ``auto_id=False`` to simplify the output::

        Sender: A valid e-mail address, please.

        Cc myself:

        +``error_messages`` +~~~~~~~~~~~~~~~~~~ + +**New in Django development version** + +The ``error_messages`` argument lets you override the default messages which the +field will raise. Pass in a dictionary with keys matching the error messages you +want to override. For example:: + + >>> generic = forms.CharField() + >>> generic.clean('') + Traceback (most recent call last): + ... + ValidationError: [u'This field is required.'] + + >>> name = forms.CharField(error_messages={'required': 'Please enter your name'}) + >>> name.clean('') + Traceback (most recent call last): + ... + ValidationError: [u'Please enter your name'] + +In the `built-in Field classes`_ section below, each Field defines the error +message keys it uses. + Dynamic initial values ---------------------- @@ -1143,6 +1167,7 @@ For each field, we describe the default widget used if you don't specify * Normalizes to: A Python ``True`` or ``False`` value. * Validates that the check box is checked (i.e. the value is ``True``) if the field has ``required=True``. + * Error message keys: ``required`` **New in Django development version:** The empty value for a ``CheckboxInput`` (and hence the standard ``BooleanField``) has changed to return ``False`` @@ -1162,6 +1187,7 @@ instead of ``None`` in the development version. * Normalizes to: A Unicode object. * Validates ``max_length`` or ``min_length``, if they are provided. Otherwise, all inputs are valid. + * Error message keys: ``required``, ``max_length``, ``min_length`` Has two optional arguments for validation, ``max_length`` and ``min_length``. If provided, these arguments ensure that the string is at most or at least the @@ -1174,6 +1200,7 @@ given length. * Empty value: ``''`` (an empty string) * Normalizes to: A Unicode object. * Validates that the given value exists in the list of choices. + * Error message keys: ``required``, ``invalid_choice`` Takes one extra argument, ``choices``, which is an iterable (e.g., a list or tuple) of 2-tuples to use as choices for this field. @@ -1186,6 +1213,7 @@ tuple) of 2-tuples to use as choices for this field. * Normalizes to: A Python ``datetime.date`` object. * Validates that the given value is either a ``datetime.date``, ``datetime.datetime`` or string formatted in a particular date format. + * Error message keys: ``required``, ``invalid`` Takes one optional argument, ``input_formats``, which is a list of formats used to attempt to convert a string to a valid ``datetime.date`` object. @@ -1206,6 +1234,7 @@ If no ``input_formats`` argument is provided, the default input formats are:: * Normalizes to: A Python ``datetime.datetime`` object. * Validates that the given value is either a ``datetime.datetime``, ``datetime.date`` or string formatted in a particular datetime format. + * Error message keys: ``required``, ``invalid`` Takes one optional argument, ``input_formats``, which is a list of formats used to attempt to convert a string to a valid ``datetime.datetime`` object. @@ -1235,6 +1264,9 @@ If no ``input_formats`` argument is provided, the default input formats are:: * Normalizes to: A Python ``decimal``. * Validates that the given value is a decimal. Leading and trailing whitespace is ignored. + * Error message keys: ``required``, ``invalid``, ``max_value``, + ``min_value``, ``max_digits``, ``max_decimal_places``, + ``max_whole_digits`` Takes four optional arguments: ``max_value``, ``min_value``, ``max_digits``, and ``decimal_places``. The first two define the limits for the fields value. @@ -1251,6 +1283,7 @@ decimal places permitted. * Normalizes to: A Unicode object. * Validates that the given value is a valid e-mail address, using a moderately complex regular expression. + * Error message keys: ``required``, ``invalid`` Has two optional arguments for validation, ``max_length`` and ``min_length``. If provided, these arguments ensure that the string is at most or at least the @@ -1266,6 +1299,7 @@ given length. * Normalizes to: An ``UploadedFile`` object that wraps the file content and file name into a single object. * Validates that non-empty file data has been bound to the form. + * Error message keys: ``required``, ``invalid``, ``missing``, ``empty`` An ``UploadedFile`` object has two attributes: @@ -1296,6 +1330,8 @@ When you use a ``FileField`` on a form, you must also remember to and file name into a single object. * Validates that file data has been bound to the form, and that the file is of an image format understood by PIL. + * Error message keys: ``required``, ``invalid``, ``missing``, ``empty``, + ``invalid_image`` Using an ImageField requires that the `Python Imaging Library`_ is installed. @@ -1312,6 +1348,8 @@ When you use a ``FileField`` on a form, you must also remember to * Normalizes to: A Python integer or long integer. * Validates that the given value is an integer. Leading and trailing whitespace is allowed, as in Python's ``int()`` function. + * Error message keys: ``required``, ``invalid``, ``max_value``, + ``min_value`` Takes two optional arguments for validation, ``max_value`` and ``min_value``. These control the range of values permitted in the field. @@ -1324,6 +1362,7 @@ These control the range of values permitted in the field. * Normalizes to: A Unicode object. * Validates that the given value is a valid IPv4 address, using a regular expression. + * Error message keys: ``required``, ``invalid`` ``MultipleChoiceField`` ~~~~~~~~~~~~~~~~~~~~~~~ @@ -1333,6 +1372,7 @@ These control the range of values permitted in the field. * Normalizes to: A list of Unicode objects. * Validates that every value in the given list of values exists in the list of choices. + * Error message keys: ``required``, ``invalid_choice``, ``invalid_list`` Takes one extra argument, ``choices``, which is an iterable (e.g., a list or tuple) of 2-tuples to use as choices for this field. @@ -1353,6 +1393,7 @@ tuple) of 2-tuples to use as choices for this field. * Normalizes to: A Unicode object. * Validates that the given value matches against a certain regular expression. + * Error message keys: ``required``, ``invalid`` Takes one required argument, ``regex``, which is a regular expression specified either as a string or a compiled regular expression object. @@ -1364,11 +1405,13 @@ Also takes the following optional arguments: ====================== ===================================================== ``max_length`` Ensures the string has at most this many characters. ``min_length`` Ensures the string has at least this many characters. - ``error_message`` Error message to return for failed validation. If no - message is provided, a generic error message will be - used. ====================== ===================================================== +The optional argument ``error_message`` is also accepted for backwards +compatibility. The preferred way to provide an error message is to use the +``error_messages`` argument, passing a dictionary with ``'invalid'`` as a key +and the error message as the value. + ``TimeField`` ~~~~~~~~~~~~~ @@ -1377,6 +1420,7 @@ Also takes the following optional arguments: * Normalizes to: A Python ``datetime.time`` object. * Validates that the given value is either a ``datetime.time`` or string formatted in a particular time format. + * Error message keys: ``required``, ``invalid`` Takes one optional argument, ``input_formats``, which is a list of formats used to attempt to convert a string to a valid ``datetime.time`` object. @@ -1393,6 +1437,7 @@ If no ``input_formats`` argument is provided, the default input formats are:: * Empty value: ``''`` (an empty string) * Normalizes to: A Unicode object. * Validates that the given value is a valid URL. + * Error message keys: ``required``, ``invalid``, ``invalid_link`` Takes the following optional arguments: diff --git a/docs/serialization.txt b/docs/serialization.txt index fa9b4edd51..cf8b196931 100644 --- a/docs/serialization.txt +++ b/docs/serialization.txt @@ -47,14 +47,14 @@ This is useful if you want to serialize data directly to a file-like object Subset of fields ~~~~~~~~~~~~~~~~ -If you only want a subset of fields to be serialized, you can +If you only want a subset of fields to be serialized, you can specify a ``fields`` argument to the serializer:: from django.core import serializers data = serializers.serialize('xml', SomeModel.objects.all(), fields=('name','size')) In this example, only the ``name`` and ``size`` attributes of each model will -be serialized. +be serialized. .. note:: @@ -111,9 +111,9 @@ Django "ships" with a few included serializers: ``python`` Translates to and from "simple" Python objects (lists, dicts, strings, etc.). Not really all that useful on its own, but used as a base for other serializers. - - ``yaml`` Serializes to YAML (Yet Another Markup Lanuage). This - serializer is only available if PyYAML_ is installed. + + ``yaml`` Serializes to YAML (Yet Another Markup Lanuage). This + serializer is only available if PyYAML_ is installed. ========== ============================================================== .. _json: http://json.org/ @@ -135,6 +135,23 @@ For example:: json_serializer = serializers.get_serializer("json")() json_serializer.serialize(queryset, ensure_ascii=False, stream=response) +Django ships with a copy of simplejson_ in the source. Be aware, that if +you're using that for serializing directly that not all Django output can be +passed unmodified to simplejson. In particular, `lazy translation objects`_ +need a `special encoder`_ written for them. Something like this will work:: + + from django.utils.functional import Promise + from django.utils.encoding import force_unicode + + class LazyEncoder(simplejson.JSONEncoder): + def default(self, obj): + if isinstance(obj, Promise): + return force_unicode(obj) + return obj + +.. _lazy translation objects: ../i18n/#lazy-translation +.. _special encoder: http://svn.red-bean.com/bob/simplejson/tags/simplejson-1.7/docs/index.html + Writing custom serializers `````````````````````````` diff --git a/docs/sitemaps.txt b/docs/sitemaps.txt index 1d4fba2626..eb749dda2f 100644 --- a/docs/sitemaps.txt +++ b/docs/sitemaps.txt @@ -47,7 +47,7 @@ Initialization ============== To activate sitemap generation on your Django site, add this line to your -URLconf_: +URLconf_:: (r'^sitemap.xml$', 'django.contrib.sitemaps.views.sitemap', {'sitemaps': sitemaps}) diff --git a/docs/templates.txt b/docs/templates.txt index 5d5f657747..b85f108bbe 100644 --- a/docs/templates.txt +++ b/docs/templates.txt @@ -299,6 +299,104 @@ it also defines the content that fills the hole in the *parent*. If there were two similarly-named ``{% block %}`` tags in a template, that template's parent wouldn't know which one of the blocks' content to use. +Automatic HTML escaping +======================= + +**New in Django development version** + +A very real problem when creating HTML (and other) output using templates and +variable substitution is the possibility of accidently inserting some variable +value that affects the resulting HTML. For example, a template fragment such as +:: + + Hello, {{ name }}. + +seems like a harmless way to display the user's name. However, if you are +displaying data that the user entered directly and they had entered their name as :: + + + +this would always display a Javascript alert box when the page was loaded. +Similarly, if you were displaying some data generated by another process and it +contained a '<' symbol, you couldn't just dump this straight into your HTML, +because it would be treated as the start of an element. The effects of these +sorts of problems can vary from merely annoying to allowing exploits via `Cross +Site Scripting`_ (XSS) attacks. + +.. _Cross Site Scripting: http://en.wikipedia.org/wiki/Cross-site_scripting + +In order to provide some protection against these problems, Django +provides automatic (but controllable) HTML escaping for data coming from +tempate variables. Inside this tag, any data that comes from template +variables is examined to see if it contains one of the five HTML characters +(<, >, ', " and &) that often need escaping and those characters are converted +to their respective HTML entities. It causes no harm if a character is +converted to an entity when it doesn't need to be, so all five characters are +always converted. + +Since some variables will contain data that is *intended* to be rendered +as HTML, template tag and filter writers can mark their output strings as +requiring no further escaping. For example, the ``unordered_list`` filter is +designed to return raw HTML and we want the template processor to simply +display the results as returned, without applying any escaping. That is taken +care of by the filter. The template author need do nothing special in that +case. + +By default, automatic HTML escaping is always applied. However, sometimes you +will not want this to occur (for example, if you're using the templating +system to create an email). To control automatic escaping inside your template, +wrap the affected content in the ``autoescape`` tag, like so:: + + {% autoescape off %} + Hello {{ name }} + {% endautoescape %} + +The auto-escaping tag passes its effect onto templates that extend the +current one as well as templates included via the ``include`` tag, just like +all block tags. + +The ``autoescape`` tag takes either ``on`` or ``off`` as its argument. At times, you might want to force auto-escaping when it would otherwise be disabled. For example:: + + Auto-escaping is on by default. Hello {{ name }} + + {% autoescape off %} + This will not be auto-escaped: {{ data }}. + + Nor this: {{ other_data }} + {% autoescape on %} + Auto-escaping applies again, {{ name }} + {% endautoescape %} + {% endautoescape %} + +For individual variables, the ``safe`` filter can also be used to indicate +that the contents should not be automatically escaped:: + + This will be escaped: {{ data }} + This will not be escaped: {{ data|safe }} + +Think of *safe* as shorthand for *safe from further escaping* or *can be +safely interpreted as HTML*. In this example, if ``data`` contains ``''``, +the output will be:: + + This will be escaped: <a> + This will not be escaped: + +Generally, you won't need to worry about auto-escaping very much. View +developers and custom filter authors need to think about when their data +shouldn't be escaped and mark it appropriately. They are in a better position +to know when that should happen than the template author, so it is their +responsibility. By default, all output is escaped unless the template +processor is explicitly told otherwise. + +You should also note that if you are trying to write a template that might be +used in situations where automatic escaping is enabled or disabled and you +don't know which (such as when your template is included in other templates), +you can safely write as if you were in an ``{% autoescape off %}`` situation. +Scatter ``escape`` filters around for any variables that need escaping. When +auto-escaping is on, these extra filters won't change the output -- any +variables that use the ``escape`` filter do not have further automatic +escaping applied to them. + Using the built-in reference ============================ @@ -374,6 +472,24 @@ available, and what they do. Built-in tag reference ---------------------- +autoescape +~~~~~~~~~~ + +**New in Django development version** + +Control the current auto-escaping behaviour. This tag takes either ``on`` or +``off`` as an argument and that determines whether auto-escaping is in effect +inside the block. + +When auto-escaping is in effect, all variable content has HTML escaping applied +to it before placing the result into the output (but after any filters have +been applied). This is equivalent to manually applying the ``escape`` filter +attached to each variable. + +The only exceptions are variables that are already marked as 'safe' from +escaping, either by the code that populated the variable, or because it has +the ``safe`` or ``escape`` filters applied. + block ~~~~~ @@ -452,7 +568,7 @@ just like in variable syntax. Sample usage:: - {% filter escape|lower %} + {% filter force_escape|lower %} This text will be HTML-escaped, and will appear in all lowercase. {% endfilter %} @@ -740,7 +856,7 @@ Available format strings: if they're zero and the special-case strings 'midnight' and 'noon' if appropriate. Proprietary extension. - r RFC 822 formatted date. ``'Thu, 21 Dec 2000 16:01:07 +0200'`` + r RFC 2822 formatted date. ``'Thu, 21 Dec 2000 16:01:07 +0200'`` s Seconds, 2 digits with leading zeros. ``'00'`` to ``'59'`` S English ordinal suffix for day of the ``'st'``, ``'nd'``, ``'rd'`` or ``'th'`` month, 2 characters. @@ -1076,6 +1192,10 @@ Returns true if the value is divisible by the argument. escape ~~~~~~ +**New in Django development version:** The behaviour of this filter has +changed slightly in the development version (the affects are only applied +once, after all other filters). + Escapes a string's HTML. Specifically, it makes these replacements: * ``"&"`` to ``"&"`` @@ -1084,6 +1204,16 @@ Escapes a string's HTML. Specifically, it makes these replacements: * ``'"'`` (double quote) to ``'"'`` * ``"'"`` (single quote) to ``'''`` +The escaping is only applied when the string is output, so it does not matter +where in a chained sequence of filters you put ``escape``: it will always be +applied as though it were the last filter. If you want escaping to be applied +immediately, use the ``force_escape`` filter. + +Applying ``escape`` to a variable that would normally have auto-escaping +applied to the result will only result in one round of escaping being done. So +it is safe to use this function even in auto-escaping environments. If you want +multiple escaping passes to be applied, use the ``force_escape`` filter. + filesizeformat ~~~~~~~~~~~~~~ @@ -1106,25 +1236,50 @@ floatformat When used without an argument, rounds a floating-point number to one decimal place -- but only if there's a decimal part to be displayed. For example: - * ``36.123`` gets converted to ``36.1`` - * ``36.15`` gets converted to ``36.2`` - * ``36`` gets converted to ``36`` +======== ======================= ====== +value Template Output +======== ======================= ====== +34.23234 {{ value|floatformat }} 34.2 +34.00000 {{ value|floatformat }} 34 +34.26000 {{ value|floatformat }} 34.3 +======== ======================= ====== -If used with a numeric integer argument, ``floatformat`` rounds a number to that -many decimal places. For example: +If used with a numeric integer argument, ``floatformat`` rounds a number to +that many decimal places. For example: - * ``36.1234`` with floatformat:3 gets converted to ``36.123`` - * ``36`` with floatformat:4 gets converted to ``36.0000`` +======== ========================= ====== +value Template Output +======== ========================= ====== +34.23234 {{ value|floatformat:3 }} 34.232 +34.00000 {{ value|floatformat:3 }} 34.000 +34.26000 {{ value|floatformat:3 }} 34.260 +======== ========================= ====== -If the argument passed to ``floatformat`` is negative, it will round a number to -that many decimal places -- but only if there's a decimal part to be displayed. -For example: +If the argument passed to ``floatformat`` is negative, it will round a number +to that many decimal places -- but only if there's a decimal part to be +displayed. For example: - * ``36.1234`` with floatformat:-3 gets converted to ``36.123`` - * ``36`` with floatformat:-4 gets converted to ``36`` +======== ============================ ====== +value Template Output +======== ============================ ====== +34.23234 {{ value|floatformat:"-3" }} 34.232 +34.00000 {{ value|floatformat:"-3" }} 34 +34.26000 {{ value|floatformat:"-3" }} 34.260 +======== ============================ ====== -Using ``floatformat`` with no argument is equivalent to using ``floatformat`` with -an argument of ``-1``. +Using ``floatformat`` with no argument is equivalent to using ``floatformat`` +with an argument of ``-1``. + +force_escape +~~~~~~~~~~~~ + +**New in Django development version** + +Applies HTML escaping to a string (see the ``escape`` filter for details). +This filter is applied *immediately* and returns a new, escaped string. This +is useful in the rare cases where you need multiple escaping or want to apply +other filters to the escaped results. Normally, you want to use the ``escape`` +filter. get_digit ~~~~~~~~~ @@ -1250,6 +1405,12 @@ Right-aligns the value in a field of a given width. **Argument:** field size +safe +~~~~ + +Marks a string as not requiring further HTML escaping prior to output. When +autoescaping is off, this filter has no effect. + slice ~~~~~ diff --git a/docs/templates_python.txt b/docs/templates_python.txt index bd105888ce..e4658f6461 100644 --- a/docs/templates_python.txt +++ b/docs/templates_python.txt @@ -219,13 +219,13 @@ be replaced with the name of the invalid variable. While ``TEMPLATE_STRING_IF_INVALID`` can be a useful debugging tool, it is a bad idea to turn it on as a 'development default'. - + Many templates, including those in the Admin site, rely upon the silence of the template system when a non-existent variable is encountered. If you assign a value other than ``''`` to ``TEMPLATE_STRING_IF_INVALID``, you will experience rendering problems with these templates and sites. - + Generally, ``TEMPLATE_STRING_IF_INVALID`` should only be enabled in order to debug a specific template problem, then cleared once debugging is complete. @@ -722,6 +722,95 @@ decorator instead:: If you leave off the ``name`` argument, as in the second example above, Django will use the function's name as the filter name. +Filters and auto-escaping +~~~~~~~~~~~~~~~~~~~~~~~~~ + +**New in Django development version** + +When you are writing a custom filter, you need to give some thought to how +this filter will interact with Django's auto-escaping behaviour. Firstly, you +should realise that there are three types of strings that can be passed around +inside the template code: + + * raw strings are the native Python ``str`` or ``unicode`` types. On + output, they are escaped if auto-escaping is in effect and presented + unchanged, otherwise. + + * "safe" strings are strings that are safe from further escaping at output + time. Any necessary escaping has already been done. They are commonly used + for output that contains raw HTML that is intended to be intrepreted on the + client side. + + Internally, these strings are of type ``SafeString`` or ``SafeUnicode``, + although they share a common base class in ``SafeData``, so you can test + for them using code like:: + + if isinstance(value, SafeData): + # Do something with the "safe" string. + + * strings which are marked as "needing escaping" are *always* escaped on + output, regardless of whether they are in an ``autoescape`` block or not. + These strings are only escaped once, however, even if auto-escaping + applies. This type of string is internally represented by the types + ``EscapeString`` and ``EscapeUnicode``. You will not normally need to worry + about these; they exist for the implementation of the ``escape`` filter. + +Inside your filter, you will need to think about three areas in order to be +auto-escaping compliant: + + 1. If your filter returns a string that is ready for direct output (it should + be considered a "safe" string), you should call + ``django.utils.safestring.mark_safe()`` on the result prior to returning. + This will turn the result into the appropriate ``SafeData`` type. This is + often the case when you are returning raw HTML, for example. + + 2. If your filter is given a "safe" string, is it guaranteed to return a + "safe" string? If so, set the ``is_safe`` attribute on the function to be + ``True``. For example, a filter that replaced a word consisting only of + digits with the number spelt out in words is going to be + safe-string-preserving, since it cannot introduce any of the five dangerous + characters: <, >, ", ' or &. We can write:: + + @register.filter + def convert_to_words(value): + # ... implementation here ... + return result + + convert_to_words.is_safe = True + + Note that this filter does not return a universally safe result (it does + not return ``mark_safe(result)``) because if it is handed a raw string such + as '', this will need further escaping in an auto-escape environment. + The ``is_safe`` attribute only talks about the the result when a safe + string is passed into the filter. + + 3. Will your filter behave differently depending upon whether auto-escaping + is currently in effect or not? This is normally a concern when you are + returning mixed content (HTML elements mixed with user-supplied content). + For example, the ``ordered_list`` filter that ships with Django needs to + know whether to escape its content or not. It will always return a safe + string. Since it returns raw HTML, we cannot apply escaping to the + result -- it needs to be done in-situ. + + For these cases, the filter function needs to be told what the current + auto-escaping setting is. Set the ``needs_autoescape`` attribute on the + filter to ``True`` and have your function take an extra argument called + ``autoescape`` with a default value of ``None``. When the filter is called, + the ``autoescape`` keyword argument will be ``True`` if auto-escaping is in + effect. For example, the ``unordered_list`` filter is written as:: + + def unordered_list(value, autoescape=None): + # ... lots of code here ... + + return mark_safe(...) + + unordered_list.is_safe = True + unordered_list.needs_autoescape = True + +By default, both the ``is_safe`` and ``needs_autoescape`` attributes are +``False``. You do not need to specify them if ``False`` is an acceptable +value. + Writing custom template tags ---------------------------- @@ -840,6 +929,43 @@ Ultimately, this decoupling of compilation and rendering results in an efficient template system, because a template can render multiple context without having to be parsed multiple times. +Auto-escaping considerations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The output from template tags is not automatically run through the +auto-escaping filters. However, there are still a couple of things you should +keep in mind when writing a template tag: + +If the ``render()`` function of your template stores the result in a context +variable (rather than returning the result in a string), it should take care +to call ``mark_safe()`` if appropriate. When the variable is ultimately +rendered, it will be affected by the auto-escape setting in effect at the +time, so content that should be safe from further escaping needs to be marked +as such. + +Also, if your template tag creates a new context for performing some +sub-rendering, you should be careful to set the auto-escape attribute to the +current context's value. The ``__init__`` method for the ``Context`` class +takes a parameter called ``autoescape`` that you can use for this purpose. For +example:: + + def render(self, context): + # ... + new_context = Context({'var': obj}, autoescape=context.autoescape) + # ... Do something with new_context ... + +This is not a very common situation, but it is sometimes useful, particularly +if you are rendering a template yourself. For example:: + + def render(self, context): + t = template.load_template('small_fragment.html') + return t.render(Context({'var': obj}, autoescape=context.autoescape)) + +If we had neglected to pass in the current ``context.autoescape`` value to our +new ``Context`` in this example, the results would have *always* been +automatically escaped, which may not be the desired behaviour if the template +tag is used inside a ``{% autoescape off %}`` block. + Registering the tag ~~~~~~~~~~~~~~~~~~~ @@ -917,7 +1043,7 @@ current context, available in the ``render`` method:: def __init__(self, date_to_be_formatted, format_string): self.date_to_be_formatted = date_to_be_formatted self.format_string = format_string - + def render(self, context): try: actual_date = resolve_variable(self.date_to_be_formatted, context) @@ -934,26 +1060,26 @@ format it accordingly. ``template.resolve_variable()`` is still available, but has been deprecated in favor of a new ``template.Variable`` class. Using this class will usually be more efficient than calling ``template.resolve_variable`` - + To use the ``Variable`` class, simply instantiate it with the name of the variable to be resolved, and then call ``variable.resolve(context)``. So, in the development version, the above example would be more correctly written as: - + .. parsed-literal:: - + class FormatTimeNode(template.Node): def __init__(self, date_to_be_formatted, format_string): self.date_to_be_formatted = **Variable(date_to_be_formatted)** self.format_string = format_string - + def render(self, context): try: actual_date = **self.date_to_be_formatted.resolve(context)** return actual_date.strftime(self.format_string) except template.VariableDoesNotExist: return '' - + Changes are highlighted in bold. Variable resolution will throw a ``VariableDoesNotExist`` exception if it cannot diff --git a/docs/testing.txt b/docs/testing.txt index 04c999cda8..7705380eff 100644 --- a/docs/testing.txt +++ b/docs/testing.txt @@ -721,7 +721,6 @@ This means, instead of instantiating a ``Client`` in each test:: ...you can just refer to ``self.client``, like so:: from django.test import TestCase - from django.test.client import Client class SimpleTest(TestCase): def test_details(self): diff --git a/tests/modeltests/field_subclassing/__init__.py b/tests/modeltests/field_subclassing/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/modeltests/field_subclassing/models.py b/tests/modeltests/field_subclassing/models.py new file mode 100644 index 0000000000..6182266c22 --- /dev/null +++ b/tests/modeltests/field_subclassing/models.py @@ -0,0 +1,106 @@ +""" +Tests for field subclassing. +""" + +from django.db import models +from django.utils.encoding import force_unicode +from django.core import serializers + +class Small(object): + """ + A simple class to show that non-trivial Python objects can be used as + attributes. + """ + def __init__(self, first, second): + self.first, self.second = first, second + + def __unicode__(self): + return u'%s%s' % (force_unicode(self.first), force_unicode(self.second)) + + def __str__(self): + return unicode(self).encode('utf-8') + +class SmallField(models.Field): + """ + Turns the "Small" class into a Django field. Because of the similarities + with normal character fields and the fact that Small.__unicode__ does + something sensible, we don't need to implement a lot here. + """ + __metaclass__ = models.SubfieldBase + + def __init__(self, *args, **kwargs): + kwargs['max_length'] = 2 + super(SmallField, self).__init__(*args, **kwargs) + + def get_internal_type(self): + return 'CharField' + + def to_python(self, value): + if isinstance(value, Small): + return value + return Small(value[0], value[1]) + + def get_db_prep_save(self, value): + return unicode(value) + + def get_db_prep_lookup(self, lookup_type, value): + if lookup_type == 'exact': + return force_unicode(value) + if lookup_type == 'in': + return [force_unicode(v) for v in value] + if lookup_type == 'isnull': + return [] + raise TypeError('Invalid lookup type: %r' % lookup_type) + + def flatten_data(self, follow, obj=None): + return {self.attname: force_unicode(self._get_val_from_obj(obj))} + +class MyModel(models.Model): + name = models.CharField(max_length=10) + data = SmallField('small field') + + def __unicode__(self): + return force_unicode(self.name) + +__test__ = {'API_TESTS': ur""" +# Creating a model with custom fields is done as per normal. +>>> s = Small(1, 2) +>>> print s +12 +>>> m = MyModel(name='m', data=s) +>>> m.save() + +# Custom fields still have normal field's attributes. +>>> m._meta.get_field('data').verbose_name +'small field' + +# The m.data attribute has been initialised correctly. It's a Small object. +>>> m.data.first, m.data.second +(1, 2) + +# The data loads back from the database correctly and 'data' has the right type. +>>> m1 = MyModel.objects.get(pk=m.pk) +>>> isinstance(m1.data, Small) +True +>>> print m1.data +12 + +# We can do normal filtering on the custom field (and will get an error when we +# use a lookup type that does not make sense). +>>> s1 = Small(1, 3) +>>> s2 = Small('a', 'b') +>>> MyModel.objects.filter(data__in=[s, s1, s2]) +[] +>>> MyModel.objects.filter(data__lt=s) +Traceback (most recent call last): +... +TypeError: Invalid lookup type: 'lt' + +# Serialization works, too. +>>> stream = serializers.serialize("json", MyModel.objects.all()) +>>> stream +'[{"pk": 1, "model": "field_subclassing.mymodel", "fields": {"data": "12", "name": "m"}}]' +>>> obj = list(serializers.deserialize("json", stream))[0] +>>> obj.object == m +True +"""} diff --git a/tests/modeltests/model_forms/models.py b/tests/modeltests/model_forms/models.py index bc14c117d5..e4e230c98d 100644 --- a/tests/modeltests/model_forms/models.py +++ b/tests/modeltests/model_forms/models.py @@ -440,6 +440,8 @@ the data in the database when the form is instantiated. >>> from django.newforms import ModelChoiceField, ModelMultipleChoiceField >>> f = ModelChoiceField(Category.objects.all()) +>>> list(f.choices) +[(u'', u'---------'), (1, u'Entertainment'), (2, u"It's a test"), (3, u'Third'), (4, u'Fourth')] >>> f.clean('') Traceback (most recent call last): ... @@ -485,9 +487,23 @@ Traceback (most recent call last): ... ValidationError: [u'Select a valid choice. That choice is not one of the available choices.'] +# queryset can be changed after the field is created. +>>> f.queryset = Category.objects.exclude(name='Fourth') +>>> list(f.choices) +[(u'', u'---------'), (1, u'Entertainment'), (2, u"It's a test"), (3, u'Third')] +>>> f.clean(3) + +>>> f.clean(4) +Traceback (most recent call last): +... +ValidationError: [u'Select a valid choice. That choice is not one of the available choices.'] + + # ModelMultipleChoiceField #################################################### >>> f = ModelMultipleChoiceField(Category.objects.all()) +>>> list(f.choices) +[(1, u'Entertainment'), (2, u"It's a test"), (3, u'Third'), (4, u'Fourth')] >>> f.clean(None) Traceback (most recent call last): ... @@ -517,7 +533,7 @@ Traceback (most recent call last): ... ValidationError: [u'Enter a list of values.'] -# Add a Category object *after* the ModelChoiceField has already been +# Add a Category object *after* the ModelMultipleChoiceField has already been # instantiated. This proves clean() checks the database during clean() rather # than caching it at time of instantiation. >>> Category.objects.create(id=6, name='Sixth', url='6th') @@ -525,7 +541,7 @@ ValidationError: [u'Enter a list of values.'] >>> f.clean([6]) [] -# Delete a Category object *after* the ModelChoiceField has already been +# Delete a Category object *after* the ModelMultipleChoiceField has already been # instantiated. This proves clean() checks the database during clean() rather # than caching it at time of instantiation. >>> Category.objects.get(url='6th').delete() @@ -552,6 +568,22 @@ Traceback (most recent call last): ... ValidationError: [u'Select a valid choice. 10 is not one of the available choices.'] +# queryset can be changed after the field is created. +>>> f.queryset = Category.objects.exclude(name='Fourth') +>>> list(f.choices) +[(1, u'Entertainment'), (2, u"It's a test"), (3, u'Third')] +>>> f.clean([3]) +[] +>>> f.clean([4]) +Traceback (most recent call last): +... +ValidationError: [u'Select a valid choice. 4 is not one of the available choices.'] +>>> f.clean(['3', '4']) +Traceback (most recent call last): +... +ValidationError: [u'Select a valid choice. 4 is not one of the available choices.'] + + # PhoneNumberField ############################################################ >>> PhoneNumberForm = form_for_model(PhoneNumber) diff --git a/tests/modeltests/test_client/models.py b/tests/modeltests/test_client/models.py index 2df5d3cf77..c7aaaff67d 100644 --- a/tests/modeltests/test_client/models.py +++ b/tests/modeltests/test_client/models.py @@ -4,7 +4,7 @@ The test client is a class that can act like a simple browser for testing purposes. - + It allows the user to compose GET and POST requests, and obtain the response that the server gave to those requests. The server Response objects are annotated with the details @@ -15,8 +15,8 @@ Client objects are stateful - they will retain cookie (and thus session) details for the lifetime of the Client instance. This is not intended as a replacement for Twill,Selenium, or -other browser automation frameworks - it is here to allow -testing against the contexts and templates produced by a view, +other browser automation frameworks - it is here to allow +testing against the contexts and templates produced by a view, rather than the HTML rendered to the end-user. """ @@ -25,14 +25,14 @@ from django.core import mail class ClientTest(TestCase): fixtures = ['testdata.json'] - + def test_get_view(self): "GET a view" # The data is ignored, but let's check it doesn't crash the system # anyway. data = {'var': u'\xf2'} response = self.client.get('/test_client/get_view/', data) - + # Check some response details self.assertContains(response, 'This is a test') self.assertEqual(response.context['var'], u'\xf2') @@ -41,36 +41,36 @@ class ClientTest(TestCase): def test_get_post_view(self): "GET a view that normally expects POSTs" response = self.client.get('/test_client/post_view/', {}) - + # Check some response details self.assertEqual(response.status_code, 200) self.assertEqual(response.template.name, 'Empty GET Template') self.assertTemplateUsed(response, 'Empty GET Template') self.assertTemplateNotUsed(response, 'Empty POST Template') - + def test_empty_post(self): "POST an empty dictionary to a view" response = self.client.post('/test_client/post_view/', {}) - + # Check some response details self.assertEqual(response.status_code, 200) self.assertEqual(response.template.name, 'Empty POST Template') self.assertTemplateNotUsed(response, 'Empty GET Template') self.assertTemplateUsed(response, 'Empty POST Template') - + def test_post(self): "POST some data to a view" post_data = { 'value': 37 } response = self.client.post('/test_client/post_view/', post_data) - + # Check some response details self.assertEqual(response.status_code, 200) self.assertEqual(response.context['data'], '37') self.assertEqual(response.template.name, 'POST Template') self.failUnless('Data received' in response.content) - + def test_raw_post(self): "POST raw data (with a content type) to a view" test_doc = """BlinkMalcolm Gladwell""" @@ -83,18 +83,21 @@ class ClientTest(TestCase): def test_redirect(self): "GET a URL that redirects elsewhere" response = self.client.get('/test_client/redirect_view/') - # Check that the response was a 302 (redirect) - self.assertRedirects(response, 'http://testserver/test_client/get_view/') - - client_providing_host = Client(HTTP_HOST='django.testserver') + # Check that the response was a 302 (redirect) and that + # assertRedirect() understands to put an implicit http://testserver/ in + # front of non-absolute URLs. + self.assertRedirects(response, '/test_client/get_view/') + + host = 'django.testserver' + client_providing_host = Client(HTTP_HOST=host) response = client_providing_host.get('/test_client/redirect_view/') # Check that the response was a 302 (redirect) with absolute URI - self.assertRedirects(response, 'http://django.testserver/test_client/get_view/') - + self.assertRedirects(response, '/test_client/get_view/', host=host) + def test_redirect_with_query(self): "GET a URL that redirects with given GET parameters" response = self.client.get('/test_client/redirect_view/', {'var': 'value'}) - + # Check if parameters are intact self.assertRedirects(response, 'http://testserver/test_client/get_view/?var=value') @@ -112,7 +115,7 @@ class ClientTest(TestCase): def test_redirect_to_strange_location(self): "GET a URL that redirects to a non-200 page" response = self.client.get('/test_client/double_redirect_view/') - + # Check that the response was a 302, and that # the attempt to get the redirection location returned 301 when retrieved self.assertRedirects(response, 'http://testserver/test_client/permanent_redirect_view/', target_status_code=301) @@ -120,7 +123,7 @@ class ClientTest(TestCase): def test_notfound_response(self): "GET a URL that responds as '404:Not Found'" response = self.client.get('/test_client/bad_view/') - + # Check that the response was a 404, and that the content contains MAGIC self.assertContains(response, 'MAGIC', status_code=404) @@ -148,12 +151,12 @@ class ClientTest(TestCase): self.assertTemplateUsed(response, "Form GET Template") # Check that the multi-value data has been rolled out ok self.assertContains(response, 'Select a valid choice.', 0) - + def test_incomplete_data_form(self): "POST incomplete data to a form" post_data = { 'text': 'Hello World', - 'value': 37 + 'value': 37 } response = self.client.post('/test_client/form_view/', post_data) self.assertContains(response, 'This field is required.', 3) @@ -198,7 +201,7 @@ class ClientTest(TestCase): "POST incomplete data to a form using multiple templates" post_data = { 'text': 'Hello World', - 'value': 37 + 'value': 37 } response = self.client.post('/test_client/form_view_with_template/', post_data) self.assertContains(response, 'POST data has errors') @@ -226,21 +229,21 @@ class ClientTest(TestCase): self.assertTemplateNotUsed(response, "Invalid POST Template") self.assertFormError(response, 'form', 'email', 'Enter a valid e-mail address.') - + def test_unknown_page(self): "GET an invalid URL" response = self.client.get('/test_client/unknown_view/') - + # Check that the response was a 404 self.assertEqual(response.status_code, 404) - + def test_view_with_login(self): "Request a page that is protected with @login_required" - + # Get the page without logging in. Should result in 302. response = self.client.get('/test_client/login_protected_view/') self.assertRedirects(response, 'http://testserver/accounts/login/?next=/test_client/login_protected_view/') - + # Log in login = self.client.login(username='testclient', password='password') self.failUnless(login, 'Could not log in') @@ -250,9 +253,25 @@ class ClientTest(TestCase): self.assertEqual(response.status_code, 200) self.assertEqual(response.context['user'].username, 'testclient') + def test_view_with_method_login(self): + "Request a page that is protected with a @login_required method" + + # Get the page without logging in. Should result in 302. + response = self.client.get('/test_client/login_protected_method_view/') + self.assertRedirects(response, 'http://testserver/accounts/login/?next=/test_client/login_protected_method_view/') + + # Log in + login = self.client.login(username='testclient', password='password') + self.failUnless(login, 'Could not log in') + + # Request a page that requires a login + response = self.client.get('/test_client/login_protected_method_view/') + self.assertEqual(response.status_code, 200) + self.assertEqual(response.context['user'].username, 'testclient') + def test_view_with_login_and_custom_redirect(self): "Request a page that is protected with @login_required(redirect_field_name='redirect_to')" - + # Get the page without logging in. Should result in 302. response = self.client.get('/test_client/login_protected_view_custom_redirect/') self.assertRedirects(response, 'http://testserver/accounts/login/?redirect_to=/test_client/login_protected_view_custom_redirect/') @@ -295,6 +314,40 @@ class ClientTest(TestCase): response = self.client.get('/test_client/login_protected_view/') self.assertRedirects(response, 'http://testserver/accounts/login/?next=/test_client/login_protected_view/') + def test_view_with_permissions(self): + "Request a page that is protected with @permission_required" + + # Get the page without logging in. Should result in 302. + response = self.client.get('/test_client/permission_protected_view/') + self.assertRedirects(response, 'http://testserver/accounts/login/?next=/test_client/permission_protected_view/') + + # Log in + login = self.client.login(username='testclient', password='password') + self.failUnless(login, 'Could not log in') + + # Log in with wrong permissions. Should result in 302. + response = self.client.get('/test_client/permission_protected_view/') + self.assertRedirects(response, 'http://testserver/accounts/login/?next=/test_client/permission_protected_view/') + + # TODO: Log in with right permissions and request the page again + + def test_view_with_method_permissions(self): + "Request a page that is protected with a @permission_required method" + + # Get the page without logging in. Should result in 302. + response = self.client.get('/test_client/permission_protected_method_view/') + self.assertRedirects(response, 'http://testserver/accounts/login/?next=/test_client/permission_protected_method_view/') + + # Log in + login = self.client.login(username='testclient', password='password') + self.failUnless(login, 'Could not log in') + + # Log in with wrong permissions. Should result in 302. + response = self.client.get('/test_client/permission_protected_method_view/') + self.assertRedirects(response, 'http://testserver/accounts/login/?next=/test_client/permission_protected_method_view/') + + # TODO: Log in with right permissions and request the page again + def test_session_modifying_view(self): "Request a page that modifies the session" # Session value isn't set initially @@ -303,53 +356,53 @@ class ClientTest(TestCase): self.fail("Shouldn't have a session value") except KeyError: pass - + from django.contrib.sessions.models import Session response = self.client.post('/test_client/session_view/') - + # Check that the session was modified self.assertEquals(self.client.session['tobacconist'], 'hovercraft') def test_view_with_exception(self): "Request a page that is known to throw an error" self.assertRaises(KeyError, self.client.get, "/test_client/broken_view/") - + #Try the same assertion, a different way try: self.client.get('/test_client/broken_view/') self.fail('Should raise an error') except KeyError: pass - + def test_mail_sending(self): "Test that mail is redirected to a dummy outbox during test setup" - + response = self.client.get('/test_client/mail_sending_view/') self.assertEqual(response.status_code, 200) - + self.assertEqual(len(mail.outbox), 1) self.assertEqual(mail.outbox[0].subject, 'Test message') self.assertEqual(mail.outbox[0].body, 'This is a test email') - self.assertEqual(mail.outbox[0].from_email, 'from@example.com') + self.assertEqual(mail.outbox[0].from_email, 'from@example.com') self.assertEqual(mail.outbox[0].to[0], 'first@example.com') self.assertEqual(mail.outbox[0].to[1], 'second@example.com') def test_mass_mail_sending(self): "Test that mass mail is redirected to a dummy outbox during test setup" - + response = self.client.get('/test_client/mass_mail_sending_view/') self.assertEqual(response.status_code, 200) - + self.assertEqual(len(mail.outbox), 2) self.assertEqual(mail.outbox[0].subject, 'First Test message') self.assertEqual(mail.outbox[0].body, 'This is the first test email') - self.assertEqual(mail.outbox[0].from_email, 'from@example.com') + self.assertEqual(mail.outbox[0].from_email, 'from@example.com') self.assertEqual(mail.outbox[0].to[0], 'first@example.com') self.assertEqual(mail.outbox[0].to[1], 'second@example.com') self.assertEqual(mail.outbox[1].subject, 'Second Test message') self.assertEqual(mail.outbox[1].body, 'This is the second test email') - self.assertEqual(mail.outbox[1].from_email, 'from@example.com') + self.assertEqual(mail.outbox[1].from_email, 'from@example.com') self.assertEqual(mail.outbox[1].to[0], 'second@example.com') self.assertEqual(mail.outbox[1].to[1], 'third@example.com') - + diff --git a/tests/modeltests/test_client/urls.py b/tests/modeltests/test_client/urls.py index 3779a0ecd1..09ee7eaf34 100644 --- a/tests/modeltests/test_client/urls.py +++ b/tests/modeltests/test_client/urls.py @@ -13,7 +13,10 @@ urlpatterns = patterns('', (r'^form_view/$', views.form_view), (r'^form_view_with_template/$', views.form_view_with_template), (r'^login_protected_view/$', views.login_protected_view), + (r'^login_protected_method_view/$', views.login_protected_method_view), (r'^login_protected_view_custom_redirect/$', views.login_protected_view_changed_redirect), + (r'^permission_protected_view/$', views.permission_protected_view), + (r'^permission_protected_method_view/$', views.permission_protected_method_view), (r'^session_view/$', views.session_view), (r'^broken_view/$', views.broken_view), (r'^mail_sending_view/$', views.mail_sending_view), diff --git a/tests/modeltests/test_client/views.py b/tests/modeltests/test_client/views.py index c406e17d30..3f4a54c5bd 100644 --- a/tests/modeltests/test_client/views.py +++ b/tests/modeltests/test_client/views.py @@ -3,7 +3,7 @@ from xml.dom.minidom import parseString from django.core.mail import EmailMessage, SMTPConnection from django.template import Context, Template from django.http import HttpResponse, HttpResponseRedirect, HttpResponseNotFound -from django.contrib.auth.decorators import login_required +from django.contrib.auth.decorators import login_required, permission_required from django.newforms.forms import Form from django.newforms import fields from django.shortcuts import render_to_response @@ -130,6 +130,38 @@ def login_protected_view_changed_redirect(request): return HttpResponse(t.render(c)) login_protected_view_changed_redirect = login_required(redirect_field_name="redirect_to")(login_protected_view_changed_redirect) +def permission_protected_view(request): + "A simple view that is permission protected." + t = Template('This is a permission protected test. ' + 'Username is {{ user.username }}. ' + 'Permissions are {{ user.get_all_permissions }}.' , + name='Permissions Template') + c = Context({'user': request.user}) + return HttpResponse(t.render(c)) +permission_protected_view = permission_required('modeltests.test_perm')(permission_protected_view) + +class _ViewManager(object): + def login_protected_view(self, request): + t = Template('This is a login protected test using a method. ' + 'Username is {{ user.username }}.', + name='Login Method Template') + c = Context({'user': request.user}) + return HttpResponse(t.render(c)) + login_protected_view = login_required(login_protected_view) + + def permission_protected_view(self, request): + t = Template('This is a permission protected test using a method. ' + 'Username is {{ user.username }}. ' + 'Permissions are {{ user.get_all_permissions }}.' , + name='Permissions Template') + c = Context({'user': request.user}) + return HttpResponse(t.render(c)) + permission_protected_view = permission_required('modeltests.test_perm')(permission_protected_view) + +_view_manager = _ViewManager() +login_protected_method_view = _view_manager.login_protected_view +permission_protected_method_view = _view_manager.permission_protected_view + def session_view(request): "A view that modifies the session" request.session['tobacconist'] = 'hovercraft' diff --git a/tests/regressiontests/defaultfilters/tests.py b/tests/regressiontests/defaultfilters/tests.py index 9482f1cc9f..26d448900d 100644 --- a/tests/regressiontests/defaultfilters/tests.py +++ b/tests/regressiontests/defaultfilters/tests.py @@ -17,6 +17,10 @@ u'0' u'7.700' >>> floatformat(6.000000,3) u'6.000' +>>> floatformat(6.200000, 3) +u'6.200' +>>> floatformat(6.200000, -3) +u'6.200' >>> floatformat(13.1031,-3) u'13.103' >>> floatformat(11.1197, -2) @@ -190,10 +194,10 @@ u'a stri to be maled' >>> cut(u'a string to be mangled', 'strings') u'a string to be mangled' ->>> escape(u' here') +>>> force_escape(u' here') u'<some html & special characters > here' ->>> escape(u' here ĐÅ€£') +>>> force_escape(u' here ĐÅ€£') u'<some html & special characters > here \xc4\x90\xc3\x85\xe2\x82\xac\xc2\xa3' >>> linebreaks(u'line 1') diff --git a/tests/regressiontests/forms/error_messages.py b/tests/regressiontests/forms/error_messages.py new file mode 100644 index 0000000000..ff7e110f6f --- /dev/null +++ b/tests/regressiontests/forms/error_messages.py @@ -0,0 +1,315 @@ +# -*- coding: utf-8 -*- +tests = r""" +>>> from django.newforms import * + +# CharField ################################################################### + +>>> e = {'required': 'REQUIRED'} +>>> e['min_length'] = 'LENGTH %(length)s, MIN LENGTH %(min)s' +>>> e['max_length'] = 'LENGTH %(length)s, MAX LENGTH %(max)s' +>>> f = CharField(min_length=5, max_length=10, error_messages=e) +>>> f.clean('') +Traceback (most recent call last): +... +ValidationError: [u'REQUIRED'] +>>> f.clean('1234') +Traceback (most recent call last): +... +ValidationError: [u'LENGTH 4, MIN LENGTH 5'] +>>> f.clean('12345678901') +Traceback (most recent call last): +... +ValidationError: [u'LENGTH 11, MAX LENGTH 10'] + +# IntegerField ################################################################ + +>>> e = {'required': 'REQUIRED'} +>>> e['invalid'] = 'INVALID' +>>> e['min_value'] = 'MIN VALUE IS %s' +>>> e['max_value'] = 'MAX VALUE IS %s' +>>> f = IntegerField(min_value=5, max_value=10, error_messages=e) +>>> f.clean('') +Traceback (most recent call last): +... +ValidationError: [u'REQUIRED'] +>>> f.clean('abc') +Traceback (most recent call last): +... +ValidationError: [u'INVALID'] +>>> f.clean('4') +Traceback (most recent call last): +... +ValidationError: [u'MIN VALUE IS 5'] +>>> f.clean('11') +Traceback (most recent call last): +... +ValidationError: [u'MAX VALUE IS 10'] + +# FloatField ################################################################## + +>>> e = {'required': 'REQUIRED'} +>>> e['invalid'] = 'INVALID' +>>> e['min_value'] = 'MIN VALUE IS %s' +>>> e['max_value'] = 'MAX VALUE IS %s' +>>> f = FloatField(min_value=5, max_value=10, error_messages=e) +>>> f.clean('') +Traceback (most recent call last): +... +ValidationError: [u'REQUIRED'] +>>> f.clean('abc') +Traceback (most recent call last): +... +ValidationError: [u'INVALID'] +>>> f.clean('4') +Traceback (most recent call last): +... +ValidationError: [u'MIN VALUE IS 5'] +>>> f.clean('11') +Traceback (most recent call last): +... +ValidationError: [u'MAX VALUE IS 10'] + +# DecimalField ################################################################ + +>>> e = {'required': 'REQUIRED'} +>>> e['invalid'] = 'INVALID' +>>> e['min_value'] = 'MIN VALUE IS %s' +>>> e['max_value'] = 'MAX VALUE IS %s' +>>> e['max_digits'] = 'MAX DIGITS IS %s' +>>> e['max_decimal_places'] = 'MAX DP IS %s' +>>> e['max_whole_digits'] = 'MAX DIGITS BEFORE DP IS %s' +>>> f = DecimalField(min_value=5, max_value=10, error_messages=e) +>>> f2 = DecimalField(max_digits=4, decimal_places=2, error_messages=e) +>>> f.clean('') +Traceback (most recent call last): +... +ValidationError: [u'REQUIRED'] +>>> f.clean('abc') +Traceback (most recent call last): +... +ValidationError: [u'INVALID'] +>>> f.clean('4') +Traceback (most recent call last): +... +ValidationError: [u'MIN VALUE IS 5'] +>>> f.clean('11') +Traceback (most recent call last): +... +ValidationError: [u'MAX VALUE IS 10'] +>>> f2.clean('123.45') +Traceback (most recent call last): +... +ValidationError: [u'MAX DIGITS IS 4'] +>>> f2.clean('1.234') +Traceback (most recent call last): +... +ValidationError: [u'MAX DP IS 2'] +>>> f2.clean('123.4') +Traceback (most recent call last): +... +ValidationError: [u'MAX DIGITS BEFORE DP IS 2'] + +# DateField ################################################################### + +>>> e = {'required': 'REQUIRED'} +>>> e['invalid'] = 'INVALID' +>>> f = DateField(error_messages=e) +>>> f.clean('') +Traceback (most recent call last): +... +ValidationError: [u'REQUIRED'] +>>> f.clean('abc') +Traceback (most recent call last): +... +ValidationError: [u'INVALID'] + +# TimeField ################################################################### + +>>> e = {'required': 'REQUIRED'} +>>> e['invalid'] = 'INVALID' +>>> f = TimeField(error_messages=e) +>>> f.clean('') +Traceback (most recent call last): +... +ValidationError: [u'REQUIRED'] +>>> f.clean('abc') +Traceback (most recent call last): +... +ValidationError: [u'INVALID'] + +# DateTimeField ############################################################### + +>>> e = {'required': 'REQUIRED'} +>>> e['invalid'] = 'INVALID' +>>> f = DateTimeField(error_messages=e) +>>> f.clean('') +Traceback (most recent call last): +... +ValidationError: [u'REQUIRED'] +>>> f.clean('abc') +Traceback (most recent call last): +... +ValidationError: [u'INVALID'] + +# RegexField ################################################################## + +>>> e = {'required': 'REQUIRED'} +>>> e['invalid'] = 'INVALID' +>>> e['min_length'] = 'LENGTH %(length)s, MIN LENGTH %(min)s' +>>> e['max_length'] = 'LENGTH %(length)s, MAX LENGTH %(max)s' +>>> f = RegexField(r'^\d+$', min_length=5, max_length=10, error_messages=e) +>>> f.clean('') +Traceback (most recent call last): +... +ValidationError: [u'REQUIRED'] +>>> f.clean('abcde') +Traceback (most recent call last): +... +ValidationError: [u'INVALID'] +>>> f.clean('1234') +Traceback (most recent call last): +... +ValidationError: [u'LENGTH 4, MIN LENGTH 5'] +>>> f.clean('12345678901') +Traceback (most recent call last): +... +ValidationError: [u'LENGTH 11, MAX LENGTH 10'] + +# EmailField ################################################################## + +>>> e = {'required': 'REQUIRED'} +>>> e['invalid'] = 'INVALID' +>>> e['min_length'] = 'LENGTH %(length)s, MIN LENGTH %(min)s' +>>> e['max_length'] = 'LENGTH %(length)s, MAX LENGTH %(max)s' +>>> f = EmailField(min_length=8, max_length=10, error_messages=e) +>>> f.clean('') +Traceback (most recent call last): +... +ValidationError: [u'REQUIRED'] +>>> f.clean('abcdefgh') +Traceback (most recent call last): +... +ValidationError: [u'INVALID'] +>>> f.clean('a@b.com') +Traceback (most recent call last): +... +ValidationError: [u'LENGTH 7, MIN LENGTH 8'] +>>> f.clean('aye@bee.com') +Traceback (most recent call last): +... +ValidationError: [u'LENGTH 11, MAX LENGTH 10'] + +# FileField ################################################################## + +>>> e = {'required': 'REQUIRED'} +>>> e['invalid'] = 'INVALID' +>>> e['missing'] = 'MISSING' +>>> e['empty'] = 'EMPTY FILE' +>>> f = FileField(error_messages=e) +>>> f.clean('') +Traceback (most recent call last): +... +ValidationError: [u'REQUIRED'] +>>> f.clean('abc') +Traceback (most recent call last): +... +ValidationError: [u'INVALID'] +>>> f.clean({}) +Traceback (most recent call last): +... +ValidationError: [u'MISSING'] +>>> f.clean({'filename': 'name', 'content':''}) +Traceback (most recent call last): +... +ValidationError: [u'EMPTY FILE'] + +# URLField ################################################################## + +>>> e = {'required': 'REQUIRED'} +>>> e['invalid'] = 'INVALID' +>>> e['invalid_link'] = 'INVALID LINK' +>>> f = URLField(verify_exists=True, error_messages=e) +>>> f.clean('') +Traceback (most recent call last): +... +ValidationError: [u'REQUIRED'] +>>> f.clean('abc.c') +Traceback (most recent call last): +... +ValidationError: [u'INVALID'] +>>> f.clean('http://www.jfoiwjfoi23jfoijoaijfoiwjofiwjefewl.com') +Traceback (most recent call last): +... +ValidationError: [u'INVALID LINK'] + +# BooleanField ################################################################ + +>>> e = {'required': 'REQUIRED'} +>>> f = BooleanField(error_messages=e) +>>> f.clean('') +Traceback (most recent call last): +... +ValidationError: [u'REQUIRED'] + +# ChoiceField ################################################################# + +>>> e = {'required': 'REQUIRED'} +>>> e['invalid_choice'] = '%(value)s IS INVALID CHOICE' +>>> f = ChoiceField(choices=[('a', 'aye')], error_messages=e) +>>> f.clean('') +Traceback (most recent call last): +... +ValidationError: [u'REQUIRED'] +>>> f.clean('b') +Traceback (most recent call last): +... +ValidationError: [u'b IS INVALID CHOICE'] + +# MultipleChoiceField ######################################################### + +>>> e = {'required': 'REQUIRED'} +>>> e['invalid_choice'] = '%(value)s IS INVALID CHOICE' +>>> e['invalid_list'] = 'NOT A LIST' +>>> f = MultipleChoiceField(choices=[('a', 'aye')], error_messages=e) +>>> f.clean('') +Traceback (most recent call last): +... +ValidationError: [u'REQUIRED'] +>>> f.clean('b') +Traceback (most recent call last): +... +ValidationError: [u'NOT A LIST'] +>>> f.clean(['b']) +Traceback (most recent call last): +... +ValidationError: [u'b IS INVALID CHOICE'] + +# SplitDateTimeField ########################################################## + +>>> e = {'required': 'REQUIRED'} +>>> e['invalid_date'] = 'INVALID DATE' +>>> e['invalid_time'] = 'INVALID TIME' +>>> f = SplitDateTimeField(error_messages=e) +>>> f.clean('') +Traceback (most recent call last): +... +ValidationError: [u'REQUIRED'] +>>> f.clean(['a', 'b']) +Traceback (most recent call last): +... +ValidationError: [u'INVALID DATE', u'INVALID TIME'] + +# IPAddressField ############################################################## + +>>> e = {'required': 'REQUIRED'} +>>> e['invalid'] = 'INVALID IP ADDRESS' +>>> f = IPAddressField(error_messages=e) +>>> f.clean('') +Traceback (most recent call last): +... +ValidationError: [u'REQUIRED'] +>>> f.clean('127.0.0') +Traceback (most recent call last): +... +ValidationError: [u'INVALID IP ADDRESS'] +""" diff --git a/tests/regressiontests/forms/extra.py b/tests/regressiontests/forms/extra.py index 7f6175f649..9dff4071f1 100644 --- a/tests/regressiontests/forms/extra.py +++ b/tests/regressiontests/forms/extra.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- tests = r""" >>> from django.newforms import * +>>> from django.utils.encoding import force_unicode >>> import datetime >>> import time >>> import re @@ -362,7 +363,7 @@ u'sirrobin' ... return self.as_divs() ... def as_divs(self): ... if not self: return u'' -... return u'
        %s
        ' % ''.join([u'
        %s
        ' % e for e in self]) +... return u'
        %s
        ' % ''.join([u'
        %s
        ' % force_unicode(e) for e in self]) >>> class CommentForm(Form): ... name = CharField(max_length=50, required=False) ... email = EmailField() diff --git a/tests/regressiontests/forms/forms.py b/tests/regressiontests/forms/forms.py index ed88e3a6bb..7c0cf8abf3 100644 --- a/tests/regressiontests/forms/forms.py +++ b/tests/regressiontests/forms/forms.py @@ -1554,7 +1554,7 @@ does not have help text, nothing will be output. ... ''') >>> print t.render(Context({'form': UserRegistration(auto_id=False)}))
        -

        Username:
        Good luck picking a username that doesn't already exist.

        +

        Username:
        Good luck picking a username that doesn't already exist.

        Password1:

        Password2:

        diff --git a/tests/regressiontests/forms/regressions.py b/tests/regressiontests/forms/regressions.py index 1bfb425188..1bb6f6e7e5 100644 --- a/tests/regressiontests/forms/regressions.py +++ b/tests/regressiontests/forms/regressions.py @@ -26,7 +26,6 @@ There were some problems with form translations in #3600 Translations are done at rendering time, so multi-lingual apps can define forms early and still send back the right translation. -# XFAIL >>> activate('de') >>> print f.as_p()

        diff --git a/tests/regressiontests/forms/tests.py b/tests/regressiontests/forms/tests.py index aa33386d09..6acd4f2e1d 100644 --- a/tests/regressiontests/forms/tests.py +++ b/tests/regressiontests/forms/tests.py @@ -2,6 +2,7 @@ from extra import tests as extra_tests from fields import tests as fields_tests from forms import tests as form_tests +from error_messages import tests as custom_error_message_tests from localflavor.ar import tests as localflavor_ar_tests from localflavor.au import tests as localflavor_au_tests from localflavor.br import tests as localflavor_br_tests @@ -29,6 +30,7 @@ __test__ = { 'extra_tests': extra_tests, 'fields_tests': fields_tests, 'form_tests': form_tests, + 'custom_error_message_tests': custom_error_message_tests, 'localflavor_ar_tests': localflavor_ar_tests, 'localflavor_au_tests': localflavor_au_tests, 'localflavor_br_tests': localflavor_br_tests, @@ -48,7 +50,7 @@ __test__ = { 'localflavor_sk_tests': localflavor_sk_tests, 'localflavor_uk_tests': localflavor_uk_tests, 'localflavor_us_tests': localflavor_us_tests, - 'regressions_tests': regression_tests, + 'regression_tests': regression_tests, 'util_tests': util_tests, 'widgets_tests': widgets_tests, } diff --git a/tests/regressiontests/forms/util.py b/tests/regressiontests/forms/util.py index 4f81709082..bfaf73f6bc 100644 --- a/tests/regressiontests/forms/util.py +++ b/tests/regressiontests/forms/util.py @@ -42,4 +42,11 @@ u'' # Can take a mixture in a list. >>> print ValidationError(["First error.", u"Not \u03C0.", ugettext_lazy("Error.")]).messages
        • First error.
        • Not π.
        • Error.
        + +>>> class VeryBadError: +... def __unicode__(self): return u"A very bad error." + +# Can take a non-string. +>>> print ValidationError(VeryBadError()).messages +
        • A very bad error.
        """ diff --git a/tests/regressiontests/humanize/tests.py b/tests/regressiontests/humanize/tests.py index 196488ba6e..6f60c6d6f9 100644 --- a/tests/regressiontests/humanize/tests.py +++ b/tests/regressiontests/humanize/tests.py @@ -3,6 +3,7 @@ from datetime import timedelta, date from django.template import Template, Context, add_to_builtins from django.utils.dateformat import DateFormat from django.utils.translation import ugettext as _ +from django.utils.html import escape add_to_builtins('django.contrib.humanize.templatetags.humanize') @@ -15,7 +16,7 @@ class HumanizeTests(unittest.TestCase): test_content = test_list[index] t = Template('{{ test_content|%s }}' % method) rendered = t.render(Context(locals())).strip() - self.assertEqual(rendered, result_list[index], + self.assertEqual(rendered, escape(result_list[index]), msg="%s test failed, produced %s, should've produced %s" % (method, rendered, result_list[index])) def test_ordinal(self): diff --git a/tests/regressiontests/templates/filters.py b/tests/regressiontests/templates/filters.py new file mode 100644 index 0000000000..5d7129480c --- /dev/null +++ b/tests/regressiontests/templates/filters.py @@ -0,0 +1,220 @@ +# coding: utf-8 +""" +Tests for template filters (as opposed to template tags). + +The tests are hidden inside a function so that things like timestamps and +timezones are only evaluated at the moment of execution and will therefore be +consistent. +""" + +from datetime import datetime, timedelta + +from django.utils.tzinfo import LocalTimezone +from django.utils.safestring import mark_safe + +# RESULT SYNTAX -- +# 'template_name': ('template contents', 'context dict', +# 'expected string output' or Exception class) +def get_filter_tests(): + now = datetime.now() + now_tz = datetime.now(LocalTimezone(now)) + return { + # Default compare with datetime.now() + 'filter-timesince01' : ('{{ a|timesince }}', {'a': datetime.now() + timedelta(minutes=-1, seconds = -10)}, '1 minute'), + 'filter-timesince02' : ('{{ a|timesince }}', {'a': datetime.now() - timedelta(days=1, minutes = 1)}, '1 day'), + 'filter-timesince03' : ('{{ a|timesince }}', {'a': datetime.now() - timedelta(hours=1, minutes=25, seconds = 10)}, '1 hour, 25 minutes'), + + # Compare to a given parameter + 'filter-timesince04' : ('{{ a|timesince:b }}', {'a':now + timedelta(days=2), 'b':now + timedelta(days=1)}, '1 day'), + 'filter-timesince05' : ('{{ a|timesince:b }}', {'a':now + timedelta(days=2, minutes=1), 'b':now + timedelta(days=2)}, '1 minute'), + + # Check that timezone is respected + 'filter-timesince06' : ('{{ a|timesince:b }}', {'a':now_tz + timedelta(hours=8), 'b':now_tz}, '8 hours'), + + # Default compare with datetime.now() + 'filter-timeuntil01' : ('{{ a|timeuntil }}', {'a':datetime.now() + timedelta(minutes=2, seconds = 10)}, '2 minutes'), + 'filter-timeuntil02' : ('{{ a|timeuntil }}', {'a':(datetime.now() + timedelta(days=1, seconds = 10))}, '1 day'), + 'filter-timeuntil03' : ('{{ a|timeuntil }}', {'a':(datetime.now() + timedelta(hours=8, minutes=10, seconds = 10))}, '8 hours, 10 minutes'), + + # Compare to a given parameter + 'filter-timeuntil04' : ('{{ a|timeuntil:b }}', {'a':now - timedelta(days=1), 'b':now - timedelta(days=2)}, '1 day'), + 'filter-timeuntil05' : ('{{ a|timeuntil:b }}', {'a':now - timedelta(days=2), 'b':now - timedelta(days=2, minutes=1)}, '1 minute'), + + 'filter-addslash01': ("{% autoescape off %}{{ a|addslashes }} {{ b|addslashes }}{% endautoescape %}", {"a": "
        '", "b": mark_safe("'")}, ur"\' \'"), + 'filter-addslash02': ("{{ a|addslashes }} {{ b|addslashes }}", {"a": "'", "b": mark_safe("'")}, ur"<a>\' \'"), + + 'filter-capfirst01': ("{% autoescape off %}{{ a|capfirst }} {{ b|capfirst }}{% endautoescape %}", {"a": "fred>", "b": mark_safe("fred>")}, u"Fred> Fred>"), + 'filter-capfirst02': ("{{ a|capfirst }} {{ b|capfirst }}", {"a": "fred>", "b": mark_safe("fred>")}, u"Fred> Fred>"), + + # Note that applying fix_ampsersands in autoescape mode leads to + # double escaping. + 'filter-fix_ampersands01': ("{% autoescape off %}{{ a|fix_ampersands }} {{ b|fix_ampersands }}{% endautoescape %}", {"a": "a&b", "b": mark_safe("a&b")}, u"a&b a&b"), + 'filter-fix_ampersands02': ("{{ a|fix_ampersands }} {{ b|fix_ampersands }}", {"a": "a&b", "b": mark_safe("a&b")}, u"a&amp;b a&b"), + + 'filter-floatformat01': ("{% autoescape off %}{{ a|floatformat }} {{ b|floatformat }}{% endautoescape %}", {"a": "1.42", "b": mark_safe("1.42")}, u"1.4 1.4"), + 'filter-floatformat02': ("{{ a|floatformat }} {{ b|floatformat }}", {"a": "1.42", "b": mark_safe("1.42")}, u"1.4 1.4"), + + # The contents of "linenumbers" is escaped according to the current + # autoescape setting. + 'filter-linenumbers01': ("{{ a|linenumbers }} {{ b|linenumbers }}", {"a": "one\n\nthree", "b": mark_safe("one\n<two>\nthree")}, u"1. one\n2. <two>\n3. three 1. one\n2. <two>\n3. three"), + 'filter-linenumbers02': ("{% autoescape off %}{{ a|linenumbers }} {{ b|linenumbers }}{% endautoescape %}", {"a": "one\n\nthree", "b": mark_safe("one\n<two>\nthree")}, u"1. one\n2. \n3. three 1. one\n2. <two>\n3. three"), + + 'filter-lower01': ("{% autoescape off %}{{ a|lower }} {{ b|lower }}{% endautoescape %}", {"a": "Apple & banana", "b": mark_safe("Apple & banana")}, u"apple & banana apple & banana"), + 'filter-lower02': ("{{ a|lower }} {{ b|lower }}", {"a": "Apple & banana", "b": mark_safe("Apple & banana")}, u"apple & banana apple & banana"), + + # The make_list filter can destroy existing escaping, so the results are + # escaped. + 'filter-make_list01': ("{% autoescape off %}{{ a|make_list }}{% endautoescape %}", {"a": mark_safe("&")}, u"[u'&']"), + 'filter-make_list02': ("{{ a|make_list }}", {"a": mark_safe("&")}, u"[u'&']"), + 'filter-make_list03': ('{% autoescape off %}{{ a|make_list|stringformat:"s"|safe }}{% endautoescape %}', {"a": mark_safe("&")}, u"[u'&']"), + 'filter-make_list04': ('{{ a|make_list|stringformat:"s"|safe }}', {"a": mark_safe("&")}, u"[u'&']"), + + # Running slugify on a pre-escaped string leads to odd behaviour, + # but the result is still safe. + 'filter-slugify01': ("{% autoescape off %}{{ a|slugify }} {{ b|slugify }}{% endautoescape %}", {"a": "a & b", "b": mark_safe("a & b")}, u"a-b a-amp-b"), + 'filter-slugify02': ("{{ a|slugify }} {{ b|slugify }}", {"a": "a & b", "b": mark_safe("a & b")}, u"a-b a-amp-b"), + + # Notice that escaping is applied *after* any filters, so the string + # formatting here only needs to deal with pre-escaped characters. + 'filter-stringformat01': ('{% autoescape off %}.{{ a|stringformat:"5s" }}. .{{ b|stringformat:"5s" }}.{% endautoescape %}', {"a": "ahttp://example.com/x=&y= http://example.com?x=&y='), + 'filter-urlize02': ('{{ a|urlize }} {{ b|urlize }}', {"a": "http://example.com/x=&y=", "b": mark_safe("http://example.com?x=&y=")}, u'http://example.com/x=&y= http://example.com?x=&y='), + 'filter-urlize03': ('{% autoescape off %}{{ a|urlize }}{% endautoescape %}', {"a": mark_safe("a & b")}, 'a & b'), + 'filter-urlize04': ('{{ a|urlize }}', {"a": mark_safe("a & b")}, 'a & b'), + + 'filter-urlizetrunc01': ('{% autoescape off %}{{ a|urlizetrunc:"8" }} {{ b|urlizetrunc:"8" }}{% endautoescape %}', {"a": "http://example.com/x=&y=", "b": mark_safe("http://example.com?x=&y=")}, u'http:... http:...'), + 'filter-urlizetrunc02': ('{{ a|urlizetrunc:"8" }} {{ b|urlizetrunc:"8" }}', {"a": "http://example.com/x=&y=", "b": mark_safe("http://example.com?x=&y=")}, u'http:... http:...'), + + 'filter-wordcount01': ('{% autoescape off %}{{ a|wordcount }} {{ b|wordcount }}{% endautoescape %}', {"a": "a & b", "b": mark_safe("a & b")}, "3 3"), + 'filter-wordcount02': ('{{ a|wordcount }} {{ b|wordcount }}', {"a": "a & b", "b": mark_safe("a & b")}, "3 3"), + + 'filter-wordwrap01': ('{% autoescape off %}{{ a|wordwrap:"3" }} {{ b|wordwrap:"3" }}{% endautoescape %}', {"a": "a & b", "b": mark_safe("a & b")}, u"a &\nb a &\nb"), + 'filter-wordwrap02': ('{{ a|wordwrap:"3" }} {{ b|wordwrap:"3" }}', {"a": "a & b", "b": mark_safe("a & b")}, u"a &\nb a &\nb"), + + 'filter-ljust01': ('{% autoescape off %}.{{ a|ljust:"5" }}. .{{ b|ljust:"5" }}.{% endautoescape %}', {"a": "a&b", "b": mark_safe("a&b")}, u".a&b . .a&b ."), + 'filter-ljust02': ('.{{ a|ljust:"5" }}. .{{ b|ljust:"5" }}.', {"a": "a&b", "b": mark_safe("a&b")}, u".a&b . .a&b ."), + + 'filter-rjust01': ('{% autoescape off %}.{{ a|rjust:"5" }}. .{{ b|rjust:"5" }}.{% endautoescape %}', {"a": "a&b", "b": mark_safe("a&b")}, u". a&b. . a&b."), + 'filter-rjust02': ('.{{ a|rjust:"5" }}. .{{ b|rjust:"5" }}.', {"a": "a&b", "b": mark_safe("a&b")}, u". a&b. . a&b."), + + 'filter-center01': ('{% autoescape off %}.{{ a|center:"5" }}. .{{ b|center:"5" }}.{% endautoescape %}', {"a": "a&b", "b": mark_safe("a&b")}, u". a&b . . a&b ."), + 'filter-center02': ('.{{ a|center:"5" }}. .{{ b|center:"5" }}.', {"a": "a&b", "b": mark_safe("a&b")}, u". a&b . . a&b ."), + + 'filter-cut01': ('{% autoescape off %}{{ a|cut:"x" }} {{ b|cut:"x" }}{% endautoescape %}', {"a": "x&y", "b": mark_safe("x&y")}, u"&y &y"), + 'filter-cut02': ('{{ a|cut:"x" }} {{ b|cut:"x" }}', {"a": "x&y", "b": mark_safe("x&y")}, u"&y &y"), + 'filter-cut03': ('{% autoescape off %}{{ a|cut:"&" }} {{ b|cut:"&" }}{% endautoescape %}', {"a": "x&y", "b": mark_safe("x&y")}, u"xy xamp;y"), + 'filter-cut04': ('{{ a|cut:"&" }} {{ b|cut:"&" }}', {"a": "x&y", "b": mark_safe("x&y")}, u"xy xamp;y"), + # Passing ';' to cut can break existing HTML entities, so those strings + # are auto-escaped. + 'filter-cut05': ('{% autoescape off %}{{ a|cut:";" }} {{ b|cut:";" }}{% endautoescape %}', {"a": "x&y", "b": mark_safe("x&y")}, u"x&y x&y"), + 'filter-cut06': ('{{ a|cut:";" }} {{ b|cut:";" }}', {"a": "x&y", "b": mark_safe("x&y")}, u"x&y x&ampy"), + + # The "escape" filter works the same whether autoescape is on or off, + # but it has no effect on strings already marked as safe. + 'filter-escape01': ('{{ a|escape }} {{ b|escape }}', {"a": "x&y", "b": mark_safe("x&y")}, u"x&y x&y"), + 'filter-escape02': ('{% autoescape off %}{{ a|escape }} {{ b|escape }}{% endautoescape %}', {"a": "x&y", "b": mark_safe("x&y")}, "x&y x&y"), + + # It is only applied once, regardless of the number of times it + # appears in a chain. + 'filter-escape03': ('{% autoescape off %}{{ a|escape|escape }}{% endautoescape %}', {"a": "x&y"}, u"x&y"), + 'filter-escape04': ('{{ a|escape|escape }}', {"a": "x&y"}, u"x&y"), + + # Force_escape is applied immediately. It can be used to provide + # double-escaping, for example. + 'filter-force-escape01': ('{% autoescape off %}{{ a|force_escape }}{% endautoescape %}', {"a": "x&y"}, u"x&y"), + 'filter-force-escape02': ('{{ a|force_escape }}', {"a": "x&y"}, u"x&y"), + 'filter-force-escape03': ('{% autoescape off %}{{ a|force_escape|force_escape }}{% endautoescape %}', {"a": "x&y"}, u"x&amp;y"), + 'filter-force-escape04': ('{{ a|force_escape|force_escape }}', {"a": "x&y"}, u"x&amp;y"), + + # Because the result of force_escape is "safe", an additional + # escape filter has no effect. + 'filter-force-escape05': ('{% autoescape off %}{{ a|force_escape|escape }}{% endautoescape %}', {"a": "x&y"}, u"x&y"), + 'filter-force-escape06': ('{{ a|force_escape|escape }}', {"a": "x&y"}, u"x&y"), + 'filter-force-escape07': ('{% autoescape off %}{{ a|escape|force_escape }}{% endautoescape %}', {"a": "x&y"}, u"x&y"), + 'filter-force-escape07': ('{{ a|escape|force_escape }}', {"a": "x&y"}, u"x&y"), + + # The contents in "linebreaks" and "linebreaksbr" are escaped + # according to the current autoescape setting. + 'filter-linebreaks01': ('{{ a|linebreaks }} {{ b|linebreaks }}', {"a": "x&\ny", "b": mark_safe("x&\ny")}, u"

        x&
        y

        x&
        y

        "), + 'filter-linebreaks02': ('{% autoescape off %}{{ a|linebreaks }} {{ b|linebreaks }}{% endautoescape %}', {"a": "x&\ny", "b": mark_safe("x&\ny")}, u"

        x&
        y

        x&
        y

        "), + + 'filter-linebreaksbr01': ('{{ a|linebreaksbr }} {{ b|linebreaksbr }}', {"a": "x&\ny", "b": mark_safe("x&\ny")}, u"x&
        y x&
        y"), + 'filter-linebreaksbr02': ('{% autoescape off %}{{ a|linebreaksbr }} {{ b|linebreaksbr }}{% endautoescape %}', {"a": "x&\ny", "b": mark_safe("x&\ny")}, u"x&
        y x&
        y"), + + 'filter-safe01': ("{{ a }} -- {{ a|safe }}", {"a": u"hello"}, "<b>hello</b> -- hello"), + 'filter-safe02': ("{% autoescape off %}{{ a }} -- {{ a|safe }}{% endautoescape %}", {"a": "hello"}, u"hello -- hello"), + + 'filter-removetags01': ('{{ a|removetags:"a b" }} {{ b|removetags:"a b" }}', {"a": "x

        y

        ", "b": mark_safe("x

        y

        ")}, u"x <p>y</p> x

        y

        "), + 'filter-removetags02': ('{% autoescape off %}{{ a|removetags:"a b" }} {{ b|removetags:"a b" }}{% endautoescape %}', {"a": "x

        y

        ", "b": mark_safe("x

        y

        ")}, u"x

        y

        x

        y

        "), + + 'filter-striptags01': ('{{ a|striptags }} {{ b|striptags }}', {"a": "x

        y

        ", "b": mark_safe("x

        y

        ")}, "x y x y"), + 'filter-striptags02': ('{% autoescape off %}{{ a|striptags }} {{ b|striptags }}{% endautoescape %}', {"a": "x

        y

        ", "b": mark_safe("x

        y

        ")}, "x y x y"), + + 'filter-first01': ('{{ a|first }} {{ b|first }}', {"a": ["a&b", "x"], "b": [mark_safe("a&b"), "x"]}, "a&b a&b"), + 'filter-first02': ('{% autoescape off %}{{ a|first }} {{ b|first }}{% endautoescape %}', {"a": ["a&b", "x"], "b": [mark_safe("a&b"), "x"]}, "a&b a&b"), + + 'filter-random01': ('{{ a|random }} {{ b|random }}', {"a": ["a&b", "a&b"], "b": [mark_safe("a&b"), mark_safe("a&b")]}, "a&b a&b"), + 'filter-random02': ('{% autoescape off %}{{ a|random }} {{ b|random }}{% endautoescape %}', {"a": ["a&b", "a&b"], "b": [mark_safe("a&b"), mark_safe("a&b")]}, "a&b a&b"), + + 'filter-slice01': ('{{ a|slice:"1:3" }} {{ b|slice:"1:3" }}', {"a": "a&b", "b": mark_safe("a&b")}, "&b &b"), + 'filter-slice02': ('{% autoescape off %}{{ a|slice:"1:3" }} {{ b|slice:"1:3" }}{% endautoescape %}', {"a": "a&b", "b": mark_safe("a&b")}, "&b &b"), + + 'filter-unordered_list01': ('{{ a|unordered_list }}', {"a": ["x>", [["x>\n\t
          \n\t\t
        • <y
        • \n\t
        \n\t"), + 'filter-unordered_list02': ('{% autoescape off %}{{ a|unordered_list }}{% endautoescape %}', {"a": ["x>", [["x>\n\t
          \n\t\t
        • \n\t
        \n\t"), + 'filter-unordered_list03': ('{{ a|unordered_list }}', {"a": ["x>", [[mark_safe("x>\n\t
          \n\t\t
        • \n\t
        \n\t"), + 'filter-unordered_list04': ('{% autoescape off %}{{ a|unordered_list }}{% endautoescape %}', {"a": ["x>", [[mark_safe("x>\n\t
          \n\t\t
        • \n\t
        \n\t"), + 'filter-unordered_list05': ('{% autoescape off %}{{ a|unordered_list }}{% endautoescape %}', {"a": ["x>", [["x>\n\t
          \n\t\t
        • \n\t
        \n\t"), + + # If the input to "default" filter is marked as safe, then so is the + # output. However, if the default arg is used, auto-escaping kicks in + # (if enabled), because we cannot mark the default as safe. + # + # Note: we have to use {"a": ""} here, otherwise the invalid template + # variable string interferes with the test result. + 'filter-default01': ('{{ a|default:"x<" }}', {"a": ""}, "x<"), + 'filter-default02': ('{% autoescape off %}{{ a|default:"x<" }}{% endautoescape %}', {"a": ""}, "x<"), + 'filter-default03': ('{{ a|default:"x<" }}', {"a": mark_safe("x>")}, "x>"), + 'filter-default04': ('{% autoescape off %}{{ a|default:"x<" }}{% endautoescape %}', {"a": mark_safe("x>")}, "x>"), + + 'filter-default_if_none01': ('{{ a|default:"x<" }}', {"a": None}, "x<"), + 'filter-default_if_none02': ('{% autoescape off %}{{ a|default:"x<" }}{% endautoescape %}', {"a": None}, "x<"), + + 'filter-phone2numeric01': ('{{ a|phone2numeric }} {{ b|phone2numeric }}', {"a": "<1-800-call-me>", "b": mark_safe("<1-800-call-me>") }, "<1-800-2255-63> <1-800-2255-63>"), + 'filter-phone2numeric02': ('{% autoescape off %}{{ a|phone2numeric }} {{ b|phone2numeric }}{% endautoescape %}', {"a": "<1-800-call-me>", "b": mark_safe("<1-800-call-me>") }, "<1-800-2255-63> <1-800-2255-63>"), + + # Chaining a bunch of safeness-preserving filters should not alter + # the safe status either way. + 'chaining01': ('{{ a|capfirst|center:"7" }}.{{ b|capfirst|center:"7" }}', {"a": "a < b", "b": mark_safe("a < b")}, " A < b . A < b "), + 'chaining02': ('{% autoescape off %}{{ a|capfirst|center:"7" }}.{{ b|capfirst|center:"7" }}{% endautoescape %}', {"a": "a < b", "b": mark_safe("a < b")}, " A < b . A < b "), + + # Using a filter that forces a string back to unsafe: + 'chaining03': ('{{ a|cut:"b"|capfirst }}.{{ b|cut:"b"|capfirst }}', {"a": "a < b", "b": mark_safe("a < b")}, "A < .A < "), + 'chaining04': ('{% autoescape off %}{{ a|cut:"b"|capfirst }}.{{ b|cut:"b"|capfirst }}{% endautoescape %}', {"a": "a < b", "b": mark_safe("a < b")}, "A < .A < "), + + # Using a filter that forces safeness does not lead to double-escaping + 'chaining05': ('{{ a|escape|capfirst }}', {"a": "a < b"}, "A < b"), + 'chaining06': ('{% autoescape off %}{{ a|escape|capfirst }}{% endautoescape %}', {"a": "a < b"}, "A < b"), + + # Force to safe, then back (also showing why using force_escape too + # early in a chain can lead to unexpected results). + 'chaining07': ('{{ a|force_escape|cut:"b" }}', {"a": "a < b"}, "a < "), + 'chaining08': ('{% autoescape off %}{{ a|force_escape|cut:"b" }}{% endautoescape %}', {"a": "a < b"}, "a < "), + 'chaining09': ('{{ a|cut:"b"|force_escape }}', {"a": "a < b"}, "a < "), + 'chaining10': ('{% autoescape off %}{{ a|cut:"b"|force_escape }}{% endautoescape %}', {"a": "a < b"}, "a < "), + 'chaining11': ('{{ a|cut:"b"|safe }}', {"a": "a < b"}, "a < "), + 'chaining12': ('{% autoescape off %}{{ a|cut:"b"|safe }}{% endautoescape %}', {"a": "a < b"}, "a < "), + 'chaining13': ('{{ a|safe|force_escape }}', {"a": "a < b"}, "a < b"), + 'chaining14': ('{% autoescape off %}{{ a|safe|force_escape }}{% endautoescape %}', {"a": "a < b"}, "a < b"), + } diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py index 60f7d54145..d52e8f0abf 100644 --- a/tests/regressiontests/templates/tests.py +++ b/tests/regressiontests/templates/tests.py @@ -14,9 +14,11 @@ from django import template from django.template import loader from django.template.loaders import app_directories, filesystem from django.utils.translation import activate, deactivate, ugettext as _ +from django.utils.safestring import mark_safe from django.utils.tzinfo import LocalTimezone from unicode import unicode_tests +import filters # Some other tests we would like to run __test__ = { @@ -120,20 +122,97 @@ class Templates(unittest.TestCase): ['/dir1/index.html']) def test_templates(self): - # NOW and NOW_tz are used by timesince tag tests. - NOW = datetime.now() - NOW_tz = datetime.now(LocalTimezone(datetime.now())) + template_tests = self.get_template_tests() + filter_tests = filters.get_filter_tests() + # Quickly check that we aren't accidentally using a name in both + # template and filter tests. + overlapping_names = [name for name in filter_tests if name in + template_tests] + assert not overlapping_names, 'Duplicate test name(s): %s' % ', '.join(overlapping_names) + + template_tests.update(filter_tests) + + # Register our custom template loader. + def test_template_loader(template_name, template_dirs=None): + "A custom template loader that loads the unit-test templates." + try: + return (template_tests[template_name][0] , "test:%s" % template_name) + except KeyError: + raise template.TemplateDoesNotExist, template_name + + old_template_loaders = loader.template_source_loaders + loader.template_source_loaders = [test_template_loader] + + failures = [] + tests = template_tests.items() + tests.sort() + + # Turn TEMPLATE_DEBUG off, because tests assume that. + old_td, settings.TEMPLATE_DEBUG = settings.TEMPLATE_DEBUG, False + + # Set TEMPLATE_STRING_IF_INVALID to a known string + old_invalid = settings.TEMPLATE_STRING_IF_INVALID + expected_invalid_str = 'INVALID' + + for name, vals in tests: + if isinstance(vals[2], tuple): + normal_string_result = vals[2][0] + invalid_string_result = vals[2][1] + if '%s' in invalid_string_result: + expected_invalid_str = 'INVALID %s' + invalid_string_result = invalid_string_result % vals[2][2] + template.invalid_var_format_string = True + else: + normal_string_result = vals[2] + invalid_string_result = vals[2] + + if 'LANGUAGE_CODE' in vals[1]: + activate(vals[1]['LANGUAGE_CODE']) + else: + activate('en-us') + + for invalid_str, result in [('', normal_string_result), + (expected_invalid_str, invalid_string_result)]: + settings.TEMPLATE_STRING_IF_INVALID = invalid_str + try: + test_template = loader.get_template(name) + output = self.render(test_template, vals) + except Exception, e: + if e.__class__ != result: + failures.append("Template test (TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Got %s, exception: %s" % (invalid_str, name, e.__class__, e)) + continue + if output != result: + failures.append("Template test (TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Expected %r, got %r" % (invalid_str, name, result, output)) + + if 'LANGUAGE_CODE' in vals[1]: + deactivate() + + if template.invalid_var_format_string: + expected_invalid_str = 'INVALID' + template.invalid_var_format_string = False + + loader.template_source_loaders = old_template_loaders + deactivate() + settings.TEMPLATE_DEBUG = old_td + settings.TEMPLATE_STRING_IF_INVALID = old_invalid + + self.assertEqual(failures, [], '\n'.join(failures)) + + def render(self, test_template, vals): + return test_template.render(template.Context(vals[1])) + + def get_template_tests(self): # SYNTAX -- # 'template_name': ('template contents', 'context dict', 'expected string output' or Exception class) - TEMPLATE_TESTS = { - - ### BASIC SYNTAX ########################################################## + return { + ### BASIC SYNTAX ################################################ # Plain text should go through the template parser untouched 'basic-syntax01': ("something cool", {}, "something cool"), - # Variables should be replaced with their value in the current context + # Variables should be replaced with their value in the current + # context 'basic-syntax02': ("{{ headline }}", {'headline':'Success'}, "Success"), # More than one replacement variable is allowed in a template @@ -240,7 +319,8 @@ class Templates(unittest.TestCase): 'filter-syntax09': ('{{ var|removetags:"b i"|upper|lower }}', {"var": "Yes"}, "yes"), # Escaped string as argument - 'filter-syntax10': (r'{{ var|default_if_none:" endquote\" hah" }}', {"var": None}, ' endquote" hah'), + 'filter-syntax10': (r'{{ var|default_if_none:" endquote\" hah" }}', + {"var": None}, ' endquote" hah'), # Variable as argument 'filter-syntax11': (r'{{ var|default_if_none:var2 }}', {"var": None, "var2": "happy"}, 'happy'), @@ -760,38 +840,6 @@ class Templates(unittest.TestCase): # 'now03' : ('{% now "j \"n\" Y"%}', {}, str(datetime.now().day) + '"' + str(datetime.now().month) + '"' + str(datetime.now().year)), # 'now04' : ('{% now "j \nn\n Y"%}', {}, str(datetime.now().day) + '\n' + str(datetime.now().month) + '\n' + str(datetime.now().year)) - ### TIMESINCE TAG ################################################## - # Default compare with datetime.now() - 'timesince01' : ('{{ a|timesince }}', {'a':datetime.now() + timedelta(minutes=-1, seconds = -10)}, '1 minute'), - 'timesince02' : ('{{ a|timesince }}', {'a':(datetime.now() - timedelta(days=1, minutes = 1))}, '1 day'), - 'timesince03' : ('{{ a|timesince }}', {'a':(datetime.now() - - timedelta(hours=1, minutes=25, seconds = 10))}, '1 hour, 25 minutes'), - - # Compare to a given parameter - 'timesince04' : ('{{ a|timesince:b }}', {'a':NOW + timedelta(days=2), 'b':NOW + timedelta(days=1)}, '1 day'), - 'timesince05' : ('{{ a|timesince:b }}', {'a':NOW + timedelta(days=2, minutes=1), 'b':NOW + timedelta(days=2)}, '1 minute'), - - # Check that timezone is respected - 'timesince06' : ('{{ a|timesince:b }}', {'a':NOW_tz + timedelta(hours=8), 'b':NOW_tz}, '8 hours'), - - # Check times in the future. - 'timesince07' : ('{{ a|timesince }}', {'a':datetime.now() + timedelta(minutes=1, seconds=10)}, '0 minutes'), - 'timesince08' : ('{{ a|timesince }}', {'a':datetime.now() + timedelta(days=1, minutes=1)}, '0 minutes'), - - ### TIMEUNTIL TAG ################################################## - # Default compare with datetime.now() - 'timeuntil01' : ('{{ a|timeuntil }}', {'a':datetime.now() + timedelta(minutes=2, seconds = 10)}, '2 minutes'), - 'timeuntil02' : ('{{ a|timeuntil }}', {'a':(datetime.now() + timedelta(days=1, seconds = 10))}, '1 day'), - 'timeuntil03' : ('{{ a|timeuntil }}', {'a':(datetime.now() + timedelta(hours=8, minutes=10, seconds = 10))}, '8 hours, 10 minutes'), - - # Compare to a given parameter - 'timeuntil04' : ('{{ a|timeuntil:b }}', {'a':NOW - timedelta(days=1), 'b':NOW - timedelta(days=2)}, '1 day'), - 'timeuntil05' : ('{{ a|timeuntil:b }}', {'a':NOW - timedelta(days=2), 'b':NOW - timedelta(days=2, minutes=1)}, '1 minute'), - - # Check times in the past. - 'timeuntil07' : ('{{ a|timeuntil }}', {'a':datetime.now() - timedelta(minutes=1, seconds=10)}, '0 minutes'), - 'timeuntil08' : ('{{ a|timeuntil }}', {'a':datetime.now() - timedelta(days=1, minutes=1)}, '0 minutes'), - ### URL TAG ######################################################## # Successes 'url01' : ('{% url regressiontests.templates.views.client client.id %}', {'client': {'id': 1}}, '/url_tag/client/1/'), @@ -819,72 +867,31 @@ class Templates(unittest.TestCase): 'cache08' : ('{% load cache %}{% cache %}{% endcache %}', {}, template.TemplateSyntaxError), 'cache09' : ('{% load cache %}{% cache 1 %}{% endcache %}', {}, template.TemplateSyntaxError), 'cache10' : ('{% load cache %}{% cache foo bar %}{% endcache %}', {}, template.TemplateSyntaxError), + + ### AUTOESCAPE TAG ############################################## + 'autoescape-tag01': ("{% autoescape off %}hello{% endautoescape %}", {}, "hello"), + 'autoescape-tag02': ("{% autoescape off %}{{ first }}{% endautoescape %}", {"first": "hello"}, "hello"), + 'autoescape-tag03': ("{% autoescape on %}{{ first }}{% endautoescape %}", {"first": "hello"}, "<b>hello</b>"), + + # Autoescape disabling and enabling nest in a predictable way. + 'autoescape-tag04': ("{% autoescape off %}{{ first }} {% autoescape on%}{{ first }}{% endautoescape %}{% endautoescape %}", {"first": ""}, " <a>"), + + 'autoescape-tag05': ("{% autoescape on %}{{ first }}{% endautoescape %}", {"first": "first"}, "<b>first</b>"), + + # Strings (ASCII or unicode) already marked as "safe" are not + # auto-escaped + 'autoescape-tag06': ("{{ first }}", {"first": mark_safe("first")}, "first"), + 'autoescape-tag07': ("{% autoescape on %}{{ first }}{% endautoescape %}", {"first": mark_safe(u"Apple")}, u"Apple"), + + # String arguments to filters, if used in the result, are escaped, + # too. + 'basic-syntax08': (r'{% autoescape on %}{{ var|default_if_none:" endquote\" hah" }}{% endautoescape %}', {"var": None}, ' endquote" hah'), + + # The "safe" and "escape" filters cannot work due to internal + # implementation details (fortunately, the (no)autoescape block + # tags can be used in those cases) + 'autoescape-filtertag01': ("{{ first }}{% filter safe %}{{ first }} x"}, template.TemplateSyntaxError), } - # Register our custom template loader. - def test_template_loader(template_name, template_dirs=None): - "A custom template loader that loads the unit-test templates." - try: - return (TEMPLATE_TESTS[template_name][0] , "test:%s" % template_name) - except KeyError: - raise template.TemplateDoesNotExist, template_name - - old_template_loaders = loader.template_source_loaders - loader.template_source_loaders = [test_template_loader] - - failures = [] - tests = TEMPLATE_TESTS.items() - tests.sort() - - # Turn TEMPLATE_DEBUG off, because tests assume that. - old_td, settings.TEMPLATE_DEBUG = settings.TEMPLATE_DEBUG, False - - # Set TEMPLATE_STRING_IF_INVALID to a known string - old_invalid = settings.TEMPLATE_STRING_IF_INVALID - expected_invalid_str = 'INVALID' - - for name, vals in tests: - if isinstance(vals[2], tuple): - normal_string_result = vals[2][0] - invalid_string_result = vals[2][1] - if '%s' in invalid_string_result: - expected_invalid_str = 'INVALID %s' - invalid_string_result = invalid_string_result % vals[2][2] - template.invalid_var_format_string = True - else: - normal_string_result = vals[2] - invalid_string_result = vals[2] - - if 'LANGUAGE_CODE' in vals[1]: - activate(vals[1]['LANGUAGE_CODE']) - else: - activate('en-us') - - for invalid_str, result in [('', normal_string_result), - (expected_invalid_str, invalid_string_result)]: - settings.TEMPLATE_STRING_IF_INVALID = invalid_str - try: - output = loader.get_template(name).render(template.Context(vals[1])) - except Exception, e: - if e.__class__ != result: - failures.append("Template test (TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Got %s, exception: %s" % (invalid_str, name, e.__class__, e)) - continue - if output != result: - failures.append("Template test (TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Expected %r, got %r" % (invalid_str, name, result, output)) - - if 'LANGUAGE_CODE' in vals[1]: - deactivate() - - if template.invalid_var_format_string: - expected_invalid_str = 'INVALID' - template.invalid_var_format_string = False - - loader.template_source_loaders = old_template_loaders - deactivate() - settings.TEMPLATE_DEBUG = old_td - settings.TEMPLATE_STRING_IF_INVALID = old_invalid - - self.assertEqual(failures, [], '\n'.join(failures)) - if __name__ == "__main__": unittest.main() diff --git a/tests/regressiontests/templates/urls.py b/tests/regressiontests/templates/urls.py index d79f38e0a7..3b28e70d0b 100644 --- a/tests/regressiontests/templates/urls.py +++ b/tests/regressiontests/templates/urls.py @@ -7,7 +7,7 @@ urlpatterns = patterns('', # Test urls for testing reverse lookups (r'^$', views.index), (r'^client/(\d+)/$', views.client), - (r'^client/(\d+)/(?P[^/]+)/$', views.client_action), + (r'^client/(?P\d+)/(?P[^/]+)/$', views.client_action), url(r'^named-client/(\d+)/$', views.client, name="named.client"), # Unicode strings are permitted everywhere. diff --git a/tests/regressiontests/test_client_regress/models.py b/tests/regressiontests/test_client_regress/models.py index 60fd909f43..b5d9ae63b9 100644 --- a/tests/regressiontests/test_client_regress/models.py +++ b/tests/regressiontests/test_client_regress/models.py @@ -31,12 +31,12 @@ class AssertContainsTests(TestCase): self.assertContains(response, 'once', 2) except AssertionError, e: self.assertEquals(str(e), "Found 1 instances of 'once' in response (expected 2)") - + try: self.assertContains(response, 'twice', 1) except AssertionError, e: self.assertEquals(str(e), "Found 2 instances of 'twice' in response (expected 1)") - + try: self.assertContains(response, 'thrice') except AssertionError, e: @@ -46,37 +46,37 @@ class AssertContainsTests(TestCase): self.assertContains(response, 'thrice', 3) except AssertionError, e: self.assertEquals(str(e), "Found 0 instances of 'thrice' in response (expected 3)") - + class AssertTemplateUsedTests(TestCase): fixtures = ['testdata.json'] - + def test_no_context(self): "Template usage assertions work then templates aren't in use" response = self.client.get('/test_client_regress/no_template_view/') # Check that the no template case doesn't mess with the template assertions self.assertTemplateNotUsed(response, 'GET Template') - + try: self.assertTemplateUsed(response, 'GET Template') except AssertionError, e: self.assertEquals(str(e), "No templates used to render the response") - def test_single_context(self): + def test_single_context(self): "Template assertions work when there is a single context" response = self.client.get('/test_client/post_view/', {}) - # + # try: self.assertTemplateNotUsed(response, 'Empty GET Template') except AssertionError, e: self.assertEquals(str(e), "Template 'Empty GET Template' was used unexpectedly in rendering the response") - + try: - self.assertTemplateUsed(response, 'Empty POST Template') + self.assertTemplateUsed(response, 'Empty POST Template') except AssertionError, e: self.assertEquals(str(e), "Template 'Empty POST Template' was not a template used to render the response. Actual template(s) used: Empty GET Template") - + def test_multiple_context(self): "Template assertions work when there are multiple contexts" post_data = { @@ -99,37 +99,37 @@ class AssertTemplateUsedTests(TestCase): self.assertEquals(str(e), "Template 'base.html' was used unexpectedly in rendering the response") try: - self.assertTemplateUsed(response, "Valid POST Template") + self.assertTemplateUsed(response, "Valid POST Template") except AssertionError, e: self.assertEquals(str(e), "Template 'Valid POST Template' was not a template used to render the response. Actual template(s) used: form_view.html, base.html") class AssertRedirectsTests(TestCase): def test_redirect_page(self): - "An assertion is raised if the original page couldn't be retrieved as expected" + "An assertion is raised if the original page couldn't be retrieved as expected" # This page will redirect with code 301, not 302 - response = self.client.get('/test_client/permanent_redirect_view/') + response = self.client.get('/test_client/permanent_redirect_view/') try: self.assertRedirects(response, '/test_client/get_view/') except AssertionError, e: self.assertEquals(str(e), "Response didn't redirect as expected: Response code was 301 (expected 302)") - + def test_lost_query(self): "An assertion is raised if the redirect location doesn't preserve GET parameters" response = self.client.get('/test_client/redirect_view/', {'var': 'value'}) try: self.assertRedirects(response, '/test_client/get_view/') except AssertionError, e: - self.assertEquals(str(e), "Response redirected to 'http://testserver/test_client/get_view/?var=value', expected '/test_client/get_view/'") + self.assertEquals(str(e), "Response redirected to 'http://testserver/test_client/get_view/?var=value', expected 'http://testserver/test_client/get_view/'") def test_incorrect_target(self): "An assertion is raised if the response redirects to another target" - response = self.client.get('/test_client/permanent_redirect_view/') + response = self.client.get('/test_client/permanent_redirect_view/') try: # Should redirect to get_view self.assertRedirects(response, '/test_client/some_view/') except AssertionError, e: self.assertEquals(str(e), "Response didn't redirect as expected: Response code was 301 (expected 302)") - + def test_target_page(self): "An assertion is raised if the response redirect target cannot be retrieved as expected" response = self.client.get('/test_client/double_redirect_view/') @@ -138,7 +138,7 @@ class AssertRedirectsTests(TestCase): self.assertRedirects(response, 'http://testserver/test_client/permanent_redirect_view/') except AssertionError, e: self.assertEquals(str(e), "Couldn't retrieve redirection page '/test_client/permanent_redirect_view/': response code was 301 (expected 200)") - + class AssertFormErrorTests(TestCase): def test_unknown_form(self): "An assertion is raised if the form name is unknown" @@ -157,7 +157,7 @@ class AssertFormErrorTests(TestCase): self.assertFormError(response, 'wrong_form', 'some_field', 'Some error.') except AssertionError, e: self.assertEqual(str(e), "The form 'wrong_form' was not used to render the response") - + def test_unknown_field(self): "An assertion is raised if the field name is unknown" post_data = { @@ -175,7 +175,7 @@ class AssertFormErrorTests(TestCase): self.assertFormError(response, 'form', 'some_field', 'Some error.') except AssertionError, e: self.assertEqual(str(e), "The form 'form' in context 0 does not contain the field 'some_field'") - + def test_noerror_field(self): "An assertion is raised if the field doesn't have any errors" post_data = { @@ -193,7 +193,7 @@ class AssertFormErrorTests(TestCase): self.assertFormError(response, 'form', 'value', 'Some error.') except AssertionError, e: self.assertEqual(str(e), "The field 'value' on form 'form' in context 0 contains no errors") - + def test_unknown_error(self): "An assertion is raised if the field doesn't contain the provided error" post_data = { @@ -211,7 +211,7 @@ class AssertFormErrorTests(TestCase): self.assertFormError(response, 'form', 'email', 'Some error.') except AssertionError, e: self.assertEqual(str(e), "The field 'email' on form 'form' in context 0 does not contain the error 'Some error.' (actual errors: [u'Enter a valid e-mail address.'])") - + def test_unknown_nonfield_error(self): """ Checks that an assertion is raised if the form's non field errors @@ -231,7 +231,7 @@ class AssertFormErrorTests(TestCase): try: self.assertFormError(response, 'form', None, 'Some error.') except AssertionError, e: - self.assertEqual(str(e), "The form 'form' in context 0 does not contain the non-field error 'Some error.' (actual errors: )") + self.assertEqual(str(e), "The form 'form' in context 0 does not contain the non-field error 'Some error.' (actual errors: )") class FileUploadTests(TestCase): def test_simple_upload(self): @@ -256,8 +256,8 @@ class LoginTests(TestCase): # Get a redirection page with the second client. response = c.get("/test_client_regress/login_protected_redirect_view/") - - # At this points, the self.client isn't logged in. - # Check that assertRedirects uses the original client, not the + + # At this points, the self.client isn't logged in. + # Check that assertRedirects uses the original client, not the # default client. self.assertRedirects(response, "http://testserver/test_client_regress/get_view/") diff --git a/tests/regressiontests/text/tests.py b/tests/regressiontests/text/tests.py index 962a30ef19..7cfe44517a 100644 --- a/tests/regressiontests/text/tests.py +++ b/tests/regressiontests/text/tests.py @@ -27,6 +27,14 @@ u'Paris+%26+Orl%C3%A9ans' >>> urlquote_plus(u'Paris & Orl\xe9ans', safe="&") u'Paris+&+Orl%C3%A9ans' +### cookie_date, http_date ############################################### +>>> from django.utils.http import cookie_date, http_date +>>> t = 1167616461.0 +>>> cookie_date(t) +'Mon, 01-Jan-2007 01:54:21 GMT' +>>> http_date(t) +'Mon, 01 Jan 2007 01:54:21 GMT' + ### iri_to_uri ########################################################### >>> from django.utils.encoding import iri_to_uri >>> iri_to_uri(u'red%09ros\xe9#red')