1
0
mirror of https://github.com/django/django.git synced 2025-07-07 19:29:12 +00:00

[soc2009/model-validation] Merged to trunk at r11791

git-svn-id: http://code.djangoproject.com/svn/django/branches/soc2009/model-validation@11798 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Honza Král 2009-12-07 01:41:26 +00:00
parent 30ea350dab
commit 3b895d4a9a
78 changed files with 2154 additions and 1252 deletions

View File

@ -17,6 +17,7 @@ The PRIMARY AUTHORS are (and/or have been):
* Justin Bronn * Justin Bronn
* Karen Tracey * Karen Tracey
* Jannis Leidel * Jannis Leidel
* James Tauber
More information on the main contributors to Django can be found in More information on the main contributors to Django can be found in
docs/internals/committers.txt. docs/internals/committers.txt.

View File

@ -5,7 +5,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Django\n" "Project-Id-Version: Django\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2009-07-07 15:04+0200\n" "POT-Creation-Date: 2009-11-30 11:19+0100\n"
"PO-Revision-Date: 2009-03-24 13:28+0100\n" "PO-Revision-Date: 2009-03-24 13:28+0100\n"
"Last-Translator: Django Catalan Group <django-cat@googlegroups.com>\n" "Last-Translator: Django Catalan Group <django-cat@googlegroups.com>\n"
"Language-Team: Catalan <ca@li.org>\n" "Language-Team: Catalan <ca@li.org>\n"
@ -223,7 +223,7 @@ msgstr "xinès tradicional"
msgid "Successfully deleted %(count)d %(items)s." msgid "Successfully deleted %(count)d %(items)s."
msgstr "Eliminat/s %(count)d %(items)s satisfactòriament." msgstr "Eliminat/s %(count)d %(items)s satisfactòriament."
#: contrib/admin/actions.py:67 contrib/admin/options.py:1025 #: contrib/admin/actions.py:67 contrib/admin/options.py:1033
msgid "Are you sure?" msgid "Are you sure?"
msgstr "Esteu segurs?" msgstr "Esteu segurs?"
@ -266,15 +266,15 @@ msgstr "Aquest mes"
msgid "This year" msgid "This year"
msgstr "Aquest any" msgstr "Aquest any"
#: contrib/admin/filterspecs.py:147 forms/widgets.py:434 #: contrib/admin/filterspecs.py:147 forms/widgets.py:435
msgid "Yes" msgid "Yes"
msgstr "Si" msgstr "Si"
#: contrib/admin/filterspecs.py:147 forms/widgets.py:434 #: contrib/admin/filterspecs.py:147 forms/widgets.py:435
msgid "No" msgid "No"
msgstr "No" msgstr "No"
#: contrib/admin/filterspecs.py:154 forms/widgets.py:434 #: contrib/admin/filterspecs.py:154 forms/widgets.py:435
msgid "Unknown" msgid "Unknown"
msgstr "Desconegut" msgstr "Desconegut"
@ -310,61 +310,61 @@ msgstr "entrada del registre"
msgid "log entries" msgid "log entries"
msgstr "entrades del registre" msgstr "entrades del registre"
#: contrib/admin/options.py:133 contrib/admin/options.py:147 #: contrib/admin/options.py:134 contrib/admin/options.py:148
msgid "None" msgid "None"
msgstr "cap" msgstr "cap"
#: contrib/admin/options.py:519 #: contrib/admin/options.py:521
#, python-format #, python-format
msgid "Changed %s." msgid "Changed %s."
msgstr "Modificat %s." msgstr "Modificat %s."
#: contrib/admin/options.py:519 contrib/admin/options.py:529 #: contrib/admin/options.py:521 contrib/admin/options.py:531
#: contrib/comments/templates/comments/preview.html:16 forms/models.py:388 #: contrib/comments/templates/comments/preview.html:16 forms/models.py:384
#: forms/models.py:600 #: forms/models.py:596
msgid "and" msgid "and"
msgstr "i" msgstr "i"
#: contrib/admin/options.py:524 #: contrib/admin/options.py:526
#, python-format #, python-format
msgid "Added %(name)s \"%(object)s\"." msgid "Added %(name)s \"%(object)s\"."
msgstr "Afegit %(name)s \"%(object)s\"" msgstr "Afegit %(name)s \"%(object)s\""
#: contrib/admin/options.py:528 #: contrib/admin/options.py:530
#, python-format #, python-format
msgid "Changed %(list)s for %(name)s \"%(object)s\"." msgid "Changed %(list)s for %(name)s \"%(object)s\"."
msgstr "Modificat %(list)s per a %(name)s \"%(object)s\"." msgstr "Modificat %(list)s per a %(name)s \"%(object)s\"."
#: contrib/admin/options.py:533 #: contrib/admin/options.py:535
#, python-format #, python-format
msgid "Deleted %(name)s \"%(object)s\"." msgid "Deleted %(name)s \"%(object)s\"."
msgstr "Eliminat %(name)s \"%(object)s\"." msgstr "Eliminat %(name)s \"%(object)s\"."
#: contrib/admin/options.py:537 #: contrib/admin/options.py:539
msgid "No fields changed." msgid "No fields changed."
msgstr "Cap camp canviat." msgstr "Cap camp canviat."
#: contrib/admin/options.py:598 contrib/auth/admin.py:67 #: contrib/admin/options.py:601 contrib/auth/admin.py:67
#, python-format #, python-format
msgid "The %(name)s \"%(obj)s\" was added successfully." msgid "The %(name)s \"%(obj)s\" was added successfully."
msgstr "El/la %(name)s \"%(obj)s\".ha estat afegit/da amb èxit." msgstr "El/la %(name)s \"%(obj)s\".ha estat afegit/da amb èxit."
#: contrib/admin/options.py:602 contrib/admin/options.py:635 #: contrib/admin/options.py:605 contrib/admin/options.py:638
#: contrib/auth/admin.py:75 #: contrib/auth/admin.py:75
msgid "You may edit it again below." msgid "You may edit it again below."
msgstr "Podeu editar-lo de nou a baix." msgstr "Podeu editar-lo de nou a baix."
#: contrib/admin/options.py:612 contrib/admin/options.py:645 #: contrib/admin/options.py:615 contrib/admin/options.py:648
#, python-format #, python-format
msgid "You may add another %s below." msgid "You may add another %s below."
msgstr "Podeu afegir un altre %s a baix." msgstr "Podeu afegir un altre %s a baix."
#: contrib/admin/options.py:633 #: contrib/admin/options.py:636
#, python-format #, python-format
msgid "The %(name)s \"%(obj)s\" was changed successfully." msgid "The %(name)s \"%(obj)s\" was changed successfully."
msgstr "S'ha modificat amb èxit el/la %(name)s \"%(obj)s." msgstr "S'ha modificat amb èxit el/la %(name)s \"%(obj)s."
#: contrib/admin/options.py:641 #: contrib/admin/options.py:644
#, python-format #, python-format
msgid "" msgid ""
"The %(name)s \"%(obj)s\" was added successfully. You may edit it again below." "The %(name)s \"%(obj)s\" was added successfully. You may edit it again below."
@ -372,43 +372,43 @@ msgstr ""
"S'ha afegit exitosament el/la %(name)s \"%(obj)s\". Pot editar-lo de nou " "S'ha afegit exitosament el/la %(name)s \"%(obj)s\". Pot editar-lo de nou "
"abaix." "abaix."
#: contrib/admin/options.py:772 #: contrib/admin/options.py:777
#, python-format #, python-format
msgid "Add %s" msgid "Add %s"
msgstr "Afegir %s" msgstr "Afegir %s"
#: contrib/admin/options.py:803 contrib/admin/options.py:1003 #: contrib/admin/options.py:809 contrib/admin/options.py:1011
#, python-format #, python-format
msgid "%(name)s object with primary key %(key)r does not exist." msgid "%(name)s object with primary key %(key)r does not exist."
msgstr "No existèix cap objecte %(name)s amb la clau primària %(key)r." msgstr "No existèix cap objecte %(name)s amb la clau primària %(key)r."
#: contrib/admin/options.py:860 #: contrib/admin/options.py:866
#, python-format #, python-format
msgid "Change %s" msgid "Change %s"
msgstr "Modificar %s" msgstr "Modificar %s"
#: contrib/admin/options.py:904 #: contrib/admin/options.py:910
msgid "Database error" msgid "Database error"
msgstr "Error de base de dades" msgstr "Error de base de dades"
#: contrib/admin/options.py:940 #: contrib/admin/options.py:946
#, python-format #, python-format
msgid "%(count)s %(name)s was changed successfully." msgid "%(count)s %(name)s was changed successfully."
msgid_plural "%(count)s %(name)s were changed successfully." msgid_plural "%(count)s %(name)s were changed successfully."
msgstr[0] "%(count)s %(name)s s'ha modificat amb èxit." msgstr[0] "%(count)s %(name)s s'ha modificat amb èxit."
msgstr[1] "%(count)s %(name)s s'han modificat amb èxit." msgstr[1] "%(count)s %(name)s s'han modificat amb èxit."
#: contrib/admin/options.py:1018 #: contrib/admin/options.py:1026
#, python-format #, python-format
msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgid "The %(name)s \"%(obj)s\" was deleted successfully."
msgstr "El/la %(name)s \"%(obj)s\" ha estat eliminat amb èxit." msgstr "El/la %(name)s \"%(obj)s\" ha estat eliminat amb èxit."
#: contrib/admin/options.py:1054 #: contrib/admin/options.py:1063
#, python-format #, python-format
msgid "Change history: %s" msgid "Change history: %s"
msgstr "Modificar històric: %s" msgstr "Modificar històric: %s"
#: contrib/admin/sites.py:20 contrib/admin/views/decorators.py:14 #: contrib/admin/sites.py:22 contrib/admin/views/decorators.py:14
#: contrib/auth/forms.py:80 #: contrib/auth/forms.py:80
msgid "" msgid ""
"Please enter a correct username and password. Note that both fields are case-" "Please enter a correct username and password. Note that both fields are case-"
@ -417,11 +417,11 @@ msgstr ""
"Si us plau, introduïu un nom d'usuari i contrasenya vàlids. Tingueu en " "Si us plau, introduïu un nom d'usuari i contrasenya vàlids. Tingueu en "
"compte que tots dos camps son sensibles a majúscules i minúscules." "compte que tots dos camps son sensibles a majúscules i minúscules."
#: contrib/admin/sites.py:278 contrib/admin/views/decorators.py:40 #: contrib/admin/sites.py:292 contrib/admin/views/decorators.py:40
msgid "Please log in again, because your session has expired." msgid "Please log in again, because your session has expired."
msgstr "Si us plau, identifiqueu-vos de nou doncs la vostra sessió ha expirat." msgstr "Si us plau, identifiqueu-vos de nou doncs la vostra sessió ha expirat."
#: contrib/admin/sites.py:285 contrib/admin/views/decorators.py:47 #: contrib/admin/sites.py:299 contrib/admin/views/decorators.py:47
msgid "" msgid ""
"Looks like your browser isn't configured to accept cookies. Please enable " "Looks like your browser isn't configured to accept cookies. Please enable "
"cookies, reload this page, and try again." "cookies, reload this page, and try again."
@ -430,29 +430,29 @@ msgstr ""
"'cookies' (galetes). Si us plau, habiliteu les 'cookies', recarregueu " "'cookies' (galetes). Si us plau, habiliteu les 'cookies', recarregueu "
"aquesta pàgina i proveu-ho de nou. " "aquesta pàgina i proveu-ho de nou. "
#: contrib/admin/sites.py:301 contrib/admin/sites.py:307 #: contrib/admin/sites.py:315 contrib/admin/sites.py:321
#: contrib/admin/views/decorators.py:66 #: contrib/admin/views/decorators.py:66
msgid "Usernames cannot contain the '@' character." msgid "Usernames cannot contain the '@' character."
msgstr "Els noms d'usuari no poden contenir el caracter '@'." msgstr "Els noms d'usuari no poden contenir el caracter '@'."
#: contrib/admin/sites.py:304 contrib/admin/views/decorators.py:62 #: contrib/admin/sites.py:318 contrib/admin/views/decorators.py:62
#, python-format #, python-format
msgid "Your e-mail address is not your username. Try '%s' instead." msgid "Your e-mail address is not your username. Try '%s' instead."
msgstr "" msgstr ""
"La vostra adreça de correu no és el vostre nom d'usuari. Provi '%s' en tot " "La vostra adreça de correu no és el vostre nom d'usuari. Provi '%s' en tot "
"cas." "cas."
#: contrib/admin/sites.py:360 #: contrib/admin/sites.py:374
msgid "Site administration" msgid "Site administration"
msgstr "Lloc administratiu" msgstr "Lloc administratiu"
#: contrib/admin/sites.py:373 contrib/admin/templates/admin/login.html:26 #: contrib/admin/sites.py:388 contrib/admin/templates/admin/login.html:26
#: contrib/admin/templates/registration/password_reset_complete.html:14 #: contrib/admin/templates/registration/password_reset_complete.html:14
#: contrib/admin/views/decorators.py:20 #: contrib/admin/views/decorators.py:20
msgid "Log in" msgid "Log in"
msgstr "Iniciar sessió" msgstr "Iniciar sessió"
#: contrib/admin/sites.py:417 #: contrib/admin/sites.py:433
#, python-format #, python-format
msgid "%s administration" msgid "%s administration"
msgstr "Administració de %s" msgstr "Administració de %s"
@ -467,27 +467,27 @@ msgstr "Un o més %(fieldname)s en %(name)s: %(obj)s"
msgid "One or more %(fieldname)s in %(name)s:" msgid "One or more %(fieldname)s in %(name)s:"
msgstr "Un o més %(fieldname)s en %(name)s:" msgstr "Un o més %(fieldname)s en %(name)s:"
#: contrib/admin/widgets.py:71 #: contrib/admin/widgets.py:72
msgid "Date:" msgid "Date:"
msgstr "Data:" msgstr "Data:"
#: contrib/admin/widgets.py:71 #: contrib/admin/widgets.py:72
msgid "Time:" msgid "Time:"
msgstr "Hora:" msgstr "Hora:"
#: contrib/admin/widgets.py:95 #: contrib/admin/widgets.py:96
msgid "Currently:" msgid "Currently:"
msgstr "Actualment:" msgstr "Actualment:"
#: contrib/admin/widgets.py:95 #: contrib/admin/widgets.py:96
msgid "Change:" msgid "Change:"
msgstr "Modificar:" msgstr "Modificar:"
#: contrib/admin/widgets.py:124 #: contrib/admin/widgets.py:125
msgid "Lookup" msgid "Lookup"
msgstr "Cercar" msgstr "Cercar"
#: contrib/admin/widgets.py:236 #: contrib/admin/widgets.py:237
msgid "Add Another" msgid "Add Another"
msgstr "Afegir un altre" msgstr "Afegir un altre"
@ -502,7 +502,7 @@ msgstr "Ho sentim, però no s'ha pogut trobar la pàgina sol·licitada"
#: contrib/admin/templates/admin/500.html:4 #: contrib/admin/templates/admin/500.html:4
#: contrib/admin/templates/admin/app_index.html:8 #: contrib/admin/templates/admin/app_index.html:8
#: contrib/admin/templates/admin/base.html:31 #: contrib/admin/templates/admin/base.html:54
#: contrib/admin/templates/admin/change_form.html:17 #: contrib/admin/templates/admin/change_form.html:17
#: contrib/admin/templates/admin/change_list.html:25 #: contrib/admin/templates/admin/change_list.html:25
#: contrib/admin/templates/admin/delete_confirmation.html:6 #: contrib/admin/templates/admin/delete_confirmation.html:6
@ -555,18 +555,18 @@ msgstr "Anar"
msgid "%(name)s" msgid "%(name)s"
msgstr "%(name)s" msgstr "%(name)s"
#: contrib/admin/templates/admin/base.html:26 #: contrib/admin/templates/admin/base.html:27
msgid "Welcome," msgid "Welcome,"
msgstr "Benvingut/da," msgstr "Benvingut/da,"
#: contrib/admin/templates/admin/base.html:26 #: contrib/admin/templates/admin/base.html:32
#: contrib/admin/templates/registration/password_change_done.html:3 #: contrib/admin/templates/registration/password_change_done.html:3
#: contrib/admin/templates/registration/password_change_form.html:3 #: contrib/admin/templates/registration/password_change_form.html:3
#: contrib/admindocs/templates/admin_doc/bookmarklets.html:3 #: contrib/admindocs/templates/admin_doc/bookmarklets.html:3
msgid "Documentation" msgid "Documentation"
msgstr "Documentació" msgstr "Documentació"
#: contrib/admin/templates/admin/base.html:26 #: contrib/admin/templates/admin/base.html:40
#: contrib/admin/templates/admin/auth/user/change_password.html:14 #: contrib/admin/templates/admin/auth/user/change_password.html:14
#: contrib/admin/templates/admin/auth/user/change_password.html:47 #: contrib/admin/templates/admin/auth/user/change_password.html:47
#: contrib/admin/templates/registration/password_change_done.html:3 #: contrib/admin/templates/registration/password_change_done.html:3
@ -574,7 +574,7 @@ msgstr "Documentació"
msgid "Change password" msgid "Change password"
msgstr "Canviar contrasenya" msgstr "Canviar contrasenya"
#: contrib/admin/templates/admin/base.html:26 #: contrib/admin/templates/admin/base.html:47
#: contrib/admin/templates/registration/password_change_done.html:3 #: contrib/admin/templates/registration/password_change_done.html:3
#: contrib/admin/templates/registration/password_change_form.html:3 #: contrib/admin/templates/registration/password_change_form.html:3
msgid "Log out" msgid "Log out"
@ -600,7 +600,7 @@ msgstr "Històric"
#: contrib/admin/templates/admin/change_form.html:28 #: contrib/admin/templates/admin/change_form.html:28
#: contrib/admin/templates/admin/edit_inline/stacked.html:13 #: contrib/admin/templates/admin/edit_inline/stacked.html:13
#: contrib/admin/templates/admin/edit_inline/tabular.html:27 #: contrib/admin/templates/admin/edit_inline/tabular.html:28
msgid "View on site" msgid "View on site"
msgstr "Veure al lloc" msgstr "Veure al lloc"
@ -670,9 +670,9 @@ msgstr ""
#, python-format #, python-format
msgid "" msgid ""
"Are you sure you want to delete the selected %(object_name)s objects? All of " "Are you sure you want to delete the selected %(object_name)s objects? All of "
"the following objects and it's related items will be deleted:" "the following objects and their related items will be deleted:"
msgstr "" msgstr ""
"Esteu segurs de voler esborrar els/les %(object_name)s seleccionats?Tots " "Esteu segurs de voler esborrar els/les %(object_name)s seleccionats? Tots "
"aquests objectes i els seus elements relacionats s'esborraran:" "aquests objectes i els seus elements relacionats s'esborraran:"
#: contrib/admin/templates/admin/filter.html:2 #: contrib/admin/templates/admin/filter.html:2
@ -736,7 +736,6 @@ msgid "User"
msgstr "Usuari" msgstr "Usuari"
#: contrib/admin/templates/admin/object_history.html:24 #: contrib/admin/templates/admin/object_history.html:24
#: contrib/comments/templates/comments/moderation_queue.html:33
msgid "Action" msgid "Action"
msgstr "Acció" msgstr "Acció"
@ -985,7 +984,7 @@ msgstr "Adreça de correu electrònic:"
msgid "Reset my password" msgid "Reset my password"
msgstr "Restablir la meva contrasenya" msgstr "Restablir la meva contrasenya"
#: contrib/admin/templatetags/admin_list.py:299 #: contrib/admin/templatetags/admin_list.py:304
msgid "All dates" msgid "All dates"
msgstr "Totes les dates" msgstr "Totes les dates"
@ -1007,145 +1006,144 @@ msgstr "lloc"
msgid "template" msgid "template"
msgstr "plantilla" msgstr "plantilla"
#: contrib/admindocs/views.py:58 contrib/admindocs/views.py:60 #: contrib/admindocs/views.py:61 contrib/admindocs/views.py:63
#: contrib/admindocs/views.py:62 #: contrib/admindocs/views.py:65
msgid "tag:" msgid "tag:"
msgstr "etiqueta:" msgstr "etiqueta:"
#: contrib/admindocs/views.py:91 contrib/admindocs/views.py:93 #: contrib/admindocs/views.py:94 contrib/admindocs/views.py:96
#: contrib/admindocs/views.py:95 #: contrib/admindocs/views.py:98
msgid "filter:" msgid "filter:"
msgstr "filtre:" msgstr "filtre:"
#: contrib/admindocs/views.py:155 contrib/admindocs/views.py:157 #: contrib/admindocs/views.py:158 contrib/admindocs/views.py:160
#: contrib/admindocs/views.py:159 #: contrib/admindocs/views.py:162
msgid "view:" msgid "view:"
msgstr "vista:" msgstr "vista:"
#: contrib/admindocs/views.py:187 #: contrib/admindocs/views.py:190
#, python-format #, python-format
msgid "App %r not found" msgid "App %r not found"
msgstr "No s'ha pogut trobar l'aplicació %r" msgstr "No s'ha pogut trobar l'aplicació %r"
#: contrib/admindocs/views.py:194 #: contrib/admindocs/views.py:197
#, python-format #, python-format
msgid "Model %(model_name)r not found in app %(app_label)r" msgid "Model %(model_name)r not found in app %(app_label)r"
msgstr "El model %(model_name)r no s'ha trobat en l'aplicació %(app_label)r" msgstr "El model %(model_name)r no s'ha trobat en l'aplicació %(app_label)r"
#: contrib/admindocs/views.py:206 #: contrib/admindocs/views.py:209
#, python-format #, python-format
msgid "the related `%(app_label)s.%(data_type)s` object" msgid "the related `%(app_label)s.%(data_type)s` object"
msgstr "l'objecte relacionat `%(app_label)s.%(data_type)s`" msgstr "l'objecte relacionat `%(app_label)s.%(data_type)s`"
#: contrib/admindocs/views.py:206 contrib/admindocs/views.py:225 #: contrib/admindocs/views.py:209 contrib/admindocs/views.py:228
#: contrib/admindocs/views.py:230 contrib/admindocs/views.py:244 #: contrib/admindocs/views.py:233 contrib/admindocs/views.py:247
#: contrib/admindocs/views.py:258 contrib/admindocs/views.py:263 #: contrib/admindocs/views.py:261 contrib/admindocs/views.py:266
msgid "model:" msgid "model:"
msgstr "model:" msgstr "model:"
#: contrib/admindocs/views.py:221 contrib/admindocs/views.py:253 #: contrib/admindocs/views.py:224 contrib/admindocs/views.py:256
#, python-format #, python-format
msgid "related `%(app_label)s.%(object_name)s` objects" msgid "related `%(app_label)s.%(object_name)s` objects"
msgstr "objectes relacionats `%(app_label)s.%(object_name)s`" msgstr "objectes relacionats `%(app_label)s.%(object_name)s`"
#: contrib/admindocs/views.py:225 contrib/admindocs/views.py:258 #: contrib/admindocs/views.py:228 contrib/admindocs/views.py:261
#, python-format #, python-format
msgid "all %s" msgid "all %s"
msgstr "tots %s" msgstr "tots %s"
#: contrib/admindocs/views.py:230 contrib/admindocs/views.py:263 #: contrib/admindocs/views.py:233 contrib/admindocs/views.py:266
#, python-format #, python-format
msgid "number of %s" msgid "number of %s"
msgstr "nombre de %s" msgstr "nombre de %s"
#: contrib/admindocs/views.py:268 #: contrib/admindocs/views.py:271
#, python-format #, python-format
msgid "Fields on %s objects" msgid "Fields on %s objects"
msgstr "Camps en objectes %s" msgstr "Camps en objectes %s"
#: contrib/admindocs/views.py:331 contrib/admindocs/views.py:342 #: contrib/admindocs/views.py:334 contrib/admindocs/views.py:345
#: contrib/admindocs/views.py:344 contrib/admindocs/views.py:350 #: contrib/admindocs/views.py:347 contrib/admindocs/views.py:353
#: contrib/admindocs/views.py:351 contrib/admindocs/views.py:353 #: contrib/admindocs/views.py:354 contrib/admindocs/views.py:356
msgid "Integer" msgid "Integer"
msgstr "Enter" msgstr "Enter"
#: contrib/admindocs/views.py:332 #: contrib/admindocs/views.py:335
msgid "Boolean (Either True or False)" msgid "Boolean (Either True or False)"
msgstr "Booleà (Verdader o Fals)" msgstr "Booleà (Verdader o Fals)"
#: contrib/admindocs/views.py:333 contrib/admindocs/views.py:352 #: contrib/admindocs/views.py:336 contrib/admindocs/views.py:355
#, python-format #, python-format
msgid "String (up to %(max_length)s)" msgid "String (up to %(max_length)s)"
msgstr "Cadena (de fins a %(max_length)s)" msgstr "Cadena (de fins a %(max_length)s)"
#: contrib/admindocs/views.py:334 #: contrib/admindocs/views.py:337
msgid "Comma-separated integers" msgid "Comma-separated integers"
msgstr "Enters separats per comes" msgstr "Enters separats per comes"
#: contrib/admindocs/views.py:335 #: contrib/admindocs/views.py:338
msgid "Date (without time)" msgid "Date (without time)"
msgstr "Data (sense hora)" msgstr "Data (sense hora)"
#: contrib/admindocs/views.py:336 #: contrib/admindocs/views.py:339
msgid "Date (with time)" msgid "Date (with time)"
msgstr "Data (amb hora)" msgstr "Data (amb hora)"
#: contrib/admindocs/views.py:337 #: contrib/admindocs/views.py:340
msgid "Decimal number" msgid "Decimal number"
msgstr "Número decimal" msgstr "Número decimal"
#: contrib/admindocs/views.py:338 #: contrib/admindocs/views.py:341
msgid "E-mail address" msgid "E-mail address"
msgstr "Adreça de correu electrònic" msgstr "Adreça de correu electrònic"
#: contrib/admindocs/views.py:339 contrib/admindocs/views.py:340 #: contrib/admindocs/views.py:342 contrib/admindocs/views.py:343
#: contrib/admindocs/views.py:343 #: contrib/admindocs/views.py:346
msgid "File path" msgid "File path"
msgstr "Ruta del fitxer" msgstr "Ruta del fitxer"
#: contrib/admindocs/views.py:341 #: contrib/admindocs/views.py:344
msgid "Floating point number" msgid "Floating point number"
msgstr "Número amb punt de coma flotant" msgstr "Número amb punt de coma flotant"
#: contrib/admindocs/views.py:345 contrib/comments/models.py:60 #: contrib/admindocs/views.py:348 contrib/comments/models.py:60
msgid "IP address" msgid "IP address"
msgstr "Adreça IP" msgstr "Adreça IP"
#: contrib/admindocs/views.py:347 #: contrib/admindocs/views.py:350
msgid "Boolean (Either True, False or None)" msgid "Boolean (Either True, False or None)"
msgstr "Booleà (Verdader, Fals o 'None' (cap))" msgstr "Booleà (Verdader, Fals o 'None' (cap))"
#: contrib/admindocs/views.py:348 #: contrib/admindocs/views.py:351
msgid "Relation to parent model" msgid "Relation to parent model"
msgstr "Relació amb el model pare" msgstr "Relació amb el model pare"
#: contrib/admindocs/views.py:349 #: contrib/admindocs/views.py:352
msgid "Phone number" msgid "Phone number"
msgstr "Número de telèfon" msgstr "Número de telèfon"
#: contrib/admindocs/views.py:354 #: contrib/admindocs/views.py:357
msgid "Text" msgid "Text"
msgstr "Text" msgstr "Text"
#: contrib/admindocs/views.py:355 #: contrib/admindocs/views.py:358
msgid "Time" msgid "Time"
msgstr "Hora" msgstr "Hora"
#: contrib/admindocs/views.py:356 contrib/comments/forms.py:95 #: contrib/admindocs/views.py:359 contrib/comments/forms.py:95
#: contrib/comments/templates/comments/moderation_queue.html:37
#: contrib/flatpages/admin.py:8 contrib/flatpages/models.py:7 #: contrib/flatpages/admin.py:8 contrib/flatpages/models.py:7
msgid "URL" msgid "URL"
msgstr "URL" msgstr "URL"
#: contrib/admindocs/views.py:357 #: contrib/admindocs/views.py:360
msgid "U.S. state (two uppercase letters)" msgid "U.S. state (two uppercase letters)"
msgstr "Estat dels E.U.A. (dues lletres majúscules)" msgstr "Estat dels E.U.A. (dues lletres majúscules)"
#: contrib/admindocs/views.py:358 #: contrib/admindocs/views.py:361
msgid "XML text" msgid "XML text"
msgstr "Text XML" msgstr "Text XML"
#: contrib/admindocs/views.py:384 #: contrib/admindocs/views.py:387
#, python-format #, python-format
msgid "%s does not appear to be a urlpattern object" msgid "%s does not appear to be a urlpattern object"
msgstr "%s no sembla ser un objecte 'urlpattern'" msgstr "%s no sembla ser un objecte 'urlpattern'"
@ -1438,22 +1436,54 @@ msgstr "usuaris"
msgid "message" msgid "message"
msgstr "missatge" msgstr "missatge"
#: contrib/auth/views.py:56 #: contrib/auth/views.py:60
msgid "Logged out" msgid "Logged out"
msgstr "Sessió finalitzada" msgstr "Sessió finalitzada"
#: contrib/auth/management/commands/createsuperuser.py:23 forms/fields.py:429 #: contrib/auth/management/commands/createsuperuser.py:23 forms/fields.py:428
msgid "Enter a valid e-mail address." msgid "Enter a valid e-mail address."
msgstr "Introduïu una adreça de correu vàlida." msgstr "Introduïu una adreça de correu vàlida."
#: contrib/comments/admin.py:11 #: contrib/comments/admin.py:12
msgid "Content" msgid "Content"
msgstr "contingut" msgstr "contingut"
#: contrib/comments/admin.py:14 #: contrib/comments/admin.py:15
msgid "Metadata" msgid "Metadata"
msgstr "metadades" msgstr "metadades"
# Context problem... waitting for comments from django-i18n
#: contrib/comments/admin.py:39
msgid "flagged"
msgstr "marcat"
#: contrib/comments/admin.py:40
msgid "Flag selected comments"
msgstr "Marcar els comentaris seleccionats"
#: contrib/comments/admin.py:43
msgid "approved"
msgstr "aprovat"
#: contrib/comments/admin.py:44
msgid "Approve selected comments"
msgstr "Aprovar els comentaris seleccionats"
#: contrib/comments/admin.py:47
msgid "removed"
msgstr "eliminat"
#: contrib/comments/admin.py:48
msgid "Remove selected comments"
msgstr "Eliminar els comentaris seleccionats"
#: contrib/comments/admin.py:60
#, python-format
msgid "1 comment was successfully %(action)s."
msgid_plural "%(count)s comments were successfully %(action)s."
msgstr[0] "1 comentari ha estat %(action)s satisfactòriament."
msgstr[1] "%(count)s comentaris han estat %(action)s satisfactòriament."
#: contrib/comments/feeds.py:13 #: contrib/comments/feeds.py:13
#, python-format #, python-format
msgid "%(site_name)s comments" msgid "%(site_name)s comments"
@ -1465,7 +1495,6 @@ msgid "Latest comments on %(site_name)s"
msgstr "Últims comentaris a %(site_name)s." msgstr "Últims comentaris a %(site_name)s."
#: contrib/comments/forms.py:93 #: contrib/comments/forms.py:93
#: contrib/comments/templates/comments/moderation_queue.html:34
msgid "Name" msgid "Name"
msgstr "nom" msgstr "nom"
@ -1474,7 +1503,6 @@ msgid "Email address"
msgstr "Adreça de correu electrònic" msgstr "Adreça de correu electrònic"
#: contrib/comments/forms.py:96 #: contrib/comments/forms.py:96
#: contrib/comments/templates/comments/moderation_queue.html:35
msgid "Comment" msgid "Comment"
msgstr "Comentari" msgstr "Comentari"
@ -1606,7 +1634,6 @@ msgid "Really make this comment public?"
msgstr "Realment vol fer aquest comentari públic?" msgstr "Realment vol fer aquest comentari públic?"
#: contrib/comments/templates/comments/approve.html:12 #: contrib/comments/templates/comments/approve.html:12
#: contrib/comments/templates/comments/moderation_queue.html:49
msgid "Approve" msgid "Approve"
msgstr "Aprovar" msgstr "Aprovar"
@ -1631,7 +1658,6 @@ msgid "Really remove this comment?"
msgstr "Realment vol eliminar aquest comentari?" msgstr "Realment vol eliminar aquest comentari?"
#: contrib/comments/templates/comments/delete.html:12 #: contrib/comments/templates/comments/delete.html:12
#: contrib/comments/templates/comments/moderation_queue.html:53
msgid "Remove" msgid "Remove"
msgstr "Eliminar" msgstr "Eliminar"
@ -1666,39 +1692,6 @@ msgstr "Publicar"
msgid "Preview" msgid "Preview"
msgstr "Vista prèvia" msgstr "Vista prèvia"
#: contrib/comments/templates/comments/moderation_queue.html:4
#: contrib/comments/templates/comments/moderation_queue.html:19
msgid "Comment moderation queue"
msgstr "Cua de moderació de comentaris"
#: contrib/comments/templates/comments/moderation_queue.html:26
msgid "No comments to moderate"
msgstr "No hi ha comentaris per a moderar"
#: contrib/comments/templates/comments/moderation_queue.html:36
msgid "Email"
msgstr "Correu electrònic"
#: contrib/comments/templates/comments/moderation_queue.html:38
msgid "Authenticated?"
msgstr "Autentificat?"
#: contrib/comments/templates/comments/moderation_queue.html:39
msgid "IP Address"
msgstr "Adreça IP"
#: contrib/comments/templates/comments/moderation_queue.html:40
msgid "Date posted"
msgstr "Data d'enviament"
#: contrib/comments/templates/comments/moderation_queue.html:63
msgid "yes"
msgstr "si"
#: contrib/comments/templates/comments/moderation_queue.html:63
msgid "no"
msgstr "no"
#: contrib/comments/templates/comments/posted.html:4 #: contrib/comments/templates/comments/posted.html:4
msgid "Thanks for commenting" msgid "Thanks for commenting"
msgstr "Gràcies per comentar" msgstr "Gràcies per comentar"
@ -1793,7 +1786,7 @@ msgstr "pàgina estàtica"
msgid "flat pages" msgid "flat pages"
msgstr "pàgines estàtiques" msgstr "pàgines estàtiques"
#: contrib/formtools/wizard.py:130 #: contrib/formtools/wizard.py:132
msgid "" msgid ""
"We apologize, but your form has expired. Please continue filling out the " "We apologize, but your form has expired. Please continue filling out the "
"form from this page." "form from this page."
@ -2615,6 +2608,10 @@ msgstr "Validació invàlida del número de compte bancari."
msgid "Enter a valid Finnish social security number." msgid "Enter a valid Finnish social security number."
msgstr "Introduïu un número vàlid de la seguretat social finlandesa." msgstr "Introduïu un número vàlid de la seguretat social finlandesa."
#: contrib/localflavor/fr/forms.py:30
msgid "Phone numbers must be in 0X XX XX XX XX format."
msgstr "Els números de telèfon han de estar en el format 0X XX XX XX XX."
#: contrib/localflavor/in_/forms.py:14 #: contrib/localflavor/in_/forms.py:14
msgid "Enter a zip code in the format XXXXXXX." msgid "Enter a zip code in the format XXXXXXX."
msgstr "Introduïu un codi zip en el format XXXXXXX." msgstr "Introduïu un codi zip en el format XXXXXXX."
@ -3053,7 +3050,8 @@ msgstr "Validació invàlida del número tributari (NIP)."
#: contrib/localflavor/pl/forms.py:109 #: contrib/localflavor/pl/forms.py:109
msgid "National Business Register Number (REGON) consists of 9 or 14 digits." msgid "National Business Register Number (REGON) consists of 9 or 14 digits."
msgstr "" msgstr ""
"El número nacional de registre de negocis (REGON) consisteix en 9 o 14 dígits." "El número nacional de registre de negocis (REGON) consisteix en 9 o 14 "
"dígits."
#: contrib/localflavor/pl/forms.py:110 #: contrib/localflavor/pl/forms.py:110
msgid "Wrong checksum for the National Business Register Number (REGON)." msgid "Wrong checksum for the National Business Register Number (REGON)."
@ -3943,14 +3941,14 @@ msgstr "Aquest valor ha de ser None (Cap), True (Veritat) o False (Fals)"
msgid "Enter a valid time in HH:MM[:ss[.uuuuuu]] format." msgid "Enter a valid time in HH:MM[:ss[.uuuuuu]] format."
msgstr "Introduïu una hora vàlida en el format HH:MM[:ss[.uuuuuu]]." msgstr "Introduïu una hora vàlida en el format HH:MM[:ss[.uuuuuu]]."
#: db/models/fields/related.py:816 #: db/models/fields/related.py:869
msgid "" msgid ""
"Hold down \"Control\", or \"Command\" on a Mac, to select more than one." "Hold down \"Control\", or \"Command\" on a Mac, to select more than one."
msgstr "" msgstr ""
"Premeu la tecla \"Control\" -o \"Command\" en un Mac- per seleccionar més " "Premeu la tecla \"Control\" -o \"Command\" en un Mac- per seleccionar més "
"d'un valor." "d'un valor."
#: db/models/fields/related.py:894 #: db/models/fields/related.py:930
#, python-format #, python-format
msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid." msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid."
msgid_plural "" msgid_plural ""
@ -3962,95 +3960,95 @@ msgstr[1] ""
"Si us plau, introduïu IDs de %(self)s vàlids. Els valors %(value)r són " "Si us plau, introduïu IDs de %(self)s vàlids. Els valors %(value)r són "
"invàlids." "invàlids."
#: forms/fields.py:54 #: forms/fields.py:53
msgid "This field is required." msgid "This field is required."
msgstr "Aquest camp és obligatori." msgstr "Aquest camp és obligatori."
#: forms/fields.py:55 #: forms/fields.py:54
msgid "Enter a valid value." msgid "Enter a valid value."
msgstr "Introduïu un valor vàlid." msgstr "Introduïu un valor vàlid."
#: forms/fields.py:138 #: forms/fields.py:137
#, python-format #, python-format
msgid "Ensure this value has at most %(max)d characters (it has %(length)d)." msgid "Ensure this value has at most %(max)d characters (it has %(length)d)."
msgstr "" msgstr ""
"Assegureu-vos de que el valor té com a màxim %(max)d caràcters (en té %" "Assegureu-vos de que el valor té com a màxim %(max)d caràcters (en té %"
"(length)d)." "(length)d)."
#: forms/fields.py:139 #: forms/fields.py:138
#, python-format #, python-format
msgid "Ensure this value has at least %(min)d characters (it has %(length)d)." msgid "Ensure this value has at least %(min)d characters (it has %(length)d)."
msgstr "" msgstr ""
"Assegureu-vos de que el valor té com a mínim %(min)d caràcters (en té %" "Assegureu-vos de que el valor té com a mínim %(min)d caràcters (en té %"
"(length)d)." "(length)d)."
#: forms/fields.py:166 #: forms/fields.py:165
msgid "Enter a whole number." msgid "Enter a whole number."
msgstr "Introduïu un número sencer." msgstr "Introduïu un número sencer."
#: forms/fields.py:167 forms/fields.py:196 forms/fields.py:225 #: forms/fields.py:166 forms/fields.py:195 forms/fields.py:224
#, python-format #, python-format
msgid "Ensure this value is less than or equal to %s." msgid "Ensure this value is less than or equal to %s."
msgstr "Aquest valor ha de ser menor o igual a %s." msgstr "Aquest valor ha de ser menor o igual a %s."
#: forms/fields.py:168 forms/fields.py:197 forms/fields.py:226 #: forms/fields.py:167 forms/fields.py:196 forms/fields.py:225
#, python-format #, python-format
msgid "Ensure this value is greater than or equal to %s." msgid "Ensure this value is greater than or equal to %s."
msgstr "Assegureu-vos de que aquest valor sigui superior o igual a %s." msgstr "Assegureu-vos de que aquest valor sigui superior o igual a %s."
#: forms/fields.py:195 forms/fields.py:224 #: forms/fields.py:194 forms/fields.py:223
msgid "Enter a number." msgid "Enter a number."
msgstr "Introduïu un número." msgstr "Introduïu un número."
#: forms/fields.py:227 #: forms/fields.py:226
#, python-format #, python-format
msgid "Ensure that there are no more than %s digits in total." msgid "Ensure that there are no more than %s digits in total."
msgstr "Assegureu-vos de que no hi ha més de %s dígits en total." msgstr "Assegureu-vos de que no hi ha més de %s dígits en total."
#: forms/fields.py:228 #: forms/fields.py:227
#, python-format #, python-format
msgid "Ensure that there are no more than %s decimal places." msgid "Ensure that there are no more than %s decimal places."
msgstr "Assegureu-vos de que no hi ha més de %s decimals." msgstr "Assegureu-vos de que no hi ha més de %s decimals."
#: forms/fields.py:229 #: forms/fields.py:228
#, python-format #, python-format
msgid "Ensure that there are no more than %s digits before the decimal point." msgid "Ensure that there are no more than %s digits before the decimal point."
msgstr "Assegureu-vos de que no hi ha més de %s dígits decimals." msgstr "Assegureu-vos de que no hi ha més de %s dígits decimals."
#: forms/fields.py:288 forms/fields.py:863 #: forms/fields.py:287 forms/fields.py:862
msgid "Enter a valid date." msgid "Enter a valid date."
msgstr "Introduïu una data vàlida." msgstr "Introduïu una data vàlida."
#: forms/fields.py:322 forms/fields.py:864 #: forms/fields.py:321 forms/fields.py:863
msgid "Enter a valid time." msgid "Enter a valid time."
msgstr "Introduïu una hora vàlida." msgstr "Introduïu una hora vàlida."
#: forms/fields.py:361 #: forms/fields.py:360
msgid "Enter a valid date/time." msgid "Enter a valid date/time."
msgstr "Introduïu una data/hora vàlides." msgstr "Introduïu una data/hora vàlides."
#: forms/fields.py:447 #: forms/fields.py:446
msgid "No file was submitted. Check the encoding type on the form." msgid "No file was submitted. Check the encoding type on the form."
msgstr "" msgstr ""
"No s'ha enviat cap fitxer. Comprovi el tipus de codificació del formulari." "No s'ha enviat cap fitxer. Comprovi el tipus de codificació del formulari."
#: forms/fields.py:448 #: forms/fields.py:447
msgid "No file was submitted." msgid "No file was submitted."
msgstr "No s'ha enviat cap fitxer." msgstr "No s'ha enviat cap fitxer."
#: forms/fields.py:449 #: forms/fields.py:448
msgid "The submitted file is empty." msgid "The submitted file is empty."
msgstr "El fitxer enviat està buit." msgstr "El fitxer enviat està buit."
#: forms/fields.py:450 #: forms/fields.py:449
#, python-format #, python-format
msgid "" msgid ""
"Ensure this filename has at most %(max)d characters (it has %(length)d)." "Ensure this filename has at most %(max)d characters (it has %(length)d)."
msgstr "" msgstr ""
"Assegureu-vos de que el valor té com a màxim %(max)d caràcters " "Assegureu-vos de que el valor té com a màxim %(max)d caràcters (en té %"
"(en té %(length)d)." "(length)d)."
#: forms/fields.py:483 #: forms/fields.py:482
msgid "" msgid ""
"Upload a valid image. The file you uploaded was either not an image or a " "Upload a valid image. The file you uploaded was either not an image or a "
"corrupted image." "corrupted image."
@ -4058,28 +4056,28 @@ msgstr ""
"Envieu una imatge vàlida. El fitxer que heu enviat no era una imatge o " "Envieu una imatge vàlida. El fitxer que heu enviat no era una imatge o "
"estava corrupte." "estava corrupte."
#: forms/fields.py:544 #: forms/fields.py:543
msgid "Enter a valid URL." msgid "Enter a valid URL."
msgstr "Introduïu una URL vàlida." msgstr "Introduïu una URL vàlida."
#: forms/fields.py:545 #: forms/fields.py:544
msgid "This URL appears to be a broken link." msgid "This URL appears to be a broken link."
msgstr "Aquesta URL sembla ser un enllaç trencat." msgstr "Aquesta URL sembla ser un enllaç trencat."
#: forms/fields.py:625 forms/fields.py:703 #: forms/fields.py:624 forms/fields.py:702
#, python-format #, python-format
msgid "Select a valid choice. %(value)s is not one of the available choices." msgid "Select a valid choice. %(value)s is not one of the available choices."
msgstr "Esculliu una opció vàlida. %(value)s no és una de les opcions vàlides." msgstr "Esculliu una opció vàlida. %(value)s no és una de les opcions vàlides."
#: forms/fields.py:704 forms/fields.py:765 forms/models.py:1003 #: forms/fields.py:703 forms/fields.py:764 forms/models.py:999
msgid "Enter a list of values." msgid "Enter a list of values."
msgstr "Introduïu una llista de valors." msgstr "Introduïu una llista de valors."
#: forms/fields.py:892 #: forms/fields.py:891
msgid "Enter a valid IPv4 address." msgid "Enter a valid IPv4 address."
msgstr "Introduïu una adreça IPv4 vàlida." msgstr "Introduïu una adreça IPv4 vàlida."
#: forms/fields.py:902 #: forms/fields.py:901
msgid "" msgid ""
"Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."
msgstr "" msgstr ""
@ -4090,56 +4088,58 @@ msgstr ""
msgid "Order" msgid "Order"
msgstr "Ordre" msgstr "Ordre"
#: forms/models.py:367 #: forms/models.py:363
#, python-format #, python-format
msgid "%(field_name)s must be unique for %(date_field)s %(lookup)s." msgid "%(field_name)s must be unique for %(date_field)s %(lookup)s."
msgstr "El camp %(field_name)s ha de ser únic per a %(lookup)s %(date_field)s." msgstr "El camp %(field_name)s ha de ser únic per a %(lookup)s %(date_field)s."
#: forms/models.py:381 forms/models.py:389 #: forms/models.py:377 forms/models.py:385
#, python-format #, python-format
msgid "%(model_name)s with this %(field_label)s already exists." msgid "%(model_name)s with this %(field_label)s already exists."
msgstr "Ja existeix %(model_name)s amb aquest %(field_label)s." msgstr "Ja existeix %(model_name)s amb aquest %(field_label)s."
#: forms/models.py:594 #: forms/models.py:590
#, python-format #, python-format
msgid "Please correct the duplicate data for %(field)s." msgid "Please correct the duplicate data for %(field)s."
msgstr "Si us plau, corregiu la dada duplicada per a %(field)s." msgstr "Si us plau, corregiu la dada duplicada per a %(field)s."
#: forms/models.py:598 #: forms/models.py:594
#, python-format #, python-format
msgid "Please correct the duplicate data for %(field)s, which must be unique." msgid "Please correct the duplicate data for %(field)s, which must be unique."
msgstr "Si us plau, corregiu la dada duplicada per a %(field)s, la qual ha de ser única." msgstr ""
"Si us plau, corregiu la dada duplicada per a %(field)s, la qual ha de ser "
"única."
#: forms/models.py:604 #: forms/models.py:600
#, python-format #, python-format
msgid "" msgid ""
"Please correct the duplicate data for %(field_name)s which must be unique " "Please correct the duplicate data for %(field_name)s which must be unique "
"for the %(lookup)s in %(date_field)s." "for the %(lookup)s in %(date_field)s."
msgstr "" msgstr ""
"Si us plau, corregiu la dada duplicada per a %(field_name)s, " "Si us plau, corregiu la dada duplicada per a %(field_name)s, la qual ha de "
"la qual ha de ser única per a la cerca %(lookup)s en %(date_field)s." "ser única per a la cerca %(lookup)s en %(date_field)s."
#: forms/models.py:612 #: forms/models.py:608
msgid "Please correct the duplicate values below." msgid "Please correct the duplicate values below."
msgstr "Si us plau, corregiu els valors duplicats a baix." msgstr "Si us plau, corregiu els valors duplicats a baix."
#: forms/models.py:867 #: forms/models.py:863
msgid "The inline foreign key did not match the parent instance primary key." msgid "The inline foreign key did not match the parent instance primary key."
msgstr "" msgstr ""
"La clau forànea en línea no coincideix amb la clau primària de la instància " "La clau forànea en línea no coincideix amb la clau primària de la instància "
"del pare" "del pare"
#: forms/models.py:930 #: forms/models.py:926
msgid "Select a valid choice. That choice is not one of the available choices." msgid "Select a valid choice. That choice is not one of the available choices."
msgstr "" msgstr ""
"Escolli una opció vàlida; Aquesta opció no és una de les opcions disponibles." "Escolli una opció vàlida; Aquesta opció no és una de les opcions disponibles."
#: forms/models.py:1004 #: forms/models.py:1000
#, python-format #, python-format
msgid "Select a valid choice. %s is not one of the available choices." msgid "Select a valid choice. %s is not one of the available choices."
msgstr "Escolliu una opció vàlida; %s' no és una de les opcions vàlides." msgstr "Escolliu una opció vàlida; %s' no és una de les opcions vàlides."
#: forms/models.py:1006 #: forms/models.py:1002
#, python-format #, python-format
msgid "\"%s\" is not a valid value for a primary key." msgid "\"%s\" is not a valid value for a primary key."
msgstr "\"%s\" no és un valor vàlid per a una clau primària." msgstr "\"%s\" no és un valor vàlid per a una clau primària."
@ -4459,6 +4459,30 @@ msgstr "El/la %(verbose_name)s s'ha actualtzat amb èxit."
msgid "The %(verbose_name)s was deleted." msgid "The %(verbose_name)s was deleted."
msgstr "El %(verbose_name)s s'ha eliminat." msgstr "El %(verbose_name)s s'ha eliminat."
#~ msgid "Comment moderation queue"
#~ msgstr "Cua de moderació de comentaris"
#~ msgid "No comments to moderate"
#~ msgstr "No hi ha comentaris per a moderar"
#~ msgid "Email"
#~ msgstr "Correu electrònic"
#~ msgid "Authenticated?"
#~ msgstr "Autentificat?"
#~ msgid "IP Address"
#~ msgstr "Adreça IP"
#~ msgid "Date posted"
#~ msgstr "Data d'enviament"
#~ msgid "yes"
#~ msgstr "si"
#~ msgid "no"
#~ msgstr "no"
#, fuzzy #, fuzzy
#~ msgid "verbose_name" #~ msgid "verbose_name"
#~ msgid_plural "verbose_name_plural" #~ msgid_plural "verbose_name_plural"

View File

@ -5,8 +5,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Django\n" "Project-Id-Version: Django\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2009-07-07 15:15+0200\n" "POT-Creation-Date: 2009-11-30 11:27+0100\n"
"PO-Revision-Date: 2009-07-07 15:22+0200\n" "PO-Revision-Date: 2009-11-30 11:31+0100\n"
"Last-Translator: Django Spanish Team <django-cat@googlegroups.com>Language-" "Last-Translator: Django Spanish Team <django-cat@googlegroups.com>Language-"
"Team: Django Spanish Team <django-cat@googlegroups.com>MIME-Version: 1.0\n" "Team: Django Spanish Team <django-cat@googlegroups.com>MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
@ -222,7 +222,7 @@ msgstr "chino tradicional"
msgid "Successfully deleted %(count)d %(items)s." msgid "Successfully deleted %(count)d %(items)s."
msgstr "Eliminado/s %(count)d %(items)s satisfactoriamente." msgstr "Eliminado/s %(count)d %(items)s satisfactoriamente."
#: contrib/admin/actions.py:67 contrib/admin/options.py:1025 #: contrib/admin/actions.py:67 contrib/admin/options.py:1033
msgid "Are you sure?" msgid "Are you sure?"
msgstr "¿Está seguro?" msgstr "¿Está seguro?"
@ -265,15 +265,15 @@ msgstr "Este mes"
msgid "This year" msgid "This year"
msgstr "Este año" msgstr "Este año"
#: contrib/admin/filterspecs.py:147 forms/widgets.py:434 #: contrib/admin/filterspecs.py:147 forms/widgets.py:435
msgid "Yes" msgid "Yes"
msgstr "Sí" msgstr "Sí"
#: contrib/admin/filterspecs.py:147 forms/widgets.py:434 #: contrib/admin/filterspecs.py:147 forms/widgets.py:435
msgid "No" msgid "No"
msgstr "No" msgstr "No"
#: contrib/admin/filterspecs.py:154 forms/widgets.py:434 #: contrib/admin/filterspecs.py:154 forms/widgets.py:435
msgid "Unknown" msgid "Unknown"
msgstr "Desconocido" msgstr "Desconocido"
@ -309,104 +309,104 @@ msgstr "entrada de registro"
msgid "log entries" msgid "log entries"
msgstr "entradas de registro" msgstr "entradas de registro"
#: contrib/admin/options.py:133 contrib/admin/options.py:147 #: contrib/admin/options.py:134 contrib/admin/options.py:148
msgid "None" msgid "None"
msgstr "Ninguno" msgstr "Ninguno"
#: contrib/admin/options.py:519 #: contrib/admin/options.py:521
#, python-format #, python-format
msgid "Changed %s." msgid "Changed %s."
msgstr "Modificado/a %s." msgstr "Modificado/a %s."
#: contrib/admin/options.py:519 contrib/admin/options.py:529 #: contrib/admin/options.py:521 contrib/admin/options.py:531
#: contrib/comments/templates/comments/preview.html:16 forms/models.py:388 #: contrib/comments/templates/comments/preview.html:16 forms/models.py:384
#: forms/models.py:600 #: forms/models.py:596
msgid "and" msgid "and"
msgstr "y" msgstr "y"
#: contrib/admin/options.py:524 #: contrib/admin/options.py:526
#, python-format #, python-format
msgid "Added %(name)s \"%(object)s\"." msgid "Added %(name)s \"%(object)s\"."
msgstr "Añadido/a \"%(object)s\" %(name)s." msgstr "Añadido/a \"%(object)s\" %(name)s."
#: contrib/admin/options.py:528 #: contrib/admin/options.py:530
#, python-format #, python-format
msgid "Changed %(list)s for %(name)s \"%(object)s\"." msgid "Changed %(list)s for %(name)s \"%(object)s\"."
msgstr "Modificados %(list)s para \"%(object)s\" %(name)s." msgstr "Modificados %(list)s para \"%(object)s\" %(name)s."
#: contrib/admin/options.py:533 #: contrib/admin/options.py:535
#, python-format #, python-format
msgid "Deleted %(name)s \"%(object)s\"." msgid "Deleted %(name)s \"%(object)s\"."
msgstr "Eliminado/a \"%(object)s\" %(name)s." msgstr "Eliminado/a \"%(object)s\" %(name)s."
#: contrib/admin/options.py:537 #: contrib/admin/options.py:539
msgid "No fields changed." msgid "No fields changed."
msgstr "No ha cambiado ningún campo." msgstr "No ha cambiado ningún campo."
#: contrib/admin/options.py:598 contrib/auth/admin.py:67 #: contrib/admin/options.py:601 contrib/auth/admin.py:67
#, python-format #, python-format
msgid "The %(name)s \"%(obj)s\" was added successfully." msgid "The %(name)s \"%(obj)s\" was added successfully."
msgstr "Se añadió con éxito el %(name)s \"%(obj)s\"." msgstr "Se añadió con éxito el %(name)s \"%(obj)s\"."
#: contrib/admin/options.py:602 contrib/admin/options.py:635 #: contrib/admin/options.py:605 contrib/admin/options.py:638
#: contrib/auth/admin.py:75 #: contrib/auth/admin.py:75
msgid "You may edit it again below." msgid "You may edit it again below."
msgstr "Puede editarlo de nuevo abajo." msgstr "Puede editarlo de nuevo abajo."
#: contrib/admin/options.py:612 contrib/admin/options.py:645 #: contrib/admin/options.py:615 contrib/admin/options.py:648
#, python-format #, python-format
msgid "You may add another %s below." msgid "You may add another %s below."
msgstr "Puede añadir otro %s abajo." msgstr "Puede añadir otro %s abajo."
#: contrib/admin/options.py:633 #: contrib/admin/options.py:636
#, python-format #, python-format
msgid "The %(name)s \"%(obj)s\" was changed successfully." msgid "The %(name)s \"%(obj)s\" was changed successfully."
msgstr "Se modificó con éxito el %(name)s \"%(obj)s\"." msgstr "Se modificó con éxito el %(name)s \"%(obj)s\"."
#: contrib/admin/options.py:641 #: contrib/admin/options.py:644
#, python-format #, python-format
msgid "" msgid ""
"The %(name)s \"%(obj)s\" was added successfully. You may edit it again below." "The %(name)s \"%(obj)s\" was added successfully. You may edit it again below."
msgstr "" msgstr ""
"Se añadió con éxito el %(name)s \"%(obj)s. Puede editarlo de nuevo abajo." "Se añadió con éxito el %(name)s \"%(obj)s. Puede editarlo de nuevo abajo."
#: contrib/admin/options.py:772 #: contrib/admin/options.py:777
#, python-format #, python-format
msgid "Add %s" msgid "Add %s"
msgstr "Añadir %s" msgstr "Añadir %s"
#: contrib/admin/options.py:803 contrib/admin/options.py:1003 #: contrib/admin/options.py:809 contrib/admin/options.py:1011
#, python-format #, python-format
msgid "%(name)s object with primary key %(key)r does not exist." msgid "%(name)s object with primary key %(key)r does not exist."
msgstr "No existe ningún objeto %(name)s con la clave primaria %(key)r." msgstr "No existe ningún objeto %(name)s con la clave primaria %(key)r."
#: contrib/admin/options.py:860 #: contrib/admin/options.py:866
#, python-format #, python-format
msgid "Change %s" msgid "Change %s"
msgstr "Modificar %s" msgstr "Modificar %s"
#: contrib/admin/options.py:904 #: contrib/admin/options.py:910
msgid "Database error" msgid "Database error"
msgstr "Error en la base de datos" msgstr "Error en la base de datos"
#: contrib/admin/options.py:940 #: contrib/admin/options.py:946
#, python-format #, python-format
msgid "%(count)s %(name)s was changed successfully." msgid "%(count)s %(name)s was changed successfully."
msgid_plural "%(count)s %(name)s were changed successfully." msgid_plural "%(count)s %(name)s were changed successfully."
msgstr[0] "%(count)s %(name)s fué modificado con éxito." msgstr[0] "%(count)s %(name)s fué modificado con éxito."
msgstr[1] "%(count)s %(name)s fueron modificados con éxito." msgstr[1] "%(count)s %(name)s fueron modificados con éxito."
#: contrib/admin/options.py:1018 #: contrib/admin/options.py:1026
#, python-format #, python-format
msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgid "The %(name)s \"%(obj)s\" was deleted successfully."
msgstr "Se eliminó con éxito el %(name)s \"%(obj)s\"." msgstr "Se eliminó con éxito el %(name)s \"%(obj)s\"."
#: contrib/admin/options.py:1054 #: contrib/admin/options.py:1063
#, python-format #, python-format
msgid "Change history: %s" msgid "Change history: %s"
msgstr "Histórico de modificaciones: %s" msgstr "Histórico de modificaciones: %s"
#: contrib/admin/sites.py:20 contrib/admin/views/decorators.py:14 #: contrib/admin/sites.py:22 contrib/admin/views/decorators.py:14
#: contrib/auth/forms.py:80 #: contrib/auth/forms.py:80
msgid "" msgid ""
"Please enter a correct username and password. Note that both fields are case-" "Please enter a correct username and password. Note that both fields are case-"
@ -415,11 +415,11 @@ msgstr ""
"Por favor, introduzca un nombre de usuario y contraseña correctos. Note que " "Por favor, introduzca un nombre de usuario y contraseña correctos. Note que "
"ambos campos son sensibles a mayúsculas/minúsculas." "ambos campos son sensibles a mayúsculas/minúsculas."
#: contrib/admin/sites.py:278 contrib/admin/views/decorators.py:40 #: contrib/admin/sites.py:292 contrib/admin/views/decorators.py:40
msgid "Please log in again, because your session has expired." msgid "Please log in again, because your session has expired."
msgstr "Por favor, inicie sesión de nuevo, ya que su sesión ha caducado." msgstr "Por favor, inicie sesión de nuevo, ya que su sesión ha caducado."
#: contrib/admin/sites.py:285 contrib/admin/views/decorators.py:47 #: contrib/admin/sites.py:299 contrib/admin/views/decorators.py:47
msgid "" msgid ""
"Looks like your browser isn't configured to accept cookies. Please enable " "Looks like your browser isn't configured to accept cookies. Please enable "
"cookies, reload this page, and try again." "cookies, reload this page, and try again."
@ -427,29 +427,29 @@ msgstr ""
"Parece que su navegador no está configurado para aceptar cookies. " "Parece que su navegador no está configurado para aceptar cookies. "
"Actívelas , recargue esta página, e inténtelo de nuevo." "Actívelas , recargue esta página, e inténtelo de nuevo."
#: contrib/admin/sites.py:301 contrib/admin/sites.py:307 #: contrib/admin/sites.py:315 contrib/admin/sites.py:321
#: contrib/admin/views/decorators.py:66 #: contrib/admin/views/decorators.py:66
msgid "Usernames cannot contain the '@' character." msgid "Usernames cannot contain the '@' character."
msgstr "Los nombres de usuario no pueden contener el carácter '@'." msgstr "Los nombres de usuario no pueden contener el carácter '@'."
#: contrib/admin/sites.py:304 contrib/admin/views/decorators.py:62 #: contrib/admin/sites.py:318 contrib/admin/views/decorators.py:62
#, python-format #, python-format
msgid "Your e-mail address is not your username. Try '%s' instead." msgid "Your e-mail address is not your username. Try '%s' instead."
msgstr "" msgstr ""
"Su dirección de correo no es su nombre de usuario. Pruebe con '%s' en su " "Su dirección de correo no es su nombre de usuario. Pruebe con '%s' en su "
"lugar." "lugar."
#: contrib/admin/sites.py:360 #: contrib/admin/sites.py:374
msgid "Site administration" msgid "Site administration"
msgstr "Sitio administrativo" msgstr "Sitio administrativo"
#: contrib/admin/sites.py:373 contrib/admin/templates/admin/login.html:26 #: contrib/admin/sites.py:388 contrib/admin/templates/admin/login.html:26
#: contrib/admin/templates/registration/password_reset_complete.html:14 #: contrib/admin/templates/registration/password_reset_complete.html:14
#: contrib/admin/views/decorators.py:20 #: contrib/admin/views/decorators.py:20
msgid "Log in" msgid "Log in"
msgstr "Iniciar sesión" msgstr "Iniciar sesión"
#: contrib/admin/sites.py:417 #: contrib/admin/sites.py:433
#, python-format #, python-format
msgid "%s administration" msgid "%s administration"
msgstr "Administración de %s" msgstr "Administración de %s"
@ -464,27 +464,27 @@ msgstr "Uno o más %(fieldname)s en %(name)s: %(obj)s"
msgid "One or more %(fieldname)s in %(name)s:" msgid "One or more %(fieldname)s in %(name)s:"
msgstr "Uno o más %(fieldname)s en %(name)s:" msgstr "Uno o más %(fieldname)s en %(name)s:"
#: contrib/admin/widgets.py:71 #: contrib/admin/widgets.py:72
msgid "Date:" msgid "Date:"
msgstr "Fecha:" msgstr "Fecha:"
#: contrib/admin/widgets.py:71 #: contrib/admin/widgets.py:72
msgid "Time:" msgid "Time:"
msgstr "Hora:" msgstr "Hora:"
#: contrib/admin/widgets.py:95 #: contrib/admin/widgets.py:96
msgid "Currently:" msgid "Currently:"
msgstr "Actualmente:" msgstr "Actualmente:"
#: contrib/admin/widgets.py:95 #: contrib/admin/widgets.py:96
msgid "Change:" msgid "Change:"
msgstr "Modificar:" msgstr "Modificar:"
#: contrib/admin/widgets.py:124 #: contrib/admin/widgets.py:125
msgid "Lookup" msgid "Lookup"
msgstr "Buscar" msgstr "Buscar"
#: contrib/admin/widgets.py:236 #: contrib/admin/widgets.py:237
msgid "Add Another" msgid "Add Another"
msgstr "Añadir otro" msgstr "Añadir otro"
@ -499,7 +499,7 @@ msgstr "Lo sentimos, pero no se encuentra la página solicitada."
#: contrib/admin/templates/admin/500.html:4 #: contrib/admin/templates/admin/500.html:4
#: contrib/admin/templates/admin/app_index.html:8 #: contrib/admin/templates/admin/app_index.html:8
#: contrib/admin/templates/admin/base.html:31 #: contrib/admin/templates/admin/base.html:54
#: contrib/admin/templates/admin/change_form.html:17 #: contrib/admin/templates/admin/change_form.html:17
#: contrib/admin/templates/admin/change_list.html:25 #: contrib/admin/templates/admin/change_list.html:25
#: contrib/admin/templates/admin/delete_confirmation.html:6 #: contrib/admin/templates/admin/delete_confirmation.html:6
@ -553,18 +553,18 @@ msgstr "Ir"
msgid "%(name)s" msgid "%(name)s"
msgstr "%(name)s" msgstr "%(name)s"
#: contrib/admin/templates/admin/base.html:26 #: contrib/admin/templates/admin/base.html:27
msgid "Welcome," msgid "Welcome,"
msgstr "Bienvenido/a," msgstr "Bienvenido/a,"
#: contrib/admin/templates/admin/base.html:26 #: contrib/admin/templates/admin/base.html:32
#: contrib/admin/templates/registration/password_change_done.html:3 #: contrib/admin/templates/registration/password_change_done.html:3
#: contrib/admin/templates/registration/password_change_form.html:3 #: contrib/admin/templates/registration/password_change_form.html:3
#: contrib/admindocs/templates/admin_doc/bookmarklets.html:3 #: contrib/admindocs/templates/admin_doc/bookmarklets.html:3
msgid "Documentation" msgid "Documentation"
msgstr "Documentación" msgstr "Documentación"
#: contrib/admin/templates/admin/base.html:26 #: contrib/admin/templates/admin/base.html:40
#: contrib/admin/templates/admin/auth/user/change_password.html:14 #: contrib/admin/templates/admin/auth/user/change_password.html:14
#: contrib/admin/templates/admin/auth/user/change_password.html:47 #: contrib/admin/templates/admin/auth/user/change_password.html:47
#: contrib/admin/templates/registration/password_change_done.html:3 #: contrib/admin/templates/registration/password_change_done.html:3
@ -572,7 +572,7 @@ msgstr "Documentación"
msgid "Change password" msgid "Change password"
msgstr "Cambiar contraseña" msgstr "Cambiar contraseña"
#: contrib/admin/templates/admin/base.html:26 #: contrib/admin/templates/admin/base.html:47
#: contrib/admin/templates/registration/password_change_done.html:3 #: contrib/admin/templates/registration/password_change_done.html:3
#: contrib/admin/templates/registration/password_change_form.html:3 #: contrib/admin/templates/registration/password_change_form.html:3
msgid "Log out" msgid "Log out"
@ -598,7 +598,7 @@ msgstr "Histórico"
#: contrib/admin/templates/admin/change_form.html:28 #: contrib/admin/templates/admin/change_form.html:28
#: contrib/admin/templates/admin/edit_inline/stacked.html:13 #: contrib/admin/templates/admin/edit_inline/stacked.html:13
#: contrib/admin/templates/admin/edit_inline/tabular.html:27 #: contrib/admin/templates/admin/edit_inline/tabular.html:28
msgid "View on site" msgid "View on site"
msgstr "Ver en el sitio" msgstr "Ver en el sitio"
@ -668,9 +668,9 @@ msgstr ""
#, python-format #, python-format
msgid "" msgid ""
"Are you sure you want to delete the selected %(object_name)s objects? All of " "Are you sure you want to delete the selected %(object_name)s objects? All of "
"the following objects and it's related items will be deleted:" "the following objects and their related items will be deleted:"
msgstr "" msgstr ""
"¿Está seguro de que quiere borrar los %(object_name)s? Los siguientes " "¿Está seguro de que quiere eliminar los %(object_name)s seleccionados? Los siguientes "
"objetos y sus elementos relacionados serán eliminados:" "objetos y sus elementos relacionados serán eliminados:"
#: contrib/admin/templates/admin/filter.html:2 #: contrib/admin/templates/admin/filter.html:2
@ -734,7 +734,6 @@ msgid "User"
msgstr "Usuario" msgstr "Usuario"
#: contrib/admin/templates/admin/object_history.html:24 #: contrib/admin/templates/admin/object_history.html:24
#: contrib/comments/templates/comments/moderation_queue.html:33
msgid "Action" msgid "Action"
msgstr "Acción" msgstr "Acción"
@ -987,7 +986,7 @@ msgstr "Dirección de correo electrónico:"
msgid "Reset my password" msgid "Reset my password"
msgstr "Restablecer mi contraseña" msgstr "Restablecer mi contraseña"
#: contrib/admin/templatetags/admin_list.py:299 #: contrib/admin/templatetags/admin_list.py:304
msgid "All dates" msgid "All dates"
msgstr "Todas las fechas" msgstr "Todas las fechas"
@ -1009,146 +1008,145 @@ msgstr "sitio"
msgid "template" msgid "template"
msgstr "plantilla" msgstr "plantilla"
#: contrib/admindocs/views.py:58 contrib/admindocs/views.py:60 #: contrib/admindocs/views.py:61 contrib/admindocs/views.py:63
#: contrib/admindocs/views.py:62 #: contrib/admindocs/views.py:65
msgid "tag:" msgid "tag:"
msgstr "etiqueta:" msgstr "etiqueta:"
#: contrib/admindocs/views.py:91 contrib/admindocs/views.py:93 #: contrib/admindocs/views.py:94 contrib/admindocs/views.py:96
#: contrib/admindocs/views.py:95 #: contrib/admindocs/views.py:98
msgid "filter:" msgid "filter:"
msgstr "filtro:" msgstr "filtro:"
#: contrib/admindocs/views.py:155 contrib/admindocs/views.py:157 #: contrib/admindocs/views.py:158 contrib/admindocs/views.py:160
#: contrib/admindocs/views.py:159 #: contrib/admindocs/views.py:162
msgid "view:" msgid "view:"
msgstr "vista:" msgstr "vista:"
#: contrib/admindocs/views.py:187 #: contrib/admindocs/views.py:190
#, python-format #, python-format
msgid "App %r not found" msgid "App %r not found"
msgstr "Aplicación %r no encontrada" msgstr "Aplicación %r no encontrada"
#: contrib/admindocs/views.py:194 #: contrib/admindocs/views.py:197
#, python-format #, python-format
msgid "Model %(model_name)r not found in app %(app_label)r" msgid "Model %(model_name)r not found in app %(app_label)r"
msgstr "" msgstr ""
"El modelo %(model_name)r no se ha encontrado en la aplicación %(app_label)r" "El modelo %(model_name)r no se ha encontrado en la aplicación %(app_label)r"
#: contrib/admindocs/views.py:206 #: contrib/admindocs/views.py:209
#, python-format #, python-format
msgid "the related `%(app_label)s.%(data_type)s` object" msgid "the related `%(app_label)s.%(data_type)s` object"
msgstr "el objeto relacionado `%(app_label)s.%(data_type)s`" msgstr "el objeto relacionado `%(app_label)s.%(data_type)s`"
#: contrib/admindocs/views.py:206 contrib/admindocs/views.py:225 #: contrib/admindocs/views.py:209 contrib/admindocs/views.py:228
#: contrib/admindocs/views.py:230 contrib/admindocs/views.py:244 #: contrib/admindocs/views.py:233 contrib/admindocs/views.py:247
#: contrib/admindocs/views.py:258 contrib/admindocs/views.py:263 #: contrib/admindocs/views.py:261 contrib/admindocs/views.py:266
msgid "model:" msgid "model:"
msgstr "modelo:" msgstr "modelo:"
#: contrib/admindocs/views.py:221 contrib/admindocs/views.py:253 #: contrib/admindocs/views.py:224 contrib/admindocs/views.py:256
#, python-format #, python-format
msgid "related `%(app_label)s.%(object_name)s` objects" msgid "related `%(app_label)s.%(object_name)s` objects"
msgstr "los objetos relacionados `%(app_label)s.%(object_name)s`" msgstr "los objetos relacionados `%(app_label)s.%(object_name)s`"
#: contrib/admindocs/views.py:225 contrib/admindocs/views.py:258 #: contrib/admindocs/views.py:228 contrib/admindocs/views.py:261
#, python-format #, python-format
msgid "all %s" msgid "all %s"
msgstr "todo %s" msgstr "todo %s"
#: contrib/admindocs/views.py:230 contrib/admindocs/views.py:263 #: contrib/admindocs/views.py:233 contrib/admindocs/views.py:266
#, python-format #, python-format
msgid "number of %s" msgid "number of %s"
msgstr "número de %s" msgstr "número de %s"
#: contrib/admindocs/views.py:268 #: contrib/admindocs/views.py:271
#, python-format #, python-format
msgid "Fields on %s objects" msgid "Fields on %s objects"
msgstr "Campos en %s objetos" msgstr "Campos en %s objetos"
#: contrib/admindocs/views.py:331 contrib/admindocs/views.py:342 #: contrib/admindocs/views.py:334 contrib/admindocs/views.py:345
#: contrib/admindocs/views.py:344 contrib/admindocs/views.py:350 #: contrib/admindocs/views.py:347 contrib/admindocs/views.py:353
#: contrib/admindocs/views.py:351 contrib/admindocs/views.py:353 #: contrib/admindocs/views.py:354 contrib/admindocs/views.py:356
msgid "Integer" msgid "Integer"
msgstr "Entero" msgstr "Entero"
#: contrib/admindocs/views.py:332 #: contrib/admindocs/views.py:335
msgid "Boolean (Either True or False)" msgid "Boolean (Either True or False)"
msgstr "Booleano (Verdadero o Falso)" msgstr "Booleano (Verdadero o Falso)"
#: contrib/admindocs/views.py:333 contrib/admindocs/views.py:352 #: contrib/admindocs/views.py:336 contrib/admindocs/views.py:355
#, python-format #, python-format
msgid "String (up to %(max_length)s)" msgid "String (up to %(max_length)s)"
msgstr "Cadena (máximo %(max_length)s)" msgstr "Cadena (máximo %(max_length)s)"
#: contrib/admindocs/views.py:334 #: contrib/admindocs/views.py:337
msgid "Comma-separated integers" msgid "Comma-separated integers"
msgstr "Enteros separados por comas" msgstr "Enteros separados por comas"
#: contrib/admindocs/views.py:335 #: contrib/admindocs/views.py:338
msgid "Date (without time)" msgid "Date (without time)"
msgstr "Fecha (sin hora)" msgstr "Fecha (sin hora)"
#: contrib/admindocs/views.py:336 #: contrib/admindocs/views.py:339
msgid "Date (with time)" msgid "Date (with time)"
msgstr "Fecha (con hora)" msgstr "Fecha (con hora)"
#: contrib/admindocs/views.py:337 #: contrib/admindocs/views.py:340
msgid "Decimal number" msgid "Decimal number"
msgstr "Número decimal" msgstr "Número decimal"
#: contrib/admindocs/views.py:338 #: contrib/admindocs/views.py:341
msgid "E-mail address" msgid "E-mail address"
msgstr "Dirección de correo electrónico" msgstr "Dirección de correo electrónico"
#: contrib/admindocs/views.py:339 contrib/admindocs/views.py:340 #: contrib/admindocs/views.py:342 contrib/admindocs/views.py:343
#: contrib/admindocs/views.py:343 #: contrib/admindocs/views.py:346
msgid "File path" msgid "File path"
msgstr "Ruta de fichero" msgstr "Ruta de fichero"
#: contrib/admindocs/views.py:341 #: contrib/admindocs/views.py:344
msgid "Floating point number" msgid "Floating point number"
msgstr "Número en coma flotante" msgstr "Número en coma flotante"
#: contrib/admindocs/views.py:345 contrib/comments/models.py:60 #: contrib/admindocs/views.py:348 contrib/comments/models.py:60
msgid "IP address" msgid "IP address"
msgstr "Dirección IP" msgstr "Dirección IP"
#: contrib/admindocs/views.py:347 #: contrib/admindocs/views.py:350
msgid "Boolean (Either True, False or None)" msgid "Boolean (Either True, False or None)"
msgstr "Booleano (Verdadero, Falso o Nulo)" msgstr "Booleano (Verdadero, Falso o Nulo)"
#: contrib/admindocs/views.py:348 #: contrib/admindocs/views.py:351
msgid "Relation to parent model" msgid "Relation to parent model"
msgstr "Relación con el modelo padre" msgstr "Relación con el modelo padre"
#: contrib/admindocs/views.py:349 #: contrib/admindocs/views.py:352
msgid "Phone number" msgid "Phone number"
msgstr "Número de teléfono" msgstr "Número de teléfono"
#: contrib/admindocs/views.py:354 #: contrib/admindocs/views.py:357
msgid "Text" msgid "Text"
msgstr "Texto" msgstr "Texto"
#: contrib/admindocs/views.py:355 #: contrib/admindocs/views.py:358
msgid "Time" msgid "Time"
msgstr "Hora" msgstr "Hora"
#: contrib/admindocs/views.py:356 contrib/comments/forms.py:95 #: contrib/admindocs/views.py:359 contrib/comments/forms.py:95
#: contrib/comments/templates/comments/moderation_queue.html:37
#: contrib/flatpages/admin.py:8 contrib/flatpages/models.py:7 #: contrib/flatpages/admin.py:8 contrib/flatpages/models.py:7
msgid "URL" msgid "URL"
msgstr "URL" msgstr "URL"
#: contrib/admindocs/views.py:357 #: contrib/admindocs/views.py:360
msgid "U.S. state (two uppercase letters)" msgid "U.S. state (two uppercase letters)"
msgstr "Estado de los EEUU (dos letras mayúsculas)" msgstr "Estado de los EEUU (dos letras mayúsculas)"
#: contrib/admindocs/views.py:358 #: contrib/admindocs/views.py:361
msgid "XML text" msgid "XML text"
msgstr "Texto XML" msgstr "Texto XML"
#: contrib/admindocs/views.py:384 #: contrib/admindocs/views.py:387
#, python-format #, python-format
msgid "%s does not appear to be a urlpattern object" msgid "%s does not appear to be a urlpattern object"
msgstr "%s no parece ser un objeto urlpattern" msgstr "%s no parece ser un objeto urlpattern"
@ -1441,22 +1439,53 @@ msgstr "usuarios"
msgid "message" msgid "message"
msgstr "mensaje" msgstr "mensaje"
#: contrib/auth/views.py:56 #: contrib/auth/views.py:60
msgid "Logged out" msgid "Logged out"
msgstr "Sesión terminada" msgstr "Sesión terminada"
#: contrib/auth/management/commands/createsuperuser.py:23 forms/fields.py:429 #: contrib/auth/management/commands/createsuperuser.py:23 forms/fields.py:428
msgid "Enter a valid e-mail address." msgid "Enter a valid e-mail address."
msgstr "Introduzca una dirección de correo electrónico válida." msgstr "Introduzca una dirección de correo electrónico válida."
#: contrib/comments/admin.py:11 #: contrib/comments/admin.py:12
msgid "Content" msgid "Content"
msgstr "contenido" msgstr "contenido"
#: contrib/comments/admin.py:14 #: contrib/comments/admin.py:15
msgid "Metadata" msgid "Metadata"
msgstr "metadatos" msgstr "metadatos"
#: contrib/comments/admin.py:39
msgid "flagged"
msgstr "marcado"
#: contrib/comments/admin.py:40
msgid "Flag selected comments"
msgstr "Marcar los comentarios seleccionados"
#: contrib/comments/admin.py:43
msgid "approved"
msgstr "aprobado"
#: contrib/comments/admin.py:44
msgid "Approve selected comments"
msgstr "aprobar los comentarios seleccionados"
#: contrib/comments/admin.py:47
msgid "removed"
msgstr "eliminado"
#: contrib/comments/admin.py:48
msgid "Remove selected comments"
msgstr "Eliminar los comentarios seleccionados"
#: contrib/comments/admin.py:60
#, python-format
msgid "1 comment was successfully %(action)s."
msgid_plural "%(count)s comments were successfully %(action)s."
msgstr[0] "1 comentarios ha sido %(action)s satisfactoriamente."
msgstr[1] "%(count)s comentarios han sido %(action)s satisfactoriamente."
#: contrib/comments/feeds.py:13 #: contrib/comments/feeds.py:13
#, python-format #, python-format
msgid "%(site_name)s comments" msgid "%(site_name)s comments"
@ -1468,7 +1497,6 @@ msgid "Latest comments on %(site_name)s"
msgstr "Últimos comentarios en %(site_name)s" msgstr "Últimos comentarios en %(site_name)s"
#: contrib/comments/forms.py:93 #: contrib/comments/forms.py:93
#: contrib/comments/templates/comments/moderation_queue.html:34
msgid "Name" msgid "Name"
msgstr "Nombre" msgstr "Nombre"
@ -1477,7 +1505,6 @@ msgid "Email address"
msgstr "dirección de correo electrónico" msgstr "dirección de correo electrónico"
#: contrib/comments/forms.py:96 #: contrib/comments/forms.py:96
#: contrib/comments/templates/comments/moderation_queue.html:35
msgid "Comment" msgid "Comment"
msgstr "Comentario" msgstr "Comentario"
@ -1598,14 +1625,13 @@ msgstr "marcas de comentario"
#: contrib/comments/templates/comments/approve.html:4 #: contrib/comments/templates/comments/approve.html:4
msgid "Approve a comment" msgid "Approve a comment"
msgstr "Aprovar un comentario" msgstr "Aprobar un comentario"
#: contrib/comments/templates/comments/approve.html:7 #: contrib/comments/templates/comments/approve.html:7
msgid "Really make this comment public?" msgid "Really make this comment public?"
msgstr "Realmente desea hacer este comentario público?" msgstr "Realmente desea hacer este comentario público?"
#: contrib/comments/templates/comments/approve.html:12 #: contrib/comments/templates/comments/approve.html:12
#: contrib/comments/templates/comments/moderation_queue.html:49
msgid "Approve" msgid "Approve"
msgstr "Aprobar" msgstr "Aprobar"
@ -1631,7 +1657,6 @@ msgid "Really remove this comment?"
msgstr "¿Realmente desea eliminar este comentario?" msgstr "¿Realmente desea eliminar este comentario?"
#: contrib/comments/templates/comments/delete.html:12 #: contrib/comments/templates/comments/delete.html:12
#: contrib/comments/templates/comments/moderation_queue.html:53
msgid "Remove" msgid "Remove"
msgstr "Eliminar" msgstr "Eliminar"
@ -1665,39 +1690,6 @@ msgstr "Enviar"
msgid "Preview" msgid "Preview"
msgstr "Previsualizar" msgstr "Previsualizar"
#: contrib/comments/templates/comments/moderation_queue.html:4
#: contrib/comments/templates/comments/moderation_queue.html:19
msgid "Comment moderation queue"
msgstr "Cola de moderación de comentarios"
#: contrib/comments/templates/comments/moderation_queue.html:26
msgid "No comments to moderate"
msgstr "No hay comentarios por moderar"
#: contrib/comments/templates/comments/moderation_queue.html:36
msgid "Email"
msgstr "Correo electrónico"
#: contrib/comments/templates/comments/moderation_queue.html:38
msgid "Authenticated?"
msgstr "¿Autentificado?"
#: contrib/comments/templates/comments/moderation_queue.html:39
msgid "IP Address"
msgstr "Dirección IP"
#: contrib/comments/templates/comments/moderation_queue.html:40
msgid "Date posted"
msgstr "Fecha de envío"
#: contrib/comments/templates/comments/moderation_queue.html:63
msgid "yes"
msgstr "sí"
#: contrib/comments/templates/comments/moderation_queue.html:63
msgid "no"
msgstr "no"
#: contrib/comments/templates/comments/posted.html:4 #: contrib/comments/templates/comments/posted.html:4
msgid "Thanks for commenting" msgid "Thanks for commenting"
msgstr "Gracias por comentar" msgstr "Gracias por comentar"
@ -1790,7 +1782,7 @@ msgstr "página estática"
msgid "flat pages" msgid "flat pages"
msgstr "páginas estáticas" msgstr "páginas estáticas"
#: contrib/formtools/wizard.py:130 #: contrib/formtools/wizard.py:132
msgid "" msgid ""
"We apologize, but your form has expired. Please continue filling out the " "We apologize, but your form has expired. Please continue filling out the "
"form from this page." "form from this page."
@ -1815,8 +1807,8 @@ msgid ""
"An error occurred when transforming the geometry to the SRID of the geometry " "An error occurred when transforming the geometry to the SRID of the geometry "
"form field." "form field."
msgstr "" msgstr ""
"Ocurrió un error al transformar la geometria al SRID de la geometria " "Ocurrió un error al transformar la geometria al SRID de la geometria del "
"del campo de formulario." "campo de formulario."
#: contrib/humanize/templatetags/humanize.py:19 #: contrib/humanize/templatetags/humanize.py:19
msgid "th" msgid "th"
@ -2611,6 +2603,10 @@ msgstr "El número de cuenta bancaria es incorrecto."
msgid "Enter a valid Finnish social security number." msgid "Enter a valid Finnish social security number."
msgstr "Introduzca un número de seguro social finlandés válido." msgstr "Introduzca un número de seguro social finlandés válido."
#: contrib/localflavor/fr/forms.py:30
msgid "Phone numbers must be in 0X XX XX XX XX format."
msgstr "Los números de teléfono deben tener el formato 0X XX XX XX XX."
#: contrib/localflavor/in_/forms.py:14 #: contrib/localflavor/in_/forms.py:14
msgid "Enter a zip code in the format XXXXXXX." msgid "Enter a zip code in the format XXXXXXX."
msgstr "Introduzca un código postal en el formato XXXXXXX." msgstr "Introduzca un código postal en el formato XXXXXXX."
@ -3052,7 +3048,8 @@ msgstr "El Número de Identificación Tributaria (NIP) es incorrecto."
#: contrib/localflavor/pl/forms.py:109 #: contrib/localflavor/pl/forms.py:109
msgid "National Business Register Number (REGON) consists of 9 or 14 digits." msgid "National Business Register Number (REGON) consists of 9 or 14 digits."
msgstr "" msgstr ""
"El Número Nacional de Registro de Negocios (REGON) consiste en 9 o 14 dígitos." "El Número Nacional de Registro de Negocios (REGON) consiste en 9 o 14 "
"dígitos."
#: contrib/localflavor/pl/forms.py:110 #: contrib/localflavor/pl/forms.py:110
msgid "Wrong checksum for the National Business Register Number (REGON)." msgid "Wrong checksum for the National Business Register Number (REGON)."
@ -3941,14 +3938,14 @@ msgstr "Este valor debe ser Verdadero, Falso o Ninguno."
msgid "Enter a valid time in HH:MM[:ss[.uuuuuu]] format." msgid "Enter a valid time in HH:MM[:ss[.uuuuuu]] format."
msgstr "Introduzca una hora válida en formato HH:MM[:ss[.uuuuuu]]." msgstr "Introduzca una hora válida en formato HH:MM[:ss[.uuuuuu]]."
#: db/models/fields/related.py:816 #: db/models/fields/related.py:869
msgid "" msgid ""
"Hold down \"Control\", or \"Command\" on a Mac, to select more than one." "Hold down \"Control\", or \"Command\" on a Mac, to select more than one."
msgstr "" msgstr ""
"Mantenga presionado \"Control\", o \"Command\" en un Mac, para seleccionar " "Mantenga presionado \"Control\", o \"Command\" en un Mac, para seleccionar "
"más de una opción." "más de una opción."
#: db/models/fields/related.py:894 #: db/models/fields/related.py:930
#, python-format #, python-format
msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid." msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid."
msgid_plural "" msgid_plural ""
@ -3960,88 +3957,88 @@ msgstr[1] ""
"Por favor, introduzca IDs de %(self)s válidos. Los valores %(value)r no son " "Por favor, introduzca IDs de %(self)s válidos. Los valores %(value)r no son "
"válidos." "válidos."
#: forms/fields.py:54 #: forms/fields.py:53
msgid "This field is required." msgid "This field is required."
msgstr "Este campo es obligatorio." msgstr "Este campo es obligatorio."
#: forms/fields.py:55 #: forms/fields.py:54
msgid "Enter a valid value." msgid "Enter a valid value."
msgstr "Introduzca un valor correcto." msgstr "Introduzca un valor correcto."
#: forms/fields.py:138 #: forms/fields.py:137
#, python-format #, python-format
msgid "Ensure this value has at most %(max)d characters (it has %(length)d)." msgid "Ensure this value has at most %(max)d characters (it has %(length)d)."
msgstr "" msgstr ""
"Asegúrese de que su texto tiene a lo más %(max)d caracteres (actualmente " "Asegúrese de que su texto tiene a lo más %(max)d caracteres (actualmente "
"tiene %(length)d)." "tiene %(length)d)."
#: forms/fields.py:139 #: forms/fields.py:138
#, python-format #, python-format
msgid "Ensure this value has at least %(min)d characters (it has %(length)d)." msgid "Ensure this value has at least %(min)d characters (it has %(length)d)."
msgstr "" msgstr ""
"Asegúrese de que su texto tiene al menos %(min)d caracteres (actualmente " "Asegúrese de que su texto tiene al menos %(min)d caracteres (actualmente "
"tiene %(length)d)." "tiene %(length)d)."
#: forms/fields.py:166 #: forms/fields.py:165
msgid "Enter a whole number." msgid "Enter a whole number."
msgstr "Introduzca un número entero." msgstr "Introduzca un número entero."
#: forms/fields.py:167 forms/fields.py:196 forms/fields.py:225 #: forms/fields.py:166 forms/fields.py:195 forms/fields.py:224
#, python-format #, python-format
msgid "Ensure this value is less than or equal to %s." msgid "Ensure this value is less than or equal to %s."
msgstr "Asegúrese de que este valor es menor o igual a %s." msgstr "Asegúrese de que este valor es menor o igual a %s."
#: forms/fields.py:168 forms/fields.py:197 forms/fields.py:226 #: forms/fields.py:167 forms/fields.py:196 forms/fields.py:225
#, python-format #, python-format
msgid "Ensure this value is greater than or equal to %s." msgid "Ensure this value is greater than or equal to %s."
msgstr "Asegúrese de que este valor es mayor o igual a %s." msgstr "Asegúrese de que este valor es mayor o igual a %s."
#: forms/fields.py:195 forms/fields.py:224 #: forms/fields.py:194 forms/fields.py:223
msgid "Enter a number." msgid "Enter a number."
msgstr "Introduzca un número." msgstr "Introduzca un número."
#: forms/fields.py:227 #: forms/fields.py:226
#, python-format #, python-format
msgid "Ensure that there are no more than %s digits in total." msgid "Ensure that there are no more than %s digits in total."
msgstr "Asegúrese de que no hay más de %s dígitos en total." msgstr "Asegúrese de que no hay más de %s dígitos en total."
#: forms/fields.py:228 #: forms/fields.py:227
#, python-format #, python-format
msgid "Ensure that there are no more than %s decimal places." msgid "Ensure that there are no more than %s decimal places."
msgstr "Asegúrese de que no hay más de %s decimales." msgstr "Asegúrese de que no hay más de %s decimales."
#: forms/fields.py:229 #: forms/fields.py:228
#, python-format #, python-format
msgid "Ensure that there are no more than %s digits before the decimal point." msgid "Ensure that there are no more than %s digits before the decimal point."
msgstr "Asegúrese de que no hay más de %s dígitos antes de la coma decimal." msgstr "Asegúrese de que no hay más de %s dígitos antes de la coma decimal."
#: forms/fields.py:288 forms/fields.py:863 #: forms/fields.py:287 forms/fields.py:862
msgid "Enter a valid date." msgid "Enter a valid date."
msgstr "Introduzca una fecha válida." msgstr "Introduzca una fecha válida."
#: forms/fields.py:322 forms/fields.py:864 #: forms/fields.py:321 forms/fields.py:863
msgid "Enter a valid time." msgid "Enter a valid time."
msgstr "Introduzca una hora válida." msgstr "Introduzca una hora válida."
#: forms/fields.py:361 #: forms/fields.py:360
msgid "Enter a valid date/time." msgid "Enter a valid date/time."
msgstr "Introduzca una fecha/hora válida." msgstr "Introduzca una fecha/hora válida."
#: forms/fields.py:447 #: forms/fields.py:446
msgid "No file was submitted. Check the encoding type on the form." msgid "No file was submitted. Check the encoding type on the form."
msgstr "" msgstr ""
"No se ha enviado ningún fichero. Compruebe el tipo de codificación en el " "No se ha enviado ningún fichero. Compruebe el tipo de codificación en el "
"formulario." "formulario."
#: forms/fields.py:448 #: forms/fields.py:447
msgid "No file was submitted." msgid "No file was submitted."
msgstr "No se ha enviado ningún fichero" msgstr "No se ha enviado ningún fichero"
#: forms/fields.py:449 #: forms/fields.py:448
msgid "The submitted file is empty." msgid "The submitted file is empty."
msgstr "El fichero enviado está vacío." msgstr "El fichero enviado está vacío."
#: forms/fields.py:450 #: forms/fields.py:449
#, python-format #, python-format
msgid "" msgid ""
"Ensure this filename has at most %(max)d characters (it has %(length)d)." "Ensure this filename has at most %(max)d characters (it has %(length)d)."
@ -4049,7 +4046,7 @@ msgstr ""
"Asegúrese de que su texto tiene no más de %(max)d caracteres (actualmente " "Asegúrese de que su texto tiene no más de %(max)d caracteres (actualmente "
"tiene %(length)d)." "tiene %(length)d)."
#: forms/fields.py:483 #: forms/fields.py:482
msgid "" msgid ""
"Upload a valid image. The file you uploaded was either not an image or a " "Upload a valid image. The file you uploaded was either not an image or a "
"corrupted image." "corrupted image."
@ -4057,29 +4054,29 @@ msgstr ""
"Envíe una imagen válida. El fichero que ha enviado no era una imagen o se " "Envíe una imagen válida. El fichero que ha enviado no era una imagen o se "
"trataba de una imagen corrupta." "trataba de una imagen corrupta."
#: forms/fields.py:544 #: forms/fields.py:543
msgid "Enter a valid URL." msgid "Enter a valid URL."
msgstr "Introduzca una URL válida." msgstr "Introduzca una URL válida."
#: forms/fields.py:545 #: forms/fields.py:544
msgid "This URL appears to be a broken link." msgid "This URL appears to be a broken link."
msgstr "La URL parece ser un enlace roto." msgstr "La URL parece ser un enlace roto."
#: forms/fields.py:625 forms/fields.py:703 #: forms/fields.py:624 forms/fields.py:702
#, python-format #, python-format
msgid "Select a valid choice. %(value)s is not one of the available choices." msgid "Select a valid choice. %(value)s is not one of the available choices."
msgstr "" msgstr ""
"Escoja una opción válida. %(value)s no es una de las opciones disponibles." "Escoja una opción válida. %(value)s no es una de las opciones disponibles."
#: forms/fields.py:704 forms/fields.py:765 forms/models.py:1003 #: forms/fields.py:703 forms/fields.py:764 forms/models.py:999
msgid "Enter a list of values." msgid "Enter a list of values."
msgstr "Introduzca una lista de valores." msgstr "Introduzca una lista de valores."
#: forms/fields.py:892 #: forms/fields.py:891
msgid "Enter a valid IPv4 address." msgid "Enter a valid IPv4 address."
msgstr "Introduzca una dirección IPv4 válida." msgstr "Introduzca una dirección IPv4 válida."
#: forms/fields.py:902 #: forms/fields.py:901
msgid "" msgid ""
"Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."
msgstr "" msgstr ""
@ -4090,28 +4087,28 @@ msgstr ""
msgid "Order" msgid "Order"
msgstr "Orden" msgstr "Orden"
#: forms/models.py:367 #: forms/models.py:363
#, python-format #, python-format
msgid "%(field_name)s must be unique for %(date_field)s %(lookup)s." msgid "%(field_name)s must be unique for %(date_field)s %(lookup)s."
msgstr "El campo %(field_name)s debe ser único para %(lookup)s %(date_field)s" msgstr "El campo %(field_name)s debe ser único para %(lookup)s %(date_field)s"
#: forms/models.py:381 forms/models.py:389 #: forms/models.py:377 forms/models.py:385
#, python-format #, python-format
msgid "%(model_name)s with this %(field_label)s already exists." msgid "%(model_name)s with this %(field_label)s already exists."
msgstr "Ya existe %(model_name)s con este %(field_label)s." msgstr "Ya existe %(model_name)s con este %(field_label)s."
#: forms/models.py:594 #: forms/models.py:590
#, python-format #, python-format
msgid "Please correct the duplicate data for %(field)s." msgid "Please correct the duplicate data for %(field)s."
msgstr "Por favor, corrija el dato duplicado para %(field)s." msgstr "Por favor, corrija el dato duplicado para %(field)s."
#: forms/models.py:598 #: forms/models.py:594
#, python-format #, python-format
msgid "Please correct the duplicate data for %(field)s, which must be unique." msgid "Please correct the duplicate data for %(field)s, which must be unique."
msgstr "" msgstr ""
"Por favor corriga el dato duplicado para %(field)s, el cual debe ser único." "Por favor corriga el dato duplicado para %(field)s, el cual debe ser único."
#: forms/models.py:604 #: forms/models.py:600
#, python-format #, python-format
msgid "" msgid ""
"Please correct the duplicate data for %(field_name)s which must be unique " "Please correct the duplicate data for %(field_name)s which must be unique "
@ -4120,26 +4117,26 @@ msgstr ""
"Por favor corriga los datos duplicados para %(field_name)s el cual debe ser " "Por favor corriga los datos duplicados para %(field_name)s el cual debe ser "
"único para %(lookup)s en %(date_field)s." "único para %(lookup)s en %(date_field)s."
#: forms/models.py:612 #: forms/models.py:608
msgid "Please correct the duplicate values below." msgid "Please correct the duplicate values below."
msgstr "Por favor, corrija los valores duplicados abajo." msgstr "Por favor, corrija los valores duplicados abajo."
#: forms/models.py:867 #: forms/models.py:863
msgid "The inline foreign key did not match the parent instance primary key." msgid "The inline foreign key did not match the parent instance primary key."
msgstr "" msgstr ""
"La clave foránea en linea no coincide con la clave primaria de la instancia " "La clave foránea en linea no coincide con la clave primaria de la instancia "
"padre." "padre."
#: forms/models.py:930 #: forms/models.py:926
msgid "Select a valid choice. That choice is not one of the available choices." msgid "Select a valid choice. That choice is not one of the available choices."
msgstr "Escoja una opción válida. Esa opción no está entre las disponibles." msgstr "Escoja una opción válida. Esa opción no está entre las disponibles."
#: forms/models.py:1004 #: forms/models.py:1000
#, python-format #, python-format
msgid "Select a valid choice. %s is not one of the available choices." msgid "Select a valid choice. %s is not one of the available choices."
msgstr "Escoja una opción válida; '%s' no es una de las opciones disponibles." msgstr "Escoja una opción válida; '%s' no es una de las opciones disponibles."
#: forms/models.py:1006 #: forms/models.py:1002
#, python-format #, python-format
msgid "\"%s\" is not a valid value for a primary key." msgid "\"%s\" is not a valid value for a primary key."
msgstr "\"%s\" no es un valor válido para una clave primaria." msgstr "\"%s\" no es un valor válido para una clave primaria."
@ -4459,6 +4456,30 @@ msgstr "Se actualizó con éxito el %(verbose_name)s."
msgid "The %(verbose_name)s was deleted." msgid "The %(verbose_name)s was deleted."
msgstr "El/La %(verbose_name)s ha sido borrado." msgstr "El/La %(verbose_name)s ha sido borrado."
#~ msgid "Comment moderation queue"
#~ msgstr "Cola de moderación de comentarios"
#~ msgid "No comments to moderate"
#~ msgstr "No hay comentarios por moderar"
#~ msgid "Email"
#~ msgstr "Correo electrónico"
#~ msgid "Authenticated?"
#~ msgstr "¿Autentificado?"
#~ msgid "IP Address"
#~ msgstr "Dirección IP"
#~ msgid "Date posted"
#~ msgstr "Fecha de envío"
#~ msgid "yes"
#~ msgstr "sí"
#~ msgid "no"
#~ msgstr "no"
#~ msgid "verbose_name" #~ msgid "verbose_name"
#~ msgid_plural "verbose_name_plural" #~ msgid_plural "verbose_name_plural"
#~ msgstr[0] "verbose_name" #~ msgstr[0] "verbose_name"

File diff suppressed because it is too large Load Diff

View File

@ -452,7 +452,7 @@ class AdminSite(object):
import warnings import warnings
warnings.warn( warnings.warn(
"AdminSite.root() is deprecated; use include(admin.site.urls) instead.", "AdminSite.root() is deprecated; use include(admin.site.urls) instead.",
PendingDeprecationWarning DeprecationWarning
) )
# #

View File

@ -149,7 +149,7 @@ def validate(cls, model):
validate_inline(inline, cls, model) validate_inline(inline, cls, model)
def validate_inline(cls, parent, parent_model): def validate_inline(cls, parent, parent_model):
# model is already verified to exist and be a Model # model is already verified to exist and be a Model
if cls.fk_name: # default value is None if cls.fk_name: # default value is None
f = get_field(cls, cls.model, cls.model._meta, 'fk_name', cls.fk_name) f = get_field(cls, cls.model, cls.model._meta, 'fk_name', cls.fk_name)
@ -196,6 +196,11 @@ def validate_base(cls, model):
check_isseq(cls, 'fields', cls.fields) check_isseq(cls, 'fields', cls.fields)
for field in cls.fields: for field in cls.fields:
check_formfield(cls, model, opts, 'fields', field) check_formfield(cls, model, opts, 'fields', field)
f = get_field(cls, model, opts, 'fields', field)
if isinstance(f, models.ManyToManyField) and not f.rel.through._meta.auto_created:
raise ImproperlyConfigured("'%s.fields' can't include the ManyToManyField "
"field '%s' because '%s' manually specifies "
"a 'through' model." % (cls.__name__, field, field))
if cls.fieldsets: if cls.fieldsets:
raise ImproperlyConfigured('Both fieldsets and fields are specified in %s.' % cls.__name__) raise ImproperlyConfigured('Both fieldsets and fields are specified in %s.' % cls.__name__)
if len(cls.fields) > len(set(cls.fields)): if len(cls.fields) > len(set(cls.fields)):
@ -214,11 +219,28 @@ def validate_base(cls, model):
raise ImproperlyConfigured("'fields' key is required in " raise ImproperlyConfigured("'fields' key is required in "
"%s.fieldsets[%d][1] field options dict." "%s.fieldsets[%d][1] field options dict."
% (cls.__name__, idx)) % (cls.__name__, idx))
for fields in fieldset[1]['fields']:
# The entry in fields might be a tuple. If it is a standalone
# field, make it into a tuple to make processing easier.
if type(fields) != tuple:
fields = (fields,)
for field in fields:
check_formfield(cls, model, opts, "fieldsets[%d][1]['fields']" % idx, field)
try:
f = opts.get_field(field)
if isinstance(f, models.ManyToManyField) and not f.rel.through._meta.auto_created:
raise ImproperlyConfigured("'%s.fieldsets[%d][1]['fields']' "
"can't include the ManyToManyField field '%s' because "
"'%s' manually specifies a 'through' model." % (
cls.__name__, idx, field, field))
except models.FieldDoesNotExist:
# If we can't find a field on the model that matches,
# it could be an extra field on the form.
pass
flattened_fieldsets = flatten_fieldsets(cls.fieldsets) flattened_fieldsets = flatten_fieldsets(cls.fieldsets)
if len(flattened_fieldsets) > len(set(flattened_fieldsets)): if len(flattened_fieldsets) > len(set(flattened_fieldsets)):
raise ImproperlyConfigured('There are duplicate field(s) in %s.fieldsets' % cls.__name__) raise ImproperlyConfigured('There are duplicate field(s) in %s.fieldsets' % cls.__name__)
for field in flattened_fieldsets:
check_formfield(cls, model, opts, "fieldsets[%d][1]['fields']" % idx, field)
# form # form
if hasattr(cls, 'form') and not issubclass(cls.form, BaseModelForm): if hasattr(cls, 'form') and not issubclass(cls.form, BaseModelForm):

View File

@ -4,7 +4,7 @@
Please note that MySQL only supports bounding box queries, also Please note that MySQL only supports bounding box queries, also
known as MBRs (Minimum Bounding Rectangles). Moreover, spatial known as MBRs (Minimum Bounding Rectangles). Moreover, spatial
indices may only be used on MyISAM tables -- if you need indices may only be used on MyISAM tables -- if you need
transactions, take a look at PostGIS. transactions, take a look at PostGIS.
""" """
from django.db import connection from django.db import connection
@ -38,7 +38,7 @@ MISC_TERMS = ['isnull']
# Assacceptable lookup types for Oracle spatial. # Assacceptable lookup types for Oracle spatial.
MYSQL_GIS_TERMS = MYSQL_GIS_FUNCTIONS.keys() MYSQL_GIS_TERMS = MYSQL_GIS_FUNCTIONS.keys()
MYSQL_GIS_TERMS += MISC_TERMS MYSQL_GIS_TERMS += MISC_TERMS
MYSQL_GIS_TERMS = dict((term, None) for term in MYSQL_GIS_TERMS) # Making dictionary MYSQL_GIS_TERMS = dict((term, None) for term in MYSQL_GIS_TERMS) # Making dictionary
def get_geo_where_clause(table_alias, name, lookup_type, geo_annot): def get_geo_where_clause(table_alias, name, lookup_type, geo_annot):
"Returns the SQL WHERE clause for use in MySQL spatial SQL construction." "Returns the SQL WHERE clause for use in MySQL spatial SQL construction."
@ -49,7 +49,7 @@ def get_geo_where_clause(table_alias, name, lookup_type, geo_annot):
lookup_info = MYSQL_GIS_FUNCTIONS.get(lookup_type, False) lookup_info = MYSQL_GIS_FUNCTIONS.get(lookup_type, False)
if lookup_info: if lookup_info:
return "%s(%s, %%s)" % (lookup_info, geo_col) return "%s(%s, %%s)" % (lookup_info, geo_col)
# Handling 'isnull' lookup type # Handling 'isnull' lookup type
# TODO: Is this needed because MySQL cannot handle NULL # TODO: Is this needed because MySQL cannot handle NULL
# geometries in its spatial indices. # geometries in its spatial indices.

View File

@ -3,8 +3,8 @@
routine for Oracle Spatial. routine for Oracle Spatial.
Please note that WKT support is broken on the XE version, and thus Please note that WKT support is broken on the XE version, and thus
this backend will not work on such platforms. Specifically, XE lacks this backend will not work on such platforms. Specifically, XE lacks
support for an internal JVM, and Java libraries are required to use support for an internal JVM, and Java libraries are required to use
the WKT constructors. the WKT constructors.
""" """
import re import re
@ -31,10 +31,10 @@ TRANSFORM = 'SDO_CS.TRANSFORM'
UNION = 'SDO_GEOM.SDO_UNION' UNION = 'SDO_GEOM.SDO_UNION'
UNIONAGG = 'SDO_AGGR_UNION' UNIONAGG = 'SDO_AGGR_UNION'
# We want to get SDO Geometries as WKT because it is much easier to # We want to get SDO Geometries as WKT because it is much easier to
# instantiate GEOS proxies from WKT than SDO_GEOMETRY(...) strings. # instantiate GEOS proxies from WKT than SDO_GEOMETRY(...) strings.
# However, this adversely affects performance (i.e., Java is called # However, this adversely affects performance (i.e., Java is called
# to convert to WKT on every query). If someone wishes to write a # to convert to WKT on every query). If someone wishes to write a
# SDO_GEOMETRY(...) parser in Python, let me know =) # SDO_GEOMETRY(...) parser in Python, let me know =)
GEOM_SELECT = 'SDO_UTIL.TO_WKTGEOMETRY(%s)' GEOM_SELECT = 'SDO_UTIL.TO_WKTGEOMETRY(%s)'
@ -50,7 +50,7 @@ class SDOOperation(SpatialFunction):
class SDODistance(SpatialFunction): class SDODistance(SpatialFunction):
"Class for Distance queries." "Class for Distance queries."
def __init__(self, op, tolerance=0.05): def __init__(self, op, tolerance=0.05):
super(SDODistance, self).__init__(DISTANCE, end_subst=', %s) %%s %%s' % tolerance, super(SDODistance, self).__init__(DISTANCE, end_subst=', %s) %%s %%s' % tolerance,
operator=op, result='%%s') operator=op, result='%%s')
class SDOGeomRelate(SpatialFunction): class SDOGeomRelate(SpatialFunction):
@ -59,7 +59,7 @@ class SDOGeomRelate(SpatialFunction):
# SDO_GEOM.RELATE(...) has a peculiar argument order: column, mask, geom, tolerance. # SDO_GEOM.RELATE(...) has a peculiar argument order: column, mask, geom, tolerance.
# Moreover, the runction result is the mask (e.g., 'DISJOINT' instead of 'TRUE'). # Moreover, the runction result is the mask (e.g., 'DISJOINT' instead of 'TRUE').
end_subst = "%s%s) %s '%s'" % (', %%s, ', tolerance, '=', mask) end_subst = "%s%s) %s '%s'" % (', %%s, ', tolerance, '=', mask)
beg_subst = "%%s(%%s, '%s'" % mask beg_subst = "%%s(%%s, '%s'" % mask
super(SDOGeomRelate, self).__init__('SDO_GEOM.RELATE', beg_subst=beg_subst, end_subst=end_subst) super(SDOGeomRelate, self).__init__('SDO_GEOM.RELATE', beg_subst=beg_subst, end_subst=end_subst)
class SDORelate(SpatialFunction): class SDORelate(SpatialFunction):
@ -81,7 +81,7 @@ DISTANCE_FUNCTIONS = {
'distance_gte' : (SDODistance('>='), dtypes), 'distance_gte' : (SDODistance('>='), dtypes),
'distance_lt' : (SDODistance('<'), dtypes), 'distance_lt' : (SDODistance('<'), dtypes),
'distance_lte' : (SDODistance('<='), dtypes), 'distance_lte' : (SDODistance('<='), dtypes),
'dwithin' : (SDOOperation('SDO_WITHIN_DISTANCE', 'dwithin' : (SDOOperation('SDO_WITHIN_DISTANCE',
beg_subst="%s(%s, %%s, 'distance=%%s'"), dtypes), beg_subst="%s(%s, %%s, 'distance=%%s'"), dtypes),
} }
@ -118,7 +118,7 @@ def get_geo_where_clause(table_alias, name, lookup_type, geo_annot):
# See if a Oracle Geometry function matches the lookup type next # See if a Oracle Geometry function matches the lookup type next
lookup_info = ORACLE_GEOMETRY_FUNCTIONS.get(lookup_type, False) lookup_info = ORACLE_GEOMETRY_FUNCTIONS.get(lookup_type, False)
if lookup_info: if lookup_info:
# Lookup types that are tuples take tuple arguments, e.g., 'relate' and # Lookup types that are tuples take tuple arguments, e.g., 'relate' and
# 'dwithin' lookup types. # 'dwithin' lookup types.
if isinstance(lookup_info, tuple): if isinstance(lookup_info, tuple):
# First element of tuple is lookup type, second element is the type # First element of tuple is lookup type, second element is the type
@ -128,15 +128,15 @@ def get_geo_where_clause(table_alias, name, lookup_type, geo_annot):
# Ensuring that a tuple _value_ was passed in from the user # Ensuring that a tuple _value_ was passed in from the user
if not isinstance(geo_annot.value, tuple): if not isinstance(geo_annot.value, tuple):
raise TypeError('Tuple required for `%s` lookup type.' % lookup_type) raise TypeError('Tuple required for `%s` lookup type.' % lookup_type)
if len(geo_annot.value) != 2: if len(geo_annot.value) != 2:
raise ValueError('2-element tuple required for %s lookup type.' % lookup_type) raise ValueError('2-element tuple required for %s lookup type.' % lookup_type)
# Ensuring the argument type matches what we expect. # Ensuring the argument type matches what we expect.
if not isinstance(geo_annot.value[1], arg_type): if not isinstance(geo_annot.value[1], arg_type):
raise TypeError('Argument type should be %s, got %s instead.' % (arg_type, type(geo_annot.value[1]))) raise TypeError('Argument type should be %s, got %s instead.' % (arg_type, type(geo_annot.value[1])))
if lookup_type == 'relate': if lookup_type == 'relate':
# The SDORelate class handles construction for these queries, # The SDORelate class handles construction for these queries,
# and verifies the mask argument. # and verifies the mask argument.
return sdo_op(geo_annot.value[1]).as_sql(geo_col) return sdo_op(geo_annot.value[1]).as_sql(geo_col)
else: else:
@ -144,7 +144,7 @@ def get_geo_where_clause(table_alias, name, lookup_type, geo_annot):
return sdo_op.as_sql(geo_col) return sdo_op.as_sql(geo_col)
else: else:
# Lookup info is a SDOOperation instance, whose `as_sql` method returns # Lookup info is a SDOOperation instance, whose `as_sql` method returns
# the SQL necessary for the geometry function call. For example: # the SQL necessary for the geometry function call. For example:
# SDO_CONTAINS("geoapp_country"."poly", SDO_GEOMTRY('POINT(5 23)', 4326)) = 'TRUE' # SDO_CONTAINS("geoapp_country"."poly", SDO_GEOMTRY('POINT(5 23)', 4326)) = 'TRUE'
return lookup_info.as_sql(geo_col) return lookup_info.as_sql(geo_col)
elif lookup_type == 'isnull': elif lookup_type == 'isnull':

View File

@ -18,18 +18,21 @@ SpatialBackend = BaseSpatialBackend(name='postgis', postgis=True,
distance_spheroid=DISTANCE_SPHEROID, distance_spheroid=DISTANCE_SPHEROID,
envelope=ENVELOPE, envelope=ENVELOPE,
extent=EXTENT, extent=EXTENT,
extent3d=EXTENT3D,
gis_terms=POSTGIS_TERMS, gis_terms=POSTGIS_TERMS,
geojson=ASGEOJSON, geojson=ASGEOJSON,
gml=ASGML, gml=ASGML,
intersection=INTERSECTION, intersection=INTERSECTION,
kml=ASKML, kml=ASKML,
length=LENGTH, length=LENGTH,
length3d=LENGTH3D,
length_spheroid=LENGTH_SPHEROID, length_spheroid=LENGTH_SPHEROID,
make_line=MAKE_LINE, make_line=MAKE_LINE,
mem_size=MEM_SIZE, mem_size=MEM_SIZE,
num_geom=NUM_GEOM, num_geom=NUM_GEOM,
num_points=NUM_POINTS, num_points=NUM_POINTS,
perimeter=PERIMETER, perimeter=PERIMETER,
perimeter3d=PERIMETER3D,
point_on_surface=POINT_ON_SURFACE, point_on_surface=POINT_ON_SURFACE,
scale=SCALE, scale=SCALE,
select=GEOM_SELECT, select=GEOM_SELECT,

View File

@ -2,7 +2,7 @@
This object provides quoting for GEOS geometries into PostgreSQL/PostGIS. This object provides quoting for GEOS geometries into PostgreSQL/PostGIS.
""" """
from django.contrib.gis.db.backend.postgis.query import GEOM_FROM_WKB from django.contrib.gis.db.backend.postgis.query import GEOM_FROM_EWKB
from psycopg2 import Binary from psycopg2 import Binary
from psycopg2.extensions import ISQLQuote from psycopg2.extensions import ISQLQuote
@ -11,7 +11,7 @@ class PostGISAdaptor(object):
"Initializes on the geometry." "Initializes on the geometry."
# Getting the WKB (in string form, to allow easy pickling of # Getting the WKB (in string form, to allow easy pickling of
# the adaptor) and the SRID from the geometry. # the adaptor) and the SRID from the geometry.
self.wkb = str(geom.wkb) self.ewkb = str(geom.ewkb)
self.srid = geom.srid self.srid = geom.srid
def __conform__(self, proto): def __conform__(self, proto):
@ -30,7 +30,7 @@ class PostGISAdaptor(object):
def getquoted(self): def getquoted(self):
"Returns a properly quoted string for use in PostgreSQL/PostGIS." "Returns a properly quoted string for use in PostgreSQL/PostGIS."
# Want to use WKB, so wrap with psycopg2 Binary() to quote properly. # Want to use WKB, so wrap with psycopg2 Binary() to quote properly.
return "%s(%s, %s)" % (GEOM_FROM_WKB, Binary(self.wkb), self.srid or -1) return "%s(E%s)" % (GEOM_FROM_EWKB, Binary(self.ewkb))
def prepare_database_save(self, unused): def prepare_database_save(self, unused):
return self return self

View File

@ -63,17 +63,21 @@ if MAJOR_VERSION >= 1:
DISTANCE_SPHERE = get_func('distance_sphere') DISTANCE_SPHERE = get_func('distance_sphere')
DISTANCE_SPHEROID = get_func('distance_spheroid') DISTANCE_SPHEROID = get_func('distance_spheroid')
ENVELOPE = get_func('Envelope') ENVELOPE = get_func('Envelope')
EXTENT = get_func('extent') EXTENT = get_func('Extent')
EXTENT3D = get_func('Extent3D')
GEOM_FROM_TEXT = get_func('GeomFromText') GEOM_FROM_TEXT = get_func('GeomFromText')
GEOM_FROM_EWKB = get_func('GeomFromEWKB')
GEOM_FROM_WKB = get_func('GeomFromWKB') GEOM_FROM_WKB = get_func('GeomFromWKB')
INTERSECTION = get_func('Intersection') INTERSECTION = get_func('Intersection')
LENGTH = get_func('Length') LENGTH = get_func('Length')
LENGTH3D = get_func('Length3D')
LENGTH_SPHEROID = get_func('length_spheroid') LENGTH_SPHEROID = get_func('length_spheroid')
MAKE_LINE = get_func('MakeLine') MAKE_LINE = get_func('MakeLine')
MEM_SIZE = get_func('mem_size') MEM_SIZE = get_func('mem_size')
NUM_GEOM = get_func('NumGeometries') NUM_GEOM = get_func('NumGeometries')
NUM_POINTS = get_func('npoints') NUM_POINTS = get_func('npoints')
PERIMETER = get_func('Perimeter') PERIMETER = get_func('Perimeter')
PERIMETER3D = get_func('Perimeter3D')
POINT_ON_SURFACE = get_func('PointOnSurface') POINT_ON_SURFACE = get_func('PointOnSurface')
SCALE = get_func('Scale') SCALE = get_func('Scale')
SNAP_TO_GRID = get_func('SnapToGrid') SNAP_TO_GRID = get_func('SnapToGrid')

View File

@ -24,6 +24,9 @@ class Collect(GeoAggregate):
class Extent(GeoAggregate): class Extent(GeoAggregate):
name = 'Extent' name = 'Extent'
class Extent3D(GeoAggregate):
name = 'Extent3D'
class MakeLine(GeoAggregate): class MakeLine(GeoAggregate):
name = 'MakeLine' name = 'MakeLine'

View File

@ -34,6 +34,9 @@ class GeoManager(Manager):
def extent(self, *args, **kwargs): def extent(self, *args, **kwargs):
return self.get_query_set().extent(*args, **kwargs) return self.get_query_set().extent(*args, **kwargs)
def extent3d(self, *args, **kwargs):
return self.get_query_set().extent3d(*args, **kwargs)
def geojson(self, *args, **kwargs): def geojson(self, *args, **kwargs):
return self.get_query_set().geojson(*args, **kwargs) return self.get_query_set().geojson(*args, **kwargs)

View File

@ -110,6 +110,14 @@ class GeoQuerySet(QuerySet):
""" """
return self._spatial_aggregate(aggregates.Extent, **kwargs) return self._spatial_aggregate(aggregates.Extent, **kwargs)
def extent3d(self, **kwargs):
"""
Returns the aggregate extent, in 3D, of the features in the
GeoQuerySet. It is returned as a 6-tuple, comprising:
(xmin, ymin, zmin, xmax, ymax, zmax).
"""
return self._spatial_aggregate(aggregates.Extent3D, **kwargs)
def geojson(self, precision=8, crs=False, bbox=False, **kwargs): def geojson(self, precision=8, crs=False, bbox=False, **kwargs):
""" """
Returns a GeoJSON representation of the geomtry field in a `geojson` Returns a GeoJSON representation of the geomtry field in a `geojson`
@ -524,12 +532,14 @@ class GeoQuerySet(QuerySet):
else: else:
dist_att = Distance.unit_attname(geo_field.units_name) dist_att = Distance.unit_attname(geo_field.units_name)
# Shortcut booleans for what distance function we're using. # Shortcut booleans for what distance function we're using and
# whether the geometry field is 3D.
distance = func == 'distance' distance = func == 'distance'
length = func == 'length' length = func == 'length'
perimeter = func == 'perimeter' perimeter = func == 'perimeter'
if not (distance or length or perimeter): if not (distance or length or perimeter):
raise ValueError('Unknown distance function: %s' % func) raise ValueError('Unknown distance function: %s' % func)
geom_3d = geo_field.dim == 3
# The field's get_db_prep_lookup() is used to get any # The field's get_db_prep_lookup() is used to get any
# extra distance parameters. Here we set up the # extra distance parameters. Here we set up the
@ -604,7 +614,7 @@ class GeoQuerySet(QuerySet):
# some error checking is required. # some error checking is required.
if not isinstance(geo_field, PointField): if not isinstance(geo_field, PointField):
raise ValueError('Spherical distance calculation only supported on PointFields.') raise ValueError('Spherical distance calculation only supported on PointFields.')
if not str(SpatialBackend.Geometry(buffer(params[0].wkb)).geom_type) == 'Point': if not str(SpatialBackend.Geometry(buffer(params[0].ewkb)).geom_type) == 'Point':
raise ValueError('Spherical distance calculation only supported with Point Geometry parameters') raise ValueError('Spherical distance calculation only supported with Point Geometry parameters')
# The `function` procedure argument needs to be set differently for # The `function` procedure argument needs to be set differently for
# geodetic distance calculations. # geodetic distance calculations.
@ -617,9 +627,16 @@ class GeoQuerySet(QuerySet):
elif length or perimeter: elif length or perimeter:
procedure_fmt = '%(geo_col)s' procedure_fmt = '%(geo_col)s'
if geodetic and length: if geodetic and length:
# There's no `length_sphere` # There's no `length_sphere`, and `length_spheroid` also
# works on 3D geometries.
procedure_fmt += ',%(spheroid)s' procedure_fmt += ',%(spheroid)s'
procedure_args.update({'function' : SpatialBackend.length_spheroid, 'spheroid' : where[1]}) procedure_args.update({'function' : SpatialBackend.length_spheroid, 'spheroid' : where[1]})
elif geom_3d and SpatialBackend.postgis:
# Use 3D variants of perimeter and length routines on PostGIS.
if perimeter:
procedure_args.update({'function' : SpatialBackend.perimeter3d})
elif length:
procedure_args.update({'function' : SpatialBackend.length3d})
# Setting up the settings for `_spatial_attribute`. # Setting up the settings for `_spatial_attribute`.
s = {'select_field' : DistanceField(dist_att), s = {'select_field' : DistanceField(dist_att),

View File

@ -11,6 +11,9 @@ geo_template = '%(function)s(%(field)s)'
def convert_extent(box): def convert_extent(box):
raise NotImplementedError('Aggregate extent not implemented for this spatial backend.') raise NotImplementedError('Aggregate extent not implemented for this spatial backend.')
def convert_extent3d(box):
raise NotImplementedError('Aggregate 3D extent not implemented for this spatial backend.')
def convert_geom(wkt, geo_field): def convert_geom(wkt, geo_field):
raise NotImplementedError('Aggregate method not implemented for this spatial backend.') raise NotImplementedError('Aggregate method not implemented for this spatial backend.')
@ -23,6 +26,14 @@ if SpatialBackend.postgis:
xmax, ymax = map(float, ur.split()) xmax, ymax = map(float, ur.split())
return (xmin, ymin, xmax, ymax) return (xmin, ymin, xmax, ymax)
def convert_extent3d(box3d):
# Box text will be something like "BOX3D(-90.0 30.0 1, -85.0 40.0 2)";
# parsing out and returning as a 4-tuple.
ll, ur = box3d[6:-1].split(',')
xmin, ymin, zmin = map(float, ll.split())
xmax, ymax, zmax = map(float, ur.split())
return (xmin, ymin, zmin, xmax, ymax, zmax)
def convert_geom(hex, geo_field): def convert_geom(hex, geo_field):
if hex: return SpatialBackend.Geometry(hex) if hex: return SpatialBackend.Geometry(hex)
else: return None else: return None
@ -94,7 +105,7 @@ class Collect(GeoAggregate):
sql_function = SpatialBackend.collect sql_function = SpatialBackend.collect
class Extent(GeoAggregate): class Extent(GeoAggregate):
is_extent = True is_extent = '2D'
sql_function = SpatialBackend.extent sql_function = SpatialBackend.extent
if SpatialBackend.oracle: if SpatialBackend.oracle:
@ -102,6 +113,10 @@ if SpatialBackend.oracle:
Extent.conversion_class = GeomField Extent.conversion_class = GeomField
Extent.sql_template = '%(function)s(%(field)s)' Extent.sql_template = '%(function)s(%(field)s)'
class Extent3D(GeoAggregate):
is_extent = '3D'
sql_function = SpatialBackend.extent3d
class MakeLine(GeoAggregate): class MakeLine(GeoAggregate):
conversion_class = GeomField conversion_class = GeomField
sql_function = SpatialBackend.make_line sql_function = SpatialBackend.make_line

View File

@ -262,7 +262,10 @@ class GeoQuery(sql.Query):
""" """
if isinstance(aggregate, self.aggregates_module.GeoAggregate): if isinstance(aggregate, self.aggregates_module.GeoAggregate):
if aggregate.is_extent: if aggregate.is_extent:
return self.aggregates_module.convert_extent(value) if aggregate.is_extent == '3D':
return self.aggregates_module.convert_extent3d(value)
else:
return self.aggregates_module.convert_extent(value)
else: else:
return self.aggregates_module.convert_geom(value, aggregate.source) return self.aggregates_module.convert_geom(value, aggregate.source)
else: else:

View File

@ -214,13 +214,7 @@ class OGRGeometry(GDALBase):
@property @property
def geom_type(self): def geom_type(self):
"Returns the Type for this Geometry." "Returns the Type for this Geometry."
try: return OGRGeomType(capi.get_geom_type(self.ptr))
return OGRGeomType(capi.get_geom_type(self.ptr))
except OGRException:
# VRT datasources return an invalid geometry type
# number, but a valid name -- we'll try that instead.
# See: http://trac.osgeo.org/gdal/ticket/2491
return OGRGeomType(capi.get_geom_name(self.ptr))
@property @property
def geom_name(self): def geom_name(self):
@ -684,4 +678,11 @@ GEO_CLASSES = {1 : Point,
6 : MultiPolygon, 6 : MultiPolygon,
7 : GeometryCollection, 7 : GeometryCollection,
101: LinearRing, 101: LinearRing,
1 + OGRGeomType.wkb25bit : Point,
2 + OGRGeomType.wkb25bit : LineString,
3 + OGRGeomType.wkb25bit : Polygon,
4 + OGRGeomType.wkb25bit : MultiPoint,
5 + OGRGeomType.wkb25bit : MultiLineString,
6 + OGRGeomType.wkb25bit : MultiPolygon,
7 + OGRGeomType.wkb25bit : GeometryCollection,
} }

View File

@ -4,6 +4,8 @@ from django.contrib.gis.gdal.error import OGRException
class OGRGeomType(object): class OGRGeomType(object):
"Encapulates OGR Geometry Types." "Encapulates OGR Geometry Types."
wkb25bit = -2147483648
# Dictionary of acceptable OGRwkbGeometryType s and their string names. # Dictionary of acceptable OGRwkbGeometryType s and their string names.
_types = {0 : 'Unknown', _types = {0 : 'Unknown',
1 : 'Point', 1 : 'Point',
@ -15,6 +17,13 @@ class OGRGeomType(object):
7 : 'GeometryCollection', 7 : 'GeometryCollection',
100 : 'None', 100 : 'None',
101 : 'LinearRing', 101 : 'LinearRing',
1 + wkb25bit: 'Point25D',
2 + wkb25bit: 'LineString25D',
3 + wkb25bit: 'Polygon25D',
4 + wkb25bit: 'MultiPoint25D',
5 + wkb25bit : 'MultiLineString25D',
6 + wkb25bit : 'MultiPolygon25D',
7 + wkb25bit : 'GeometryCollection25D',
} }
# Reverse type dictionary, keyed by lower-case of the name. # Reverse type dictionary, keyed by lower-case of the name.
_str_types = dict([(v.lower(), k) for k, v in _types.items()]) _str_types = dict([(v.lower(), k) for k, v in _types.items()])
@ -68,7 +77,7 @@ class OGRGeomType(object):
@property @property
def django(self): def django(self):
"Returns the Django GeometryField for this OGR Type." "Returns the Django GeometryField for this OGR Type."
s = self.name s = self.name.replace('25D', '')
if s in ('LinearRing', 'None'): if s in ('LinearRing', 'None'):
return None return None
elif s == 'Unknown': elif s == 'Unknown':

View File

@ -1,5 +1,5 @@
# Needed ctypes routines # Needed ctypes routines
from ctypes import byref from ctypes import c_double, byref
# Other GDAL imports. # Other GDAL imports.
from django.contrib.gis.gdal.base import GDALBase from django.contrib.gis.gdal.base import GDALBase
@ -7,11 +7,12 @@ from django.contrib.gis.gdal.envelope import Envelope, OGREnvelope
from django.contrib.gis.gdal.error import OGRException, OGRIndexError, SRSException from django.contrib.gis.gdal.error import OGRException, OGRIndexError, SRSException
from django.contrib.gis.gdal.feature import Feature from django.contrib.gis.gdal.feature import Feature
from django.contrib.gis.gdal.field import OGRFieldTypes from django.contrib.gis.gdal.field import OGRFieldTypes
from django.contrib.gis.gdal.geometries import OGRGeomType from django.contrib.gis.gdal.geomtype import OGRGeomType
from django.contrib.gis.gdal.geometries import OGRGeometry
from django.contrib.gis.gdal.srs import SpatialReference from django.contrib.gis.gdal.srs import SpatialReference
# GDAL ctypes function prototypes. # GDAL ctypes function prototypes.
from django.contrib.gis.gdal.prototypes import ds as capi, srs as srs_api from django.contrib.gis.gdal.prototypes import ds as capi, geom as geom_api, srs as srs_api
# For more information, see the OGR C API source code: # For more information, see the OGR C API source code:
# http://www.gdal.org/ogr/ogr__api_8h.html # http://www.gdal.org/ogr/ogr__api_8h.html
@ -156,6 +157,29 @@ class Layer(GDALBase):
return [capi.get_field_precision(capi.get_field_defn(self._ldefn, i)) return [capi.get_field_precision(capi.get_field_defn(self._ldefn, i))
for i in xrange(self.num_fields)] for i in xrange(self.num_fields)]
def _get_spatial_filter(self):
try:
return OGRGeometry(geom_api.clone_geom(capi.get_spatial_filter(self.ptr)))
except OGRException:
return None
def _set_spatial_filter(self, filter):
if isinstance(filter, OGRGeometry):
capi.set_spatial_filter(self.ptr, filter.ptr)
elif isinstance(filter, (tuple, list)):
if not len(filter) == 4:
raise ValueError('Spatial filter list/tuple must have 4 elements.')
# Map c_double onto params -- if a bad type is passed in it
# will be caught here.
xmin, ymin, xmax, ymax = map(c_double, filter)
capi.set_spatial_filter_rect(self.ptr, xmin, ymin, xmax, ymax)
elif filter is None:
capi.set_spatial_filter(self.ptr, None)
else:
raise TypeError('Spatial filter must be either an OGRGeometry instance, a 4-tuple, or None.')
spatial_filter = property(_get_spatial_filter, _set_spatial_filter)
#### Layer Methods #### #### Layer Methods ####
def get_fields(self, field_name): def get_fields(self, field_name):
""" """

View File

@ -3,7 +3,7 @@
related data structures. OGR_Dr_*, OGR_DS_*, OGR_L_*, OGR_F_*, related data structures. OGR_Dr_*, OGR_DS_*, OGR_L_*, OGR_F_*,
OGR_Fld_* routines are relevant here. OGR_Fld_* routines are relevant here.
""" """
from ctypes import c_char_p, c_int, c_long, c_void_p, POINTER from ctypes import c_char_p, c_double, c_int, c_long, c_void_p, POINTER
from django.contrib.gis.gdal.envelope import OGREnvelope from django.contrib.gis.gdal.envelope import OGREnvelope
from django.contrib.gis.gdal.libgdal import lgdal from django.contrib.gis.gdal.libgdal import lgdal
from django.contrib.gis.gdal.prototypes.generation import \ from django.contrib.gis.gdal.prototypes.generation import \
@ -38,6 +38,9 @@ get_layer_srs = srs_output(lgdal.OGR_L_GetSpatialRef, [c_void_p])
get_next_feature = voidptr_output(lgdal.OGR_L_GetNextFeature, [c_void_p]) get_next_feature = voidptr_output(lgdal.OGR_L_GetNextFeature, [c_void_p])
reset_reading = void_output(lgdal.OGR_L_ResetReading, [c_void_p], errcheck=False) reset_reading = void_output(lgdal.OGR_L_ResetReading, [c_void_p], errcheck=False)
test_capability = int_output(lgdal.OGR_L_TestCapability, [c_void_p, c_char_p]) test_capability = int_output(lgdal.OGR_L_TestCapability, [c_void_p, c_char_p])
get_spatial_filter = geom_output(lgdal.OGR_L_GetSpatialFilter, [c_void_p])
set_spatial_filter = void_output(lgdal.OGR_L_SetSpatialFilter, [c_void_p, c_void_p], errcheck=False)
set_spatial_filter_rect = void_output(lgdal.OGR_L_SetSpatialFilterRect, [c_void_p, c_double, c_double, c_double, c_double], errcheck=False)
### Feature Definition Routines ### ### Feature Definition Routines ###
get_fd_geom_type = int_output(lgdal.OGR_FD_GetGeomType, [c_void_p]) get_fd_geom_type = int_output(lgdal.OGR_FD_GetGeomType, [c_void_p])

View File

@ -1,13 +1,11 @@
import os, os.path, unittest import os, os.path, unittest
from django.contrib.gis.gdal import DataSource, Envelope, OGRException, OGRIndexError from django.contrib.gis.gdal import DataSource, Envelope, OGRGeometry, OGRException, OGRIndexError
from django.contrib.gis.gdal.field import OFTReal, OFTInteger, OFTString from django.contrib.gis.gdal.field import OFTReal, OFTInteger, OFTString
from django.contrib import gis from django.contrib import gis
# Path for SHP files # Path for SHP files
data_path = os.path.join(os.path.dirname(gis.__file__), 'tests' + os.sep + 'data') data_path = os.path.join(os.path.dirname(gis.__file__), 'tests' + os.sep + 'data')
def get_ds_file(name, ext): def get_ds_file(name, ext):
return os.sep.join([data_path, name, name + '.%s' % ext]) return os.sep.join([data_path, name, name + '.%s' % ext])
# Test SHP data source object # Test SHP data source object
@ -25,7 +23,7 @@ ds_list = (TestDS('test_point', nfeat=5, nfld=3, geom='POINT', gtype=1, driver='
srs_wkt='GEOGCS["GCS_WGS_1984",DATUM["WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]', srs_wkt='GEOGCS["GCS_WGS_1984",DATUM["WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]',
field_values={'dbl' : [float(i) for i in range(1, 6)], 'int' : range(1, 6), 'str' : [str(i) for i in range(1, 6)]}, field_values={'dbl' : [float(i) for i in range(1, 6)], 'int' : range(1, 6), 'str' : [str(i) for i in range(1, 6)]},
fids=range(5)), fids=range(5)),
TestDS('test_vrt', ext='vrt', nfeat=3, nfld=3, geom='POINT', gtype=1, driver='VRT', TestDS('test_vrt', ext='vrt', nfeat=3, nfld=3, geom='POINT', gtype='Point25D', driver='VRT',
fields={'POINT_X' : OFTString, 'POINT_Y' : OFTString, 'NUM' : OFTString}, # VRT uses CSV, which all types are OFTString. fields={'POINT_X' : OFTString, 'POINT_Y' : OFTString, 'NUM' : OFTString}, # VRT uses CSV, which all types are OFTString.
extent=(1.0, 2.0, 100.0, 523.5), # Min/Max from CSV extent=(1.0, 2.0, 100.0, 523.5), # Min/Max from CSV
field_values={'POINT_X' : ['1.0', '5.0', '100.0'], 'POINT_Y' : ['2.0', '23.0', '523.5'], 'NUM' : ['5', '17', '23']}, field_values={'POINT_X' : ['1.0', '5.0', '100.0'], 'POINT_Y' : ['2.0', '23.0', '523.5'], 'NUM' : ['5', '17', '23']},
@ -191,7 +189,41 @@ class DataSourceTest(unittest.TestCase):
if hasattr(source, 'srs_wkt'): if hasattr(source, 'srs_wkt'):
self.assertEqual(source.srs_wkt, g.srs.wkt) self.assertEqual(source.srs_wkt, g.srs.wkt)
def test06_spatial_filter(self):
"Testing the Layer.spatial_filter property."
ds = DataSource(get_ds_file('cities', 'shp'))
lyr = ds[0]
# When not set, it should be None.
self.assertEqual(None, lyr.spatial_filter)
# Must be set a/an OGRGeometry or 4-tuple.
self.assertRaises(TypeError, lyr._set_spatial_filter, 'foo')
# Setting the spatial filter with a tuple/list with the extent of
# a buffer centering around Pueblo.
self.assertRaises(ValueError, lyr._set_spatial_filter, range(5))
filter_extent = (-105.609252, 37.255001, -103.609252, 39.255001)
lyr.spatial_filter = (-105.609252, 37.255001, -103.609252, 39.255001)
self.assertEqual(OGRGeometry.from_bbox(filter_extent), lyr.spatial_filter)
feats = [feat for feat in lyr]
self.assertEqual(1, len(feats))
self.assertEqual('Pueblo', feats[0].get('Name'))
# Setting the spatial filter with an OGRGeometry for buffer centering
# around Houston.
filter_geom = OGRGeometry('POLYGON((-96.363151 28.763374,-94.363151 28.763374,-94.363151 30.763374,-96.363151 30.763374,-96.363151 28.763374))')
lyr.spatial_filter = filter_geom
self.assertEqual(filter_geom, lyr.spatial_filter)
feats = [feat for feat in lyr]
self.assertEqual(1, len(feats))
self.assertEqual('Houston', feats[0].get('Name'))
# Clearing the spatial filter by setting it to None. Now
# should indicate that there are 3 features in the Layer.
lyr.spatial_filter = None
self.assertEqual(3, len(lyr))
def suite(): def suite():
s = unittest.TestSuite() s = unittest.TestSuite()
s.addTest(unittest.makeSuite(DataSourceTest)) s.addTest(unittest.makeSuite(DataSourceTest))

View File

@ -46,6 +46,13 @@ class OGRGeomTest(unittest.TestCase):
self.assertEqual(0, gt.num) self.assertEqual(0, gt.num)
self.assertEqual('Unknown', gt.name) self.assertEqual('Unknown', gt.name)
def test00b_geomtype_25d(self):
"Testing OGRGeomType object with 25D types."
wkb25bit = OGRGeomType.wkb25bit
self.failUnless(OGRGeomType(wkb25bit + 1) == 'Point25D')
self.failUnless(OGRGeomType('MultiLineString25D') == (5 + wkb25bit))
self.assertEqual('GeometryCollectionField', OGRGeomType('GeometryCollection25D').django)
def test01a_wkt(self): def test01a_wkt(self):
"Testing WKT output." "Testing WKT output."
for g in wkt_out: for g in wkt_out:
@ -418,6 +425,17 @@ class OGRGeomTest(unittest.TestCase):
xmax, ymax = max(x), max(y) xmax, ymax = max(x), max(y)
self.assertEqual((xmin, ymin, xmax, ymax), poly.extent) self.assertEqual((xmin, ymin, xmax, ymax), poly.extent)
def test16_25D(self):
"Testing 2.5D geometries."
pnt_25d = OGRGeometry('POINT(1 2 3)')
self.assertEqual('Point25D', pnt_25d.geom_type.name)
self.assertEqual(3.0, pnt_25d.z)
self.assertEqual(3, pnt_25d.coord_dim)
ls_25d = OGRGeometry('LINESTRING(1 1 1,2 2 2,3 3 3)')
self.assertEqual('LineString25D', ls_25d.geom_type.name)
self.assertEqual([1.0, 2.0, 3.0], ls_25d.z)
self.assertEqual(3, ls_25d.coord_dim)
def suite(): def suite():
s = unittest.TestSuite() s = unittest.TestSuite()
s.addTest(unittest.makeSuite(OGRGeomTest)) s.addTest(unittest.makeSuite(OGRGeomTest))

View File

@ -357,33 +357,53 @@ class GEOSGeometry(GEOSBase, ListMixin):
#### Output Routines #### #### Output Routines ####
@property @property
def ewkt(self): def ewkt(self):
"Returns the EWKT (WKT + SRID) of the Geometry." """
Returns the EWKT (WKT + SRID) of the Geometry. Note that Z values
are *not* included in this representation because GEOS does not yet
support serializing them.
"""
if self.get_srid(): return 'SRID=%s;%s' % (self.srid, self.wkt) if self.get_srid(): return 'SRID=%s;%s' % (self.srid, self.wkt)
else: return self.wkt else: return self.wkt
@property @property
def wkt(self): def wkt(self):
"Returns the WKT (Well-Known Text) of the Geometry." "Returns the WKT (Well-Known Text) representation of this Geometry."
return wkt_w.write(self) return wkt_w.write(self)
@property @property
def hex(self): def hex(self):
""" """
Returns the HEX of the Geometry -- please note that the SRID is not Returns the WKB of this Geometry in hexadecimal form. Please note
included in this representation, because the GEOS C library uses that the SRID and Z values are not included in this representation
-1 by default, even if the SRID is set. because it is not a part of the OGC specification (use the `hexewkb`
property instead).
""" """
# A possible faster, all-python, implementation: # A possible faster, all-python, implementation:
# str(self.wkb).encode('hex') # str(self.wkb).encode('hex')
return wkb_w.write_hex(self) return wkb_w.write_hex(self)
@property
def hexewkb(self):
"""
Returns the EWKB of this Geometry in hexadecimal form. This is an
extension of the WKB specification that includes SRID and Z values
that are a part of this geometry.
"""
if self.hasz:
if not GEOS_PREPARE:
# See: http://trac.osgeo.org/geos/ticket/216
raise GEOSException('Upgrade GEOS to 3.1 to get valid 3D HEXEWKB.')
return ewkb_w3d.write_hex(self)
else:
return ewkb_w.write_hex(self)
@property @property
def json(self): def json(self):
""" """
Returns GeoJSON representation of this Geometry if GDAL 1.5+ Returns GeoJSON representation of this Geometry if GDAL 1.5+
is installed. is installed.
""" """
if gdal.GEOJSON: if gdal.GEOJSON:
return self.ogr.json return self.ogr.json
else: else:
raise GEOSException('GeoJSON output only supported on GDAL 1.5+.') raise GEOSException('GeoJSON output only supported on GDAL 1.5+.')
@ -391,9 +411,28 @@ class GEOSGeometry(GEOSBase, ListMixin):
@property @property
def wkb(self): def wkb(self):
"Returns the WKB of the Geometry as a buffer." """
Returns the WKB (Well-Known Binary) representation of this Geometry
as a Python buffer. SRID and Z values are not included, use the
`ewkb` property instead.
"""
return wkb_w.write(self) return wkb_w.write(self)
@property
def ewkb(self):
"""
Return the EWKB representation of this Geometry as a Python buffer.
This is an extension of the WKB specification that includes any SRID
and Z values that are a part of this geometry.
"""
if self.hasz:
if not GEOS_PREPARE:
# See: http://trac.osgeo.org/geos/ticket/216
raise GEOSException('Upgrade GEOS to 3.1 to get valid 3D EWKB.')
return ewkb_w3d.write(self)
else:
return ewkb_w.write(self)
@property @property
def kml(self): def kml(self):
"Returns the KML representation of this Geometry." "Returns the KML representation of this Geometry."
@ -617,7 +656,7 @@ GEOS_CLASSES = {0 : Point,
} }
# Similarly, import the GEOS I/O instances here to avoid conflicts. # Similarly, import the GEOS I/O instances here to avoid conflicts.
from django.contrib.gis.geos.io import wkt_r, wkt_w, wkb_r, wkb_w from django.contrib.gis.geos.io import wkt_r, wkt_w, wkb_r, wkb_w, ewkb_w, ewkb_w3d
# If supported, import the PreparedGeometry class. # If supported, import the PreparedGeometry class.
if GEOS_PREPARE: if GEOS_PREPARE:

View File

@ -14,19 +14,19 @@ class IOBase(GEOSBase):
"Base class for GEOS I/O objects." "Base class for GEOS I/O objects."
def __init__(self): def __init__(self):
# Getting the pointer with the constructor. # Getting the pointer with the constructor.
self.ptr = self.constructor() self.ptr = self._constructor()
def __del__(self): def __del__(self):
# Cleaning up with the appropriate destructor. # Cleaning up with the appropriate destructor.
if self._ptr: self.destructor(self._ptr) if self._ptr: self._destructor(self._ptr)
### WKT Reading and Writing objects ### ### WKT Reading and Writing objects ###
# Non-public class for internal use because its `read` method returns # Non-public class for internal use because its `read` method returns
# _pointers_ instead of a GEOSGeometry object. # _pointers_ instead of a GEOSGeometry object.
class _WKTReader(IOBase): class _WKTReader(IOBase):
constructor = capi.wkt_reader_create _constructor = capi.wkt_reader_create
destructor = capi.wkt_reader_destroy _destructor = capi.wkt_reader_destroy
ptr_type = capi.WKT_READ_PTR ptr_type = capi.WKT_READ_PTR
def read(self, wkt): def read(self, wkt):
@ -39,8 +39,8 @@ class WKTReader(_WKTReader):
return GEOSGeometry(super(WKTReader, self).read(wkt)) return GEOSGeometry(super(WKTReader, self).read(wkt))
class WKTWriter(IOBase): class WKTWriter(IOBase):
constructor = capi.wkt_writer_create _constructor = capi.wkt_writer_create
destructor = capi.wkt_writer_destroy _destructor = capi.wkt_writer_destroy
ptr_type = capi.WKT_WRITE_PTR ptr_type = capi.WKT_WRITE_PTR
def write(self, geom): def write(self, geom):
@ -51,8 +51,8 @@ class WKTWriter(IOBase):
# Non-public class for the same reason as _WKTReader above. # Non-public class for the same reason as _WKTReader above.
class _WKBReader(IOBase): class _WKBReader(IOBase):
constructor = capi.wkb_reader_create _constructor = capi.wkb_reader_create
destructor = capi.wkb_reader_destroy _destructor = capi.wkb_reader_destroy
ptr_type = capi.WKB_READ_PTR ptr_type = capi.WKB_READ_PTR
def read(self, wkb): def read(self, wkb):
@ -71,8 +71,8 @@ class WKBReader(_WKBReader):
return GEOSGeometry(super(WKBReader, self).read(wkb)) return GEOSGeometry(super(WKBReader, self).read(wkb))
class WKBWriter(IOBase): class WKBWriter(IOBase):
constructor = capi.wkb_writer_create _constructor = capi.wkb_writer_create
destructor = capi.wkb_writer_destroy _destructor = capi.wkb_writer_destroy
ptr_type = capi.WKB_WRITE_PTR ptr_type = capi.WKB_WRITE_PTR
def write(self, geom): def write(self, geom):
@ -121,3 +121,10 @@ wkt_r = _WKTReader()
wkt_w = WKTWriter() wkt_w = WKTWriter()
wkb_r = _WKBReader() wkb_r = _WKBReader()
wkb_w = WKBWriter() wkb_w = WKBWriter()
# These instances are for writing EWKB in 2D and 3D.
ewkb_w = WKBWriter()
ewkb_w.srid = True
ewkb_w3d = WKBWriter()
ewkb_w3d.srid = True
ewkb_w3d.outdim = 3

View File

@ -62,17 +62,16 @@ def string_from_geom(func):
### ctypes prototypes ### ### ctypes prototypes ###
# Deprecated creation routines from WKB, HEX, WKT # Deprecated creation and output routines from WKB, HEX, WKT
from_hex = bin_constructor(lgeos.GEOSGeomFromHEX_buf) from_hex = bin_constructor(lgeos.GEOSGeomFromHEX_buf)
from_wkb = bin_constructor(lgeos.GEOSGeomFromWKB_buf) from_wkb = bin_constructor(lgeos.GEOSGeomFromWKB_buf)
from_wkt = geom_output(lgeos.GEOSGeomFromWKT, [c_char_p]) from_wkt = geom_output(lgeos.GEOSGeomFromWKT, [c_char_p])
# Output routines
to_hex = bin_output(lgeos.GEOSGeomToHEX_buf) to_hex = bin_output(lgeos.GEOSGeomToHEX_buf)
to_wkb = bin_output(lgeos.GEOSGeomToWKB_buf) to_wkb = bin_output(lgeos.GEOSGeomToWKB_buf)
to_wkt = string_from_geom(lgeos.GEOSGeomToWKT) to_wkt = string_from_geom(lgeos.GEOSGeomToWKT)
# The GEOS geometry type, typeid, num_coordites and number of geometries # The GEOS geometry type, typeid, num_coordinates and number of geometries
geos_normalize = int_from_geom(lgeos.GEOSNormalize) geos_normalize = int_from_geom(lgeos.GEOSNormalize)
geos_type = string_from_geom(lgeos.GEOSGeomType) geos_type = string_from_geom(lgeos.GEOSGeomType)
geos_typeid = int_from_geom(lgeos.GEOSGeomTypeId) geos_typeid = int_from_geom(lgeos.GEOSGeomTypeId)

View File

@ -71,6 +71,49 @@ class GEOSTest(unittest.TestCase):
geom = fromstr(g.wkt) geom = fromstr(g.wkt)
self.assertEqual(g.hex, geom.hex) self.assertEqual(g.hex, geom.hex)
def test01b_hexewkb(self):
"Testing (HEX)EWKB output."
from binascii import a2b_hex
pnt_2d = Point(0, 1, srid=4326)
pnt_3d = Point(0, 1, 2, srid=4326)
# OGC-compliant HEX will not have SRID nor Z value.
self.assertEqual(ogc_hex, pnt_2d.hex)
self.assertEqual(ogc_hex, pnt_3d.hex)
# HEXEWKB should be appropriate for its dimension -- have to use an
# a WKBWriter w/dimension set accordingly, else GEOS will insert
# garbage into 3D coordinate if there is none. Also, GEOS has a
# a bug in versions prior to 3.1 that puts the X coordinate in
# place of Z; an exception should be raised on those versions.
self.assertEqual(hexewkb_2d, pnt_2d.hexewkb)
if GEOS_PREPARE:
self.assertEqual(hexewkb_3d, pnt_3d.hexewkb)
self.assertEqual(True, GEOSGeometry(hexewkb_3d).hasz)
else:
try:
hexewkb = pnt_3d.hexewkb
except GEOSException:
pass
else:
self.fail('Should have raised GEOSException.')
# Same for EWKB.
self.assertEqual(buffer(a2b_hex(hexewkb_2d)), pnt_2d.ewkb)
if GEOS_PREPARE:
self.assertEqual(buffer(a2b_hex(hexewkb_3d)), pnt_3d.ewkb)
else:
try:
ewkb = pnt_3d.ewkb
except GEOSException:
pass
else:
self.fail('Should have raised GEOSException')
# Redundant sanity check.
self.assertEqual(4326, GEOSGeometry(hexewkb_2d).srid)
def test01c_kml(self): def test01c_kml(self):
"Testing KML output." "Testing KML output."
for tg in wkt_out: for tg in wkt_out:

View File

@ -9,9 +9,10 @@ def geo_suite():
some backends). some backends).
""" """
from django.conf import settings from django.conf import settings
from django.contrib.gis.geos import GEOS_PREPARE
from django.contrib.gis.gdal import HAS_GDAL from django.contrib.gis.gdal import HAS_GDAL
from django.contrib.gis.utils import HAS_GEOIP from django.contrib.gis.utils import HAS_GEOIP
from django.contrib.gis.tests.utils import mysql from django.contrib.gis.tests.utils import postgis, mysql
# The test suite. # The test suite.
s = unittest.TestSuite() s = unittest.TestSuite()
@ -32,6 +33,10 @@ def geo_suite():
if not mysql: if not mysql:
test_apps.append('distapp') test_apps.append('distapp')
# Only PostGIS using GEOS 3.1+ can support 3D so far.
if postgis and GEOS_PREPARE:
test_apps.append('geo3d')
if HAS_GDAL: if HAS_GDAL:
# These tests require GDAL. # These tests require GDAL.
test_suite_names.extend(['test_spatialrefsys', 'test_geoforms']) test_suite_names.extend(['test_spatialrefsys', 'test_geoforms'])
@ -164,20 +169,3 @@ def run_tests(test_labels, verbosity=1, interactive=True, extra_tests=[], suite=
# Returning the total failures and errors # Returning the total failures and errors
return len(result.failures) + len(result.errors) return len(result.failures) + len(result.errors)
# Class for creating a fake module with a run method. This is for the
# GEOS and GDAL tests that were moved to their respective modules.
class _DeprecatedTestModule(object):
def __init__(self, mod_name):
self.mod_name = mod_name
def run(self):
from warnings import warn
warn('This test module is deprecated because it has moved to ' \
'`django.contrib.gis.%s.tests` and will disappear in 1.2.' %
self.mod_name, DeprecationWarning)
tests = import_module('django.contrib.gis.%s.tests' % self.mod_name)
tests.run()
test_geos = _DeprecatedTestModule('geos')
test_gdal = _DeprecatedTestModule('gdal')

View File

@ -1,7 +1,7 @@
<OGRVRTDataSource> <OGRVRTDataSource>
<OGRVRTLayer name="test_vrt"> <OGRVRTLayer name="test_vrt">
<SrcDataSource relativeToVRT="1">test_vrt.csv</SrcDataSource> <SrcDataSource relativeToVRT="1">test_vrt.csv</SrcDataSource>
<GeometryType>wkbPoint</GeometryType> <GeometryType>wkbPoint25D</GeometryType>
<GeometryField encoding="PointFromColumns" x="POINT_X" y="POINT_Y" z="NUM"/> <GeometryField encoding="PointFromColumns" x="POINT_X" y="POINT_Y" z="NUM"/>
</OGRVRTLayer> </OGRVRTLayer>
</OGRVRTDataSource> </OGRVRTDataSource>

View File

@ -0,0 +1,69 @@
from django.contrib.gis.db import models
class City3D(models.Model):
name = models.CharField(max_length=30)
point = models.PointField(dim=3)
objects = models.GeoManager()
def __unicode__(self):
return self.name
class Interstate2D(models.Model):
name = models.CharField(max_length=30)
line = models.LineStringField(srid=4269)
objects = models.GeoManager()
def __unicode__(self):
return self.name
class Interstate3D(models.Model):
name = models.CharField(max_length=30)
line = models.LineStringField(dim=3, srid=4269)
objects = models.GeoManager()
def __unicode__(self):
return self.name
class InterstateProj2D(models.Model):
name = models.CharField(max_length=30)
line = models.LineStringField(srid=32140)
objects = models.GeoManager()
def __unicode__(self):
return self.name
class InterstateProj3D(models.Model):
name = models.CharField(max_length=30)
line = models.LineStringField(dim=3, srid=32140)
objects = models.GeoManager()
def __unicode__(self):
return self.name
class Polygon2D(models.Model):
name = models.CharField(max_length=30)
poly = models.PolygonField(srid=32140)
objects = models.GeoManager()
def __unicode__(self):
return self.name
class Polygon3D(models.Model):
name = models.CharField(max_length=30)
poly = models.PolygonField(dim=3, srid=32140)
objects = models.GeoManager()
def __unicode__(self):
return self.name
class Point2D(models.Model):
point = models.PointField()
objects = models.GeoManager()
class Point3D(models.Model):
point = models.PointField(dim=3)
objects = models.GeoManager()
class MultiPoint3D(models.Model):
mpoint = models.MultiPointField(dim=3)
objects = models.GeoManager()

View File

@ -0,0 +1,234 @@
import os, re, unittest
from django.contrib.gis.db.models import Union, Extent3D
from django.contrib.gis.geos import GEOSGeometry, Point, Polygon
from django.contrib.gis.utils import LayerMapping, LayerMapError
from models import City3D, Interstate2D, Interstate3D, \
InterstateProj2D, InterstateProj3D, \
Point2D, Point3D, MultiPoint3D, Polygon2D, Polygon3D
data_path = os.path.realpath(os.path.join(os.path.dirname(__file__), '..', 'data'))
city_file = os.path.join(data_path, 'cities', 'cities.shp')
vrt_file = os.path.join(data_path, 'test_vrt', 'test_vrt.vrt')
# The coordinates of each city, with Z values corresponding to their
# altitude in meters.
city_data = (
('Houston', (-95.363151, 29.763374, 18)),
('Dallas', (-96.801611, 32.782057, 147)),
('Oklahoma City', (-97.521157, 34.464642, 380)),
('Wellington', (174.783117, -41.315268, 14)),
('Pueblo', (-104.609252, 38.255001, 1433)),
('Lawrence', (-95.235060, 38.971823, 251)),
('Chicago', (-87.650175, 41.850385, 181)),
('Victoria', (-123.305196, 48.462611, 15)),
)
# Reference mapping of city name to its altitude (Z value).
city_dict = dict((name, coords) for name, coords in city_data)
# 3D freeway data derived from the National Elevation Dataset:
# http://seamless.usgs.gov/products/9arc.php
interstate_data = (
('I-45',
'LINESTRING(-95.3708481 29.7765870 11.339,-95.3694580 29.7787980 4.536,-95.3690305 29.7797359 9.762,-95.3691886 29.7812450 12.448,-95.3696447 29.7850144 10.457,-95.3702511 29.7868518 9.418,-95.3706724 29.7881286 14.858,-95.3711632 29.7896157 15.386,-95.3714525 29.7936267 13.168,-95.3717848 29.7955007 15.104,-95.3717719 29.7969804 16.516,-95.3717305 29.7982117 13.923,-95.3717254 29.8000778 14.385,-95.3719875 29.8013539 15.160,-95.3720575 29.8026785 15.544,-95.3721321 29.8040912 14.975,-95.3722074 29.8050998 15.688,-95.3722779 29.8060430 16.099,-95.3733818 29.8076750 15.197,-95.3741563 29.8103686 17.268,-95.3749458 29.8129927 19.857,-95.3763564 29.8144557 15.435)',
( 11.339, 4.536, 9.762, 12.448, 10.457, 9.418, 14.858,
15.386, 13.168, 15.104, 16.516, 13.923, 14.385, 15.16 ,
15.544, 14.975, 15.688, 16.099, 15.197, 17.268, 19.857,
15.435),
),
)
# Bounding box polygon for inner-loop of Houston (in projected coordinate
# system 32140), with elevation values from the National Elevation Dataset
# (see above).
bbox_wkt = 'POLYGON((941527.97 4225693.20,962596.48 4226349.75,963152.57 4209023.95,942051.75 4208366.38,941527.97 4225693.20))'
bbox_z = (21.71, 13.21, 9.12, 16.40, 21.71)
def gen_bbox():
bbox_2d = GEOSGeometry(bbox_wkt, srid=32140)
bbox_3d = Polygon(tuple((x, y, z) for (x, y), z in zip(bbox_2d[0].coords, bbox_z)), srid=32140)
return bbox_2d, bbox_3d
class Geo3DTest(unittest.TestCase):
"""
Only a subset of the PostGIS routines are 3D-enabled, and this TestCase
tries to test the features that can handle 3D and that are also
available within GeoDjango. For more information, see the PostGIS docs
on the routines that support 3D:
http://postgis.refractions.net/documentation/manual-1.4/ch08.html#PostGIS_3D_Functions
"""
def test01_3d(self):
"Test the creation of 3D models."
# 3D models for the rest of the tests will be populated in here.
# For each 3D data set create model (and 2D version if necessary),
# retrieve, and assert geometry is in 3D and contains the expected
# 3D values.
for name, pnt_data in city_data:
x, y, z = pnt_data
pnt = Point(x, y, z, srid=4326)
City3D.objects.create(name=name, point=pnt)
city = City3D.objects.get(name=name)
self.failUnless(city.point.hasz)
self.assertEqual(z, city.point.z)
# Interstate (2D / 3D and Geographic/Projected variants)
for name, line, exp_z in interstate_data:
line_3d = GEOSGeometry(line, srid=4269)
# Using `hex` attribute because it omits 3D.
line_2d = GEOSGeometry(line_3d.hex, srid=4269)
# Creating a geographic and projected version of the
# interstate in both 2D and 3D.
Interstate3D.objects.create(name=name, line=line_3d)
InterstateProj3D.objects.create(name=name, line=line_3d)
Interstate2D.objects.create(name=name, line=line_2d)
InterstateProj2D.objects.create(name=name, line=line_2d)
# Retrieving and making sure it's 3D and has expected
# Z values -- shouldn't change because of coordinate system.
interstate = Interstate3D.objects.get(name=name)
interstate_proj = InterstateProj3D.objects.get(name=name)
for i in [interstate, interstate_proj]:
self.failUnless(i.line.hasz)
self.assertEqual(exp_z, tuple(i.line.z))
# Creating 3D Polygon.
bbox2d, bbox3d = gen_bbox()
Polygon2D.objects.create(name='2D BBox', poly=bbox2d)
Polygon3D.objects.create(name='3D BBox', poly=bbox3d)
p3d = Polygon3D.objects.get(name='3D BBox')
self.failUnless(p3d.poly.hasz)
self.assertEqual(bbox3d, p3d.poly)
def test01a_3d_layermapping(self):
"Testing LayerMapping on 3D models."
from models import Point2D, Point3D
point_mapping = {'point' : 'POINT'}
mpoint_mapping = {'mpoint' : 'MULTIPOINT'}
# The VRT is 3D, but should still be able to map sans the Z.
lm = LayerMapping(Point2D, vrt_file, point_mapping, transform=False)
lm.save()
self.assertEqual(3, Point2D.objects.count())
# The city shapefile is 2D, and won't be able to fill the coordinates
# in the 3D model -- thus, a LayerMapError is raised.
self.assertRaises(LayerMapError, LayerMapping,
Point3D, city_file, point_mapping, transform=False)
# 3D model should take 3D data just fine.
lm = LayerMapping(Point3D, vrt_file, point_mapping, transform=False)
lm.save()
self.assertEqual(3, Point3D.objects.count())
# Making sure LayerMapping.make_multi works right, by converting
# a Point25D into a MultiPoint25D.
lm = LayerMapping(MultiPoint3D, vrt_file, mpoint_mapping, transform=False)
lm.save()
self.assertEqual(3, MultiPoint3D.objects.count())
def test02a_kml(self):
"Test GeoQuerySet.kml() with Z values."
h = City3D.objects.kml(precision=6).get(name='Houston')
# KML should be 3D.
# `SELECT ST_AsKML(point, 6) FROM geo3d_city3d WHERE name = 'Houston';`
ref_kml_regex = re.compile(r'^<Point><coordinates>-95.363\d+,29.763\d+,18</coordinates></Point>$')
self.failUnless(ref_kml_regex.match(h.kml))
def test02b_geojson(self):
"Test GeoQuerySet.geojson() with Z values."
h = City3D.objects.geojson(precision=6).get(name='Houston')
# GeoJSON should be 3D
# `SELECT ST_AsGeoJSON(point, 6) FROM geo3d_city3d WHERE name='Houston';`
ref_json_regex = re.compile(r'^{"type":"Point","coordinates":\[-95.363151,29.763374,18(\.0+)?\]}$')
self.failUnless(ref_json_regex.match(h.geojson))
def test03a_union(self):
"Testing the Union aggregate of 3D models."
# PostGIS query that returned the reference EWKT for this test:
# `SELECT ST_AsText(ST_Union(point)) FROM geo3d_city3d;`
ref_ewkt = 'SRID=4326;MULTIPOINT(-123.305196 48.462611 15,-104.609252 38.255001 1433,-97.521157 34.464642 380,-96.801611 32.782057 147,-95.363151 29.763374 18,-95.23506 38.971823 251,-87.650175 41.850385 181,174.783117 -41.315268 14)'
ref_union = GEOSGeometry(ref_ewkt)
union = City3D.objects.aggregate(Union('point'))['point__union']
self.failUnless(union.hasz)
self.assertEqual(ref_union, union)
def test03b_extent(self):
"Testing the Extent3D aggregate for 3D models."
# `SELECT ST_Extent3D(point) FROM geo3d_city3d;`
ref_extent3d = (-123.305196, -41.315268, 14,174.783117, 48.462611, 1433)
extent1 = City3D.objects.aggregate(Extent3D('point'))['point__extent3d']
extent2 = City3D.objects.extent3d()
def check_extent3d(extent3d, tol=6):
for ref_val, ext_val in zip(ref_extent3d, extent3d):
self.assertAlmostEqual(ref_val, ext_val, tol)
for e3d in [extent1, extent2]:
check_extent3d(e3d)
def test04_perimeter(self):
"Testing GeoQuerySet.perimeter() on 3D fields."
# Reference query for values below:
# `SELECT ST_Perimeter3D(poly), ST_Perimeter2D(poly) FROM geo3d_polygon3d;`
ref_perim_3d = 76859.2620451
ref_perim_2d = 76859.2577803
tol = 6
self.assertAlmostEqual(ref_perim_2d,
Polygon2D.objects.perimeter().get(name='2D BBox').perimeter.m,
tol)
self.assertAlmostEqual(ref_perim_3d,
Polygon3D.objects.perimeter().get(name='3D BBox').perimeter.m,
tol)
def test05_length(self):
"Testing GeoQuerySet.length() on 3D fields."
# ST_Length_Spheroid Z-aware, and thus does not need to use
# a separate function internally.
# `SELECT ST_Length_Spheroid(line, 'SPHEROID["GRS 1980",6378137,298.257222101]')
# FROM geo3d_interstate[2d|3d];`
tol = 3
ref_length_2d = 4368.1721949481
ref_length_3d = 4368.62547052088
self.assertAlmostEqual(ref_length_2d,
Interstate2D.objects.length().get(name='I-45').length.m,
tol)
self.assertAlmostEqual(ref_length_3d,
Interstate3D.objects.length().get(name='I-45').length.m,
tol)
# Making sure `ST_Length3D` is used on for a projected
# and 3D model rather than `ST_Length`.
# `SELECT ST_Length(line) FROM geo3d_interstateproj2d;`
ref_length_2d = 4367.71564892392
# `SELECT ST_Length3D(line) FROM geo3d_interstateproj3d;`
ref_length_3d = 4368.16897234101
self.assertAlmostEqual(ref_length_2d,
InterstateProj2D.objects.length().get(name='I-45').length.m,
tol)
self.assertAlmostEqual(ref_length_3d,
InterstateProj3D.objects.length().get(name='I-45').length.m,
tol)
def test06_scale(self):
"Testing GeoQuerySet.scale() on Z values."
# Mapping of City name to reference Z values.
zscales = (-3, 4, 23)
for zscale in zscales:
for city in City3D.objects.scale(1.0, 1.0, zscale):
self.assertEqual(city_dict[city.name][2] * zscale, city.scale.z)
def test07_translate(self):
"Testing GeoQuerySet.translate() on Z values."
ztranslations = (5.23, 23, -17)
for ztrans in ztranslations:
for city in City3D.objects.translate(0, 0, ztrans):
self.assertEqual(city_dict[city.name][2] + ztrans, city.translate.z)
def suite():
s = unittest.TestSuite()
s.addTest(unittest.makeSuite(Geo3DTest))
return s

View File

@ -0,0 +1 @@
# Create your views here.

View File

@ -33,4 +33,6 @@ class GeoRegressionTests(unittest.TestCase):
"Testing `extent` on a table with a single point, see #11827." "Testing `extent` on a table with a single point, see #11827."
pnt = City.objects.get(name='Pueblo').point pnt = City.objects.get(name='Pueblo').point
ref_ext = (pnt.x, pnt.y, pnt.x, pnt.y) ref_ext = (pnt.x, pnt.y, pnt.x, pnt.y)
self.assertEqual(ref_ext, City.objects.filter(name='Pueblo').extent()) extent = City.objects.filter(name='Pueblo').extent()
for ref_val, val in zip(ref_ext, extent):
self.assertAlmostEqual(ref_val, val, 4)

View File

@ -171,3 +171,10 @@ json_geoms = (TestGeom('POINT(100 0)', json='{ "type": "Point", "coordinates": [
not_equal=True, not_equal=True,
), ),
) )
# For testing HEX(EWKB).
ogc_hex = '01010000000000000000000000000000000000F03F'
# `SELECT ST_AsHEXEWKB(ST_GeomFromText('POINT(0 1)', 4326));`
hexewkb_2d = '0101000020E61000000000000000000000000000000000F03F'
# `SELECT ST_AsHEXEWKB(ST_GeomFromEWKT('SRID=4326;POINT(0 1 2)'));`
hexewkb_3d = '01010000A0E61000000000000000000000000000000000F03F0000000000000040'

View File

@ -10,7 +10,7 @@ if HAS_GDAL:
try: try:
# LayerMapping requires DJANGO_SETTINGS_MODULE to be set, # LayerMapping requires DJANGO_SETTINGS_MODULE to be set,
# so this needs to be in try/except. # so this needs to be in try/except.
from django.contrib.gis.utils.layermapping import LayerMapping from django.contrib.gis.utils.layermapping import LayerMapping, LayerMapError
except: except:
pass pass

View File

@ -133,6 +133,9 @@ class LayerMapping(object):
MULTI_TYPES = {1 : OGRGeomType('MultiPoint'), MULTI_TYPES = {1 : OGRGeomType('MultiPoint'),
2 : OGRGeomType('MultiLineString'), 2 : OGRGeomType('MultiLineString'),
3 : OGRGeomType('MultiPolygon'), 3 : OGRGeomType('MultiPolygon'),
OGRGeomType('Point25D').num : OGRGeomType('MultiPoint25D'),
OGRGeomType('LineString25D').num : OGRGeomType('MultiLineString25D'),
OGRGeomType('Polygon25D').num : OGRGeomType('MultiPolygon25D'),
} }
# Acceptable Django field types and corresponding acceptable OGR # Acceptable Django field types and corresponding acceptable OGR
@ -282,19 +285,28 @@ class LayerMapping(object):
if self.geom_field: if self.geom_field:
raise LayerMapError('LayerMapping does not support more than one GeometryField per model.') raise LayerMapError('LayerMapping does not support more than one GeometryField per model.')
# Getting the coordinate dimension of the geometry field.
coord_dim = model_field.dim
try: try:
gtype = OGRGeomType(ogr_name) if coord_dim == 3:
gtype = OGRGeomType(ogr_name + '25D')
else:
gtype = OGRGeomType(ogr_name)
except OGRException: except OGRException:
raise LayerMapError('Invalid mapping for GeometryField "%s".' % field_name) raise LayerMapError('Invalid mapping for GeometryField "%s".' % field_name)
# Making sure that the OGR Layer's Geometry is compatible. # Making sure that the OGR Layer's Geometry is compatible.
ltype = self.layer.geom_type ltype = self.layer.geom_type
if not (gtype == ltype or self.make_multi(ltype, model_field)): if not (ltype.name.startswith(gtype.name) or self.make_multi(ltype, model_field)):
raise LayerMapError('Invalid mapping geometry; model has %s, feature has %s.' % (fld_name, gtype)) raise LayerMapError('Invalid mapping geometry; model has %s%s, layer is %s.' %
(fld_name, (coord_dim == 3 and '(dim=3)') or '', ltype))
# Setting the `geom_field` attribute w/the name of the model field # Setting the `geom_field` attribute w/the name of the model field
# that is a Geometry. # that is a Geometry. Also setting the coordinate dimension
# attribute.
self.geom_field = field_name self.geom_field = field_name
self.coord_dim = coord_dim
fields_val = model_field fields_val = model_field
elif isinstance(model_field, models.ForeignKey): elif isinstance(model_field, models.ForeignKey):
if isinstance(ogr_name, dict): if isinstance(ogr_name, dict):
@ -482,6 +494,10 @@ class LayerMapping(object):
if necessary (for example if the model field is MultiPolygonField while if necessary (for example if the model field is MultiPolygonField while
the mapped shapefile only contains Polygons). the mapped shapefile only contains Polygons).
""" """
# Downgrade a 3D geom to a 2D one, if necessary.
if self.coord_dim != geom.coord_dim:
geom.coord_dim = self.coord_dim
if self.make_multi(geom.geom_type, model_field): if self.make_multi(geom.geom_type, model_field):
# Constructing a multi-geometry type to contain the single geometry # Constructing a multi-geometry type to contain the single geometry
multi_type = self.MULTI_TYPES[geom.geom_type.num] multi_type = self.MULTI_TYPES[geom.geom_type.num]

View File

@ -68,6 +68,9 @@ class BaseHandler(object):
from django.core import exceptions, urlresolvers from django.core import exceptions, urlresolvers
from django.conf import settings from django.conf import settings
# Reset the urlconf for this thread.
urlresolvers.set_urlconf(None)
# Apply request middleware # Apply request middleware
for middleware_method in self._request_middleware: for middleware_method in self._request_middleware:
response = middleware_method(request) response = middleware_method(request)
@ -77,61 +80,69 @@ class BaseHandler(object):
# Get urlconf from request object, if available. Otherwise use default. # Get urlconf from request object, if available. Otherwise use default.
urlconf = getattr(request, "urlconf", settings.ROOT_URLCONF) urlconf = getattr(request, "urlconf", settings.ROOT_URLCONF)
# Set the urlconf for this thread to the one specified above.
urlresolvers.set_urlconf(urlconf)
resolver = urlresolvers.RegexURLResolver(r'^/', urlconf) resolver = urlresolvers.RegexURLResolver(r'^/', urlconf)
try: try:
callback, callback_args, callback_kwargs = resolver.resolve(
request.path_info)
# Apply view middleware
for middleware_method in self._view_middleware:
response = middleware_method(request, callback, callback_args, callback_kwargs)
if response:
return response
try: try:
response = callback(request, *callback_args, **callback_kwargs) callback, callback_args, callback_kwargs = resolver.resolve(
except Exception, e: request.path_info)
# If the view raised an exception, run it through exception
# middleware, and if the exception middleware returns a # Apply view middleware
# response, use that. Otherwise, reraise the exception. for middleware_method in self._view_middleware:
for middleware_method in self._exception_middleware: response = middleware_method(request, callback, callback_args, callback_kwargs)
response = middleware_method(request, e)
if response: if response:
return response return response
raise
# Complain if the view returned None (a common error).
if response is None:
try: try:
view_name = callback.func_name # If it's a function response = callback(request, *callback_args, **callback_kwargs)
except AttributeError: except Exception, e:
view_name = callback.__class__.__name__ + '.__call__' # If it's a class # If the view raised an exception, run it through exception
raise ValueError, "The view %s.%s didn't return an HttpResponse object." % (callback.__module__, view_name) # middleware, and if the exception middleware returns a
# response, use that. Otherwise, reraise the exception.
for middleware_method in self._exception_middleware:
response = middleware_method(request, e)
if response:
return response
raise
return response # Complain if the view returned None (a common error).
except http.Http404, e: if response is None:
if settings.DEBUG:
from django.views import debug
return debug.technical_404_response(request, e)
else:
try:
callback, param_dict = resolver.resolve404()
return callback(request, **param_dict)
except:
try: try:
return self.handle_uncaught_exception(request, resolver, sys.exc_info()) view_name = callback.func_name # If it's a function
finally: except AttributeError:
receivers = signals.got_request_exception.send(sender=self.__class__, request=request) view_name = callback.__class__.__name__ + '.__call__' # If it's a class
except exceptions.PermissionDenied: raise ValueError, "The view %s.%s didn't return an HttpResponse object." % (callback.__module__, view_name)
return http.HttpResponseForbidden('<h1>Permission denied</h1>')
except SystemExit: return response
# Allow sys.exit() to actually exit. See tickets #1023 and #4701 except http.Http404, e:
raise if settings.DEBUG:
except: # Handle everything else, including SuspiciousOperation, etc. from django.views import debug
# Get the exception info now, in case another exception is thrown later. return debug.technical_404_response(request, e)
exc_info = sys.exc_info() else:
receivers = signals.got_request_exception.send(sender=self.__class__, request=request) try:
return self.handle_uncaught_exception(request, resolver, exc_info) callback, param_dict = resolver.resolve404()
return callback(request, **param_dict)
except:
try:
return self.handle_uncaught_exception(request, resolver, sys.exc_info())
finally:
receivers = signals.got_request_exception.send(sender=self.__class__, request=request)
except exceptions.PermissionDenied:
return http.HttpResponseForbidden('<h1>Permission denied</h1>')
except SystemExit:
# Allow sys.exit() to actually exit. See tickets #1023 and #4701
raise
except: # Handle everything else, including SuspiciousOperation, etc.
# Get the exception info now, in case another exception is thrown later.
exc_info = sys.exc_info()
receivers = signals.got_request_exception.send(sender=self.__class__, request=request)
return self.handle_uncaught_exception(request, resolver, exc_info)
finally:
# Reset URLconf for this thread on the way out for complete
# isolation of request.urlconf
urlresolvers.set_urlconf(None)
def handle_uncaught_exception(self, request, resolver, exc_info): def handle_uncaught_exception(self, request, resolver, exc_info):
""" """

View File

@ -105,6 +105,6 @@ class SMTPConnection(_SMTPConnection):
import warnings import warnings
warnings.warn( warnings.warn(
'mail.SMTPConnection is deprecated; use mail.get_connection() instead.', 'mail.SMTPConnection is deprecated; use mail.get_connection() instead.',
DeprecationWarning PendingDeprecationWarning
) )
super(SMTPConnection, self).__init__(*args, **kwds) super(SMTPConnection, self).__init__(*args, **kwds)

View File

@ -299,7 +299,7 @@ class ManagementUtility(object):
# subcommand # subcommand
if cword == 1: if cword == 1:
print ' '.join(filter(lambda x: x.startswith(curr), subcommands)) print ' '.join(sorted(filter(lambda x: x.startswith(curr), subcommands)))
# subcommand options # subcommand options
# special case: the 'help' subcommand has no options # special case: the 'help' subcommand has no options
elif cwords[0] in subcommands and cwords[0] != 'help': elif cwords[0] in subcommands and cwords[0] != 'help':
@ -328,7 +328,7 @@ class ManagementUtility(object):
options = filter(lambda (x, v): x not in prev_opts, options) options = filter(lambda (x, v): x not in prev_opts, options)
# filter options by current input # filter options by current input
options = [(k, v) for k, v in options if k.startswith(curr)] options = sorted([(k, v) for k, v in options if k.startswith(curr)])
for option in options: for option in options:
opt_label = option[0] opt_label = option[0]
# append '=' to options which require args # append '=' to options which require args

View File

@ -65,7 +65,7 @@ class Command(NoArgsCommand):
opts = model._meta opts = model._meta
if (connection.introspection.table_name_converter(opts.db_table) in tables or if (connection.introspection.table_name_converter(opts.db_table) in tables or
(opts.auto_created and (opts.auto_created and
connection.introspection.table_name_converter(opts.auto_created._meta.db_table in tables))): connection.introspection.table_name_converter(opts.auto_created._meta.db_table) in tables)):
continue continue
sql, references = connection.creation.sql_create_model(model, self.style, seen_models) sql, references = connection.creation.sql_create_model(model, self.style, seen_models)
seen_models.add(model) seen_models.add(model)

View File

@ -10,6 +10,7 @@ a string) and returns a tuple in this format:
import re import re
from django.http import Http404 from django.http import Http404
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist
from django.utils.datastructures import MultiValueDict from django.utils.datastructures import MultiValueDict
from django.utils.encoding import iri_to_uri, force_unicode, smart_str from django.utils.encoding import iri_to_uri, force_unicode, smart_str
@ -32,6 +33,9 @@ _callable_cache = {} # Maps view and url pattern names to their view functions.
# be empty. # be empty.
_prefixes = {} _prefixes = {}
# Overridden URLconfs for each thread are stored here.
_urlconfs = {}
class Resolver404(Http404): class Resolver404(Http404):
pass pass
@ -300,9 +304,13 @@ class RegexURLResolver(object):
"arguments '%s' not found." % (lookup_view_s, args, kwargs)) "arguments '%s' not found." % (lookup_view_s, args, kwargs))
def resolve(path, urlconf=None): def resolve(path, urlconf=None):
if urlconf is None:
urlconf = get_urlconf()
return get_resolver(urlconf).resolve(path) return get_resolver(urlconf).resolve(path)
def reverse(viewname, urlconf=None, args=None, kwargs=None, prefix=None, current_app=None): def reverse(viewname, urlconf=None, args=None, kwargs=None, prefix=None, current_app=None):
if urlconf is None:
urlconf = get_urlconf()
resolver = get_resolver(urlconf) resolver = get_resolver(urlconf)
args = args or [] args = args or []
kwargs = kwargs or {} kwargs = kwargs or {}
@ -371,3 +379,25 @@ def get_script_prefix():
""" """
return _prefixes.get(currentThread(), u'/') return _prefixes.get(currentThread(), u'/')
def set_urlconf(urlconf_name):
"""
Sets the URLconf for the current thread (overriding the default one in
settings). Set to None to revert back to the default.
"""
thread = currentThread()
if urlconf_name:
_urlconfs[thread] = urlconf_name
else:
# faster than wrapping in a try/except
if thread in _urlconfs:
del _urlconfs[thread]
def get_urlconf(default=None):
"""
Returns the root URLconf to use for the current thread if it has been
changed from the default one.
"""
thread = currentThread()
if thread in _urlconfs:
return _urlconfs[thread]
return default

View File

@ -364,6 +364,8 @@ class Model(object):
defers = [] defers = []
pk_val = None pk_val = None
if self._deferred: if self._deferred:
from django.db.models.query_utils import deferred_class_factory
factory = deferred_class_factory
for field in self._meta.fields: for field in self._meta.fields:
if isinstance(self.__class__.__dict__.get(field.attname), if isinstance(self.__class__.__dict__.get(field.attname),
DeferredAttribute): DeferredAttribute):
@ -374,8 +376,9 @@ class Model(object):
# once. # once.
obj = self.__class__.__dict__[field.attname] obj = self.__class__.__dict__[field.attname]
model = obj.model_ref() model = obj.model_ref()
else:
return (model_unpickle, (model, defers), data) factory = simple_class_factory
return (model_unpickle, (model, defers, factory), data)
def _get_pk_val(self, meta=None): def _get_pk_val(self, meta=None):
if not meta: if not meta:
@ -849,12 +852,20 @@ def get_absolute_url(opts, func, self, *args, **kwargs):
class Empty(object): class Empty(object):
pass pass
def model_unpickle(model, attrs): def simple_class_factory(model, attrs):
"""Used to unpickle Models without deferred fields.
We need to do this the hard way, rather than just using
the default __reduce__ implementation, because of a
__deepcopy__ problem in Python 2.4
"""
return model
def model_unpickle(model, attrs, factory):
""" """
Used to unpickle Model subclasses with deferred fields. Used to unpickle Model subclasses with deferred fields.
""" """
from django.db.models.query_utils import deferred_class_factory cls = factory(model, attrs)
cls = deferred_class_factory(model, attrs)
return cls.__new__(cls) return cls.__new__(cls)
model_unpickle.__safe_for_unpickle__ = True model_unpickle.__safe_for_unpickle__ = True

View File

@ -586,9 +586,13 @@ class ReverseManyRelatedObjectsDescriptor(object):
# ReverseManyRelatedObjectsDescriptor instance. # ReverseManyRelatedObjectsDescriptor instance.
def __init__(self, m2m_field): def __init__(self, m2m_field):
self.field = m2m_field self.field = m2m_field
def _through(self):
# through is provided so that you have easy access to the through # through is provided so that you have easy access to the through
# model (Book.authors.through) for inlines, etc. # model (Book.authors.through) for inlines, etc. This is done as
self.through = m2m_field.rel.through # a property to ensure that the fully resolved value is returned.
return self.field.rel.through
through = property(_through)
def __get__(self, instance, instance_type=None): def __get__(self, instance, instance_type=None):
if instance is None: if instance is None:
@ -698,6 +702,10 @@ class ForeignKey(RelatedField, Field):
assert isinstance(to, basestring), "%s(%r) is invalid. First parameter to ForeignKey must be either a model, a model name, or the string %r" % (self.__class__.__name__, to, RECURSIVE_RELATIONSHIP_CONSTANT) assert isinstance(to, basestring), "%s(%r) is invalid. First parameter to ForeignKey must be either a model, a model name, or the string %r" % (self.__class__.__name__, to, RECURSIVE_RELATIONSHIP_CONSTANT)
else: else:
assert not to._meta.abstract, "%s cannot define a relation with abstract class %s" % (self.__class__.__name__, to._meta.object_name) assert not to._meta.abstract, "%s cannot define a relation with abstract class %s" % (self.__class__.__name__, to._meta.object_name)
# For backwards compatibility purposes, we need to *try* and set
# the to_field during FK construction. It won't be guaranteed to
# be correct until contribute_to_class is called. Refs #12190.
to_field = to_field or (to._meta.pk and to._meta.pk.name)
kwargs['verbose_name'] = kwargs.get('verbose_name', None) kwargs['verbose_name'] = kwargs.get('verbose_name', None)
kwargs['rel'] = rel_class(to, to_field, kwargs['rel'] = rel_class(to, to_field,
@ -849,20 +857,13 @@ def create_many_to_many_intermediary_model(field, klass):
'db_table': field._get_m2m_db_table(klass._meta), 'db_table': field._get_m2m_db_table(klass._meta),
'managed': managed, 'managed': managed,
'auto_created': klass, 'auto_created': klass,
'app_label': klass._meta.app_label,
'unique_together': (from_, to) 'unique_together': (from_, to)
}) })
# If the models have been split into subpackages, klass.__module__
# will be the subpackge, not the models module for the app. (See #12168)
# Compose the actual models module name by stripping the trailing parts
# of the namespace until we find .models
parts = klass.__module__.split('.')
while parts[-1] != 'models':
parts.pop()
module = '.'.join(parts)
# Construct and return the new class. # Construct and return the new class.
return type(name, (models.Model,), { return type(name, (models.Model,), {
'Meta': meta, 'Meta': meta,
'__module__': module, '__module__': klass.__module__,
from_: models.ForeignKey(klass, related_name='%s+' % name), from_: models.ForeignKey(klass, related_name='%s+' % name),
to: models.ForeignKey(to_model, related_name='%s+' % name) to: models.ForeignKey(to_model, related_name='%s+' % name)
}) })

View File

@ -409,7 +409,7 @@ class DateQuery(Query):
self.select = [select] self.select = [select]
self.select_fields = [None] self.select_fields = [None]
self.select_related = False # See #7097. self.select_related = False # See #7097.
self.extra = {} self.set_extra_mask([])
self.distinct = True self.distinct = True
self.order_by = order == 'ASC' and [1] or [-1] self.order_by = order == 'ASC' and [1] or [-1]

View File

@ -201,7 +201,5 @@ The Django open-source project
* **Django over time:** * **Django over time:**
:ref:`API stability <misc-api-stability>` | :ref:`API stability <misc-api-stability>` |
:ref:`Archive of release notes <releases-index>` | `Backwards-incompatible changes`_ | :ref:`Release notes and upgrading instructions <releases-index>` |
:ref:`Deprecation Timeline <internals-deprecation>` :ref:`Deprecation Timeline <internals-deprecation>`
.. _Backwards-incompatible changes: http://code.djangoproject.com/wiki/BackwardsIncompatibleChanges

View File

@ -200,6 +200,19 @@ Karen Tracey
.. _Bauhaus-University Weimar: http://www.uni-weimar.de/ .. _Bauhaus-University Weimar: http://www.uni-weimar.de/
.. _pinax: http://pinaxproject.com/ .. _pinax: http://pinaxproject.com/
`James Tauber`_
James is the lead developer of Pinax_ and the CEO and founder of
Eldarion_. He has been doing open source software since 1993, Python
since 1998 and Django since 2006. He serves on the board of the Python
Software Foundation and is currently on a leave of absence from a PhD in
linguistics.
James currently lives in Boston, MA, USA but originally hails from
Perth, Western Australia where he attended the same high school as
Russell Keith-Magee.
.. _James Tauber: http://jtauber.com/
Specialists Specialists
----------- -----------

View File

@ -46,7 +46,7 @@ To enable CSRF protection for your views, follow these steps:
``django.views.decorators.csrf.csrf_protect`` on particular views you ``django.views.decorators.csrf.csrf_protect`` on particular views you
want to protect (see below). want to protect (see below).
2. In any template that uses a POST form, use the ``csrf_token`` tag inside 2. In any template that uses a POST form, use the :ttag:`csrf_token` tag inside
the ``<form>`` element if the form is for an internal URL, e.g.:: the ``<form>`` element if the form is for an internal URL, e.g.::
<form action="" method="POST">{% csrf_token %} <form action="" method="POST">{% csrf_token %}
@ -123,14 +123,14 @@ as ``CsrfResponseMiddleware``, and it can be used by following these steps:
``CsrfResponseMiddleware`` needs to process the response before things ``CsrfResponseMiddleware`` needs to process the response before things
like compression or setting ofETags happen to the response, so it must like compression or setting ofETags happen to the response, so it must
come after ``GZipMiddleware``, ``CommonMiddleware`` and come after ``GZipMiddleware``, ``CommonMiddleware`` and
``ConditionalGetMiddleware`` in the list. It also must come after ``ConditionalGetMiddleware`` in the list. It also must come after
``CsrfViewMiddleware``. ``CsrfViewMiddleware``.
Use of the ``CsrfResponseMiddleware`` is not recommended because of the Use of the ``CsrfResponseMiddleware`` is not recommended because of the
performance hit it imposes, and because of a potential security problem (see performance hit it imposes, and because of a potential security problem (see
below). It can be used as an interim measure until applications have been below). It can be used as an interim measure until applications have been
updated to use the ``{% csrf_token %}`` tag. It is deprecated and will be updated to use the :ttag:`csrf_token` tag. It is deprecated and will be
removed in Django 1.4. removed in Django 1.4.
Django 1.1 and earlier provided a single ``CsrfMiddleware`` class. This is also Django 1.1 and earlier provided a single ``CsrfMiddleware`` class. This is also
@ -153,6 +153,8 @@ launch a CSRF attack on your site against that user. The
``@csrf_response_exempt`` decorator can be used to fix this, but only if the ``@csrf_response_exempt`` decorator can be used to fix this, but only if the
page doesn't also contain internal forms that require the token. page doesn't also contain internal forms that require the token.
.. _ref-csrf-upgrading-notes:
Upgrading notes Upgrading notes
--------------- ---------------
@ -199,7 +201,7 @@ Note that contrib apps, such as the admin, have been updated to use the
``CsrfViewMiddleware`` to your settings. However, if you have supplied ``CsrfViewMiddleware`` to your settings. However, if you have supplied
customised templates to any of the view functions of contrib apps (whether customised templates to any of the view functions of contrib apps (whether
explicitly via a keyword argument, or by overriding built-in templates), **you explicitly via a keyword argument, or by overriding built-in templates), **you
MUST update them** to include the ``csrf_token`` template tag as described MUST update them** to include the :ttag:`csrf_token` template tag as described
above, or they will stop working. (If you cannot update these templates for above, or they will stop working. (If you cannot update these templates for
some reason, you will be forced to use ``CsrfResponseMiddleware`` for these some reason, you will be forced to use ``CsrfResponseMiddleware`` for these
views to continue working). views to continue working).
@ -364,7 +366,7 @@ exactly that.
Caching Caching
======= =======
If the ``csrf_token`` template tag is used by a template (or the ``get_token`` If the :ttag:`csrf_token` template tag is used by a template (or the ``get_token``
function is called some other way), ``CsrfViewMiddleware`` will add a cookie and function is called some other way), ``CsrfViewMiddleware`` will add a cookie and
a ``Vary: Cookie`` header to the response. Similarly, a ``Vary: Cookie`` header to the response. Similarly,
``CsrfResponseMiddleware`` will send the ``Vary: Cookie`` header if it inserted ``CsrfResponseMiddleware`` will send the ``Vary: Cookie`` header if it inserted

View File

@ -78,11 +78,9 @@ Examples of output::
Displaying debug output Displaying debug output
----------------------- -----------------------
.. django-admin-option:: --verbosity <amount> Use :djadminopt:`--verbosity` to specify the amount of notification and debug information
Use ``--verbosity`` to specify the amount of notification and debug information
that ``django-admin.py`` should print to the console. For more details, see the that ``django-admin.py`` should print to the console. For more details, see the
documentation for the :ref:`default options for django-admin.py <django-admin-verbosity>`. documentation for the :djadminopt:`--verbosity` option.
Available subcommands Available subcommands
===================== =====================
@ -90,6 +88,8 @@ Available subcommands
cleanup cleanup
------- -------
.. django-admin:: cleanup
.. versionadded:: 1.0 .. versionadded:: 1.0
Can be run as a cronjob or directly to clean out old data from the database Can be run as a cronjob or directly to clean out old data from the database
@ -98,17 +98,16 @@ Can be run as a cronjob or directly to clean out old data from the database
compilemessages compilemessages
--------------- ---------------
.. django-admin:: compilemessages
.. versionchanged:: 1.0 .. versionchanged:: 1.0
Before 1.0 this was the "bin/compile-messages.py" command. Before 1.0 this was the "bin/compile-messages.py" command.
Compiles .po files created with ``makemessages`` to .mo files for use with Compiles .po files created with ``makemessages`` to .mo files for use with
the builtin gettext support. See :ref:`topics-i18n`. the builtin gettext support. See :ref:`topics-i18n`.
--locale Use the :djadminopt:`--locale`` option to specify the locale to process.
~~~~~~~~ If not provided, all locales are processed.
Use the ``--locale`` or ``-l`` option to specify the locale to process.
If not provided all locales are processed.
Example usage:: Example usage::
@ -117,7 +116,7 @@ Example usage::
createcachetable createcachetable
---------------- ----------------
.. django-admin:: createcachetable <tablename> .. django-admin:: createcachetable
Creates a cache table named ``tablename`` for use with the database cache Creates a cache table named ``tablename`` for use with the database cache
backend. See :ref:`topics-cache` for more information. backend. See :ref:`topics-cache` for more information.
@ -183,10 +182,10 @@ example, the default settings don't define ``ROOT_URLCONF``, so
Note that Django's default settings live in ``django/conf/global_settings.py``, Note that Django's default settings live in ``django/conf/global_settings.py``,
if you're ever curious to see the full list of defaults. if you're ever curious to see the full list of defaults.
dumpdata dumpdata <appname appname appname.Model ...>
-------- --------------------------------------------
.. django-admin:: dumpdata <appname appname appname.Model ...> .. django-admin:: dumpdata
Outputs to standard output all data in the database associated with the named Outputs to standard output all data in the database associated with the named
application(s). application(s).
@ -215,18 +214,17 @@ directives::
django-admin.py dumpdata --exclude=auth --exclude=contenttypes django-admin.py dumpdata --exclude=auth --exclude=contenttypes
.. django-admin-option:: --format <fmt> .. django-admin-option:: --format <fmt>
By default, ``dumpdata`` will format its output in JSON, but you can use the By default, ``dumpdata`` will format its output in JSON, but you can use the
``--format`` option to specify another format. Currently supported formats ``--format`` option to specify another format. Currently supported formats
are listed in :ref:`serialization-formats`. are listed in :ref:`serialization-formats`.
.. django-admin-option:: --indent <num> .. django-admin-option:: --indent <num>
By default, ``dumpdata`` will output all data on a single line. This isn't By default, ``dumpdata`` will output all data on a single line. This isn't
easy for humans to read, so you can use the ``--indent`` option to easy for humans to read, so you can use the ``--indent`` option to
pretty-print the output with a number of indentation spaces. pretty-print the output with a number of indentation spaces.
.. versionadded:: 1.1 .. versionadded:: 1.1
@ -239,22 +237,21 @@ model names.
flush flush
----- -----
.. django-admin: flush .. django-admin:: flush
Returns the database to the state it was in immediately after syncdb was Returns the database to the state it was in immediately after syncdb was
executed. This means that all data will be removed from the database, any executed. This means that all data will be removed from the database, any
post-synchronization handlers will be re-executed, and the ``initial_data`` post-synchronization handlers will be re-executed, and the ``initial_data``
fixture will be re-installed. fixture will be re-installed.
.. django-admin-option:: --noinput The :djadminopt:`--noinput` option may be provided to suppress all user
prompts.
Use the ``--noinput`` option to suppress all user prompting, such as "Are
you sure?" confirmation messages. This is useful if ``django-admin.py`` is
being executed as an unattended, automated script.
inspectdb inspectdb
--------- ---------
.. django-admin:: inspectdb
Introspects the database tables in the database pointed-to by the Introspects the database tables in the database pointed-to by the
``DATABASE_NAME`` setting and outputs a Django model module (a ``models.py`` ``DATABASE_NAME`` setting and outputs a Django model module (a ``models.py``
file) to standard output. file) to standard output.
@ -296,6 +293,8 @@ only works in PostgreSQL and with certain types of MySQL tables.
loaddata <fixture fixture ...> loaddata <fixture fixture ...>
------------------------------ ------------------------------
.. django-admin:: loaddata
Searches for and loads the contents of the named fixture into the database. Searches for and loads the contents of the named fixture into the database.
What's a "fixture"? What's a "fixture"?
@ -382,6 +381,8 @@ installation will be aborted, and any data installed in the call to
makemessages makemessages
------------ ------------
.. django-admin:: makemessages
.. versionchanged:: 1.0 .. versionchanged:: 1.0
Before 1.0 this was the ``bin/make-messages.py`` command. Before 1.0 this was the ``bin/make-messages.py`` command.
@ -392,8 +393,7 @@ directory. After making changes to the messages files you need to compile them
with ``compilemessages`` for use with the builtin gettext support. See the with ``compilemessages`` for use with the builtin gettext support. See the
:ref:`i18n documentation <how-to-create-language-files>` for details. :ref:`i18n documentation <how-to-create-language-files>` for details.
--all .. django-admin-option:: --all
~~~~~
Use the ``--all`` or ``-a`` option to update the message files for all Use the ``--all`` or ``-a`` option to update the message files for all
available languages. available languages.
@ -402,8 +402,7 @@ Example usage::
django-admin.py makemessages --all django-admin.py makemessages --all
--extension .. django-admin-option:: --extension
~~~~~~~~~~~
Use the ``--extension`` or ``-e`` option to specify a list of file extensions Use the ``--extension`` or ``-e`` option to specify a list of file extensions
to examine (default: ".html"). to examine (default: ".html").
@ -416,17 +415,13 @@ Separate multiple extensions with commas or use -e or --extension multiple times
django-admin.py makemessages --locale=de --extension=html,txt --extension xml django-admin.py makemessages --locale=de --extension=html,txt --extension xml
--locale Use the :djadminopt:`--locale` option to specify the locale to process.
~~~~~~~~
Use the ``--locale`` or ``-l`` option to specify the locale to process.
Example usage:: Example usage::
django-admin.py makemessages --locale=br_PT django-admin.py makemessages --locale=br_PT
--domain .. django-admin-option:: --domain
~~~~~~~~
Use the ``--domain`` or ``-d`` option to change the domain of the messages files. Use the ``--domain`` or ``-d`` option to change the domain of the messages files.
Currently supported: Currently supported:
@ -434,23 +429,21 @@ Currently supported:
* ``django`` for all ``*.py`` and ``*.html`` files (default) * ``django`` for all ``*.py`` and ``*.html`` files (default)
* ``djangojs`` for ``*.js`` files * ``djangojs`` for ``*.js`` files
.. _django-admin-reset:
reset <appname appname ...> reset <appname appname ...>
--------------------------- ---------------------------
.. django-admin:: reset
Executes the equivalent of ``sqlreset`` for the given app name(s). Executes the equivalent of ``sqlreset`` for the given app name(s).
--noinput The :djadminopt:`--noinput` option may be provided to suppress all user
~~~~~~~~~ prompts.
Use the ``--noinput`` option to suppress all user prompting, such as
"Are you sure?" confirmation messages. This is useful if ``django-admin.py``
is being executed as an unattended, automated script.
runfcgi [options] runfcgi [options]
----------------- -----------------
.. django-admin:: runfcgi
Starts a set of FastCGI processes suitable for use with any Web server that Starts a set of FastCGI processes suitable for use with any Web server that
supports the FastCGI protocol. See the :ref:`FastCGI deployment documentation supports the FastCGI protocol. See the :ref:`FastCGI deployment documentation
<howto-deployment-fastcgi>` for details. Requires the Python FastCGI module from <howto-deployment-fastcgi>` for details. Requires the Python FastCGI module from
@ -458,10 +451,10 @@ supports the FastCGI protocol. See the :ref:`FastCGI deployment documentation
.. _flup: http://www.saddi.com/software/flup/ .. _flup: http://www.saddi.com/software/flup/
runserver runserver [port or ipaddr:port]
--------- -------------------------------
.. django-admin:: runserver [port or ipaddr:port] .. django-admin:: runserver
Starts a lightweight development Web server on the local machine. By default, Starts a lightweight development Web server on the local machine. By default,
the server runs on port 8000 on the IP address 127.0.0.1. You can pass in an the server runs on port 8000 on the IP address 127.0.0.1. You can pass in an
@ -544,6 +537,8 @@ you want to configure Django to serve static media, read :ref:`howto-static-file
shell shell
----- -----
.. django-admin:: shell
Starts the Python interactive interpreter. Starts the Python interactive interpreter.
Django will use IPython_, if it's installed. If you have IPython installed and Django will use IPython_, if it's installed. If you have IPython installed and
@ -557,11 +552,15 @@ option, like so::
sql <appname appname ...> sql <appname appname ...>
------------------------- -------------------------
.. django-admin:: sql
Prints the CREATE TABLE SQL statements for the given app name(s). Prints the CREATE TABLE SQL statements for the given app name(s).
sqlall <appname appname ...> sqlall <appname appname ...>
---------------------------- ----------------------------
.. django-admin:: sqlall
Prints the CREATE TABLE and initial-data SQL statements for the given app name(s). Prints the CREATE TABLE and initial-data SQL statements for the given app name(s).
Refer to the description of ``sqlcustom`` for an explanation of how to Refer to the description of ``sqlcustom`` for an explanation of how to
@ -570,11 +569,15 @@ specify initial data.
sqlclear <appname appname ...> sqlclear <appname appname ...>
------------------------------ ------------------------------
.. django-admin:: sqlclear
Prints the DROP TABLE SQL statements for the given app name(s). Prints the DROP TABLE SQL statements for the given app name(s).
sqlcustom <appname appname ...> sqlcustom <appname appname ...>
------------------------------- -------------------------------
.. django-admin:: sqlcustom
Prints the custom SQL statements for the given app name(s). Prints the custom SQL statements for the given app name(s).
For each model in each specified app, this command looks for the file For each model in each specified app, this command looks for the file
@ -594,21 +597,30 @@ Note that the order in which the SQL files are processed is undefined.
sqlflush sqlflush
-------- --------
Prints the SQL statements that would be executed for the `flush`_ command. .. django-admin:: sqlflush
Prints the SQL statements that would be executed for the :djadmin:`flush`
command.
sqlindexes <appname appname ...> sqlindexes <appname appname ...>
-------------------------------- --------------------------------
.. django-admin:: sqlindexes
Prints the CREATE INDEX SQL statements for the given app name(s). Prints the CREATE INDEX SQL statements for the given app name(s).
sqlreset <appname appname ...> sqlreset <appname appname ...>
------------------------------ ------------------------------
.. django-admin:: sqlreset
Prints the DROP TABLE SQL, then the CREATE TABLE SQL, for the given app name(s). Prints the DROP TABLE SQL, then the CREATE TABLE SQL, for the given app name(s).
sqlsequencereset <appname appname ...> sqlsequencereset <appname appname ...>
-------------------------------------- --------------------------------------
.. django-admin:: sqlsequencereset
Prints the SQL statements for resetting sequences for the given app name(s). Prints the SQL statements for resetting sequences for the given app name(s).
Sequences are indexes used by some database engines to track the next available Sequences are indexes used by some database engines to track the next available
@ -620,12 +632,16 @@ of sync with its automatically incremented field data.
startapp <appname> startapp <appname>
------------------ ------------------
.. django-admin:: startapp
Creates a Django app directory structure for the given app name in the current Creates a Django app directory structure for the given app name in the current
directory. directory.
startproject <projectname> startproject <projectname>
-------------------------- --------------------------
.. django-admin:: startproject
Creates a Django project directory structure for the given project name in the Creates a Django project directory structure for the given project name in the
current directory. current directory.
@ -635,11 +651,11 @@ This command is disabled when the ``--settings`` option to
situations, either omit the ``--settings`` option or unset situations, either omit the ``--settings`` option or unset
``DJANGO_SETTINGS_MODULE``. ``DJANGO_SETTINGS_MODULE``.
.. _django-admin-syncdb:
syncdb syncdb
------ ------
.. django-admin:: syncdb
Creates the database tables for all apps in ``INSTALLED_APPS`` whose tables Creates the database tables for all apps in ``INSTALLED_APPS`` whose tables
have not already been created. have not already been created.
@ -669,29 +685,22 @@ with an appropriate extension (e.g. ``json`` or ``xml``). See the
documentation for ``loaddata`` for details on the specification of fixture documentation for ``loaddata`` for details on the specification of fixture
data files. data files.
--noinput The :djadminopt:`--noinput` option may be provided to suppress all user
~~~~~~~~~ prompts.
Use the ``--noinput`` option to suppress all user prompting, such as test <app or test identifier>
"Are you sure?" confirmation messages. This is useful if ``django-admin.py`` -----------------------------
is being executed as an unattended, automated script.
test .. django-admin:: test
----
Runs tests for all installed models. See :ref:`topics-testing` for more Runs tests for all installed models. See :ref:`topics-testing` for more
information. information.
--noinput
~~~~~~~~~
Use the ``--noinput`` option to suppress all user prompting, such as
"Are you sure?" confirmation messages. This is useful if ``django-admin.py``
is being executed as an unattended, automated script.
testserver <fixture fixture ...> testserver <fixture fixture ...>
-------------------------------- --------------------------------
.. django-admin:: testserver
.. versionadded:: 1.0 .. versionadded:: 1.0
Runs a Django development server (as in ``runserver``) using data from the Runs a Django development server (as in ``runserver``) using data from the
@ -727,8 +736,7 @@ Note that this server does *not* automatically detect changes to your Python
source code (as ``runserver`` does). It does, however, detect changes to source code (as ``runserver`` does). It does, however, detect changes to
templates. templates.
--addrport [port number or ipaddr:port] .. django-admin-option:: --addrport [port number or ipaddr:port]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Use ``--addrport`` to specify a different port, or IP address and port, from Use ``--addrport`` to specify a different port, or IP address and port, from
the default of 127.0.0.1:8000. This value follows exactly the same format and the default of 127.0.0.1:8000. This value follows exactly the same format and
@ -752,6 +760,8 @@ To run on 1.2.3.4:7000 with a ``test`` fixture::
validate validate
-------- --------
.. django-admin:: validate
Validates all installed models (according to the ``INSTALLED_APPS`` setting) Validates all installed models (according to the ``INSTALLED_APPS`` setting)
and prints validation errors to standard output. and prints validation errors to standard output.
@ -761,8 +771,7 @@ Default options
Although some subcommands may allow their own custom options, every subcommand Although some subcommands may allow their own custom options, every subcommand
allows for the following options: allows for the following options:
--pythonpath .. django-admin-option:: --pythonpath
------------
Example usage:: Example usage::
@ -777,8 +786,7 @@ setting the Python path for you.
.. _import search path: http://diveintopython.org/getting_to_know_python/everything_is_an_object.html .. _import search path: http://diveintopython.org/getting_to_know_python/everything_is_an_object.html
--settings .. django-admin-option:: --settings
----------
Example usage:: Example usage::
@ -792,8 +800,7 @@ variable.
Note that this option is unnecessary in ``manage.py``, because it uses Note that this option is unnecessary in ``manage.py``, because it uses
``settings.py`` from the current project by default. ``settings.py`` from the current project by default.
--traceback .. django-admin-option:: --traceback
-----------
Example usage:: Example usage::
@ -803,10 +810,7 @@ By default, ``django-admin.py`` will show a simple error message whenever an
error occurs. If you specify ``--traceback``, ``django-admin.py`` will error occurs. If you specify ``--traceback``, ``django-admin.py`` will
output a full stack trace whenever an exception is raised. output a full stack trace whenever an exception is raised.
.. _django-admin-verbosity: .. django-admin-option:: --verbosity
--verbosity
-----------
Example usage:: Example usage::
@ -819,6 +823,23 @@ that ``django-admin.py`` should print to the console.
* ``1`` means normal output (default). * ``1`` means normal output (default).
* ``2`` means verbose output. * ``2`` means verbose output.
Common options
==============
The following options are not available on every commands, but they are
common to a number of commands.
.. django-admin-option:: --locale
Use the ``--locale`` or ``-l`` option to specify the locale to process.
If not provided all locales are processed.
.. django-admin-option:: --noinput
Use the ``--noinput`` option to suppress all user prompting, such as "Are
you sure?" confirmation messages. This is useful if ``django-admin.py`` is
being executed as an unattended, automated script.
Extra niceties Extra niceties
============== ==============
@ -844,5 +865,4 @@ distribution. It enables tab-completion of ``django-admin.py`` and
with ``sql``. with ``sql``.
See :ref:`howto-custom-management-commands` for how to add customized actions. See :ref:`howto-custom-management-commands` for how to add customized actions.

View File

@ -94,9 +94,8 @@ See the docs for :meth:`~django.db.models.QuerySet.latest` for more.
.. versionadded:: 1.1 .. versionadded:: 1.1
Defaults to ``True``, meaning Django will create the appropriate database Defaults to ``True``, meaning Django will create the appropriate database
tables in :ref:`django-admin-syncdb` and remove them as part of a :ref:`reset tables in :djadmin:`syncdb` and remove them as part of a :djadmin:`reset`
<django-admin-reset>` management command. That is, Django *manages* the management command. That is, Django *manages* the database tables' lifecycles.
database tables' lifecycles.
If ``False``, no database table creation or deletion operations will be If ``False``, no database table creation or deletion operations will be
performed for this model. This is useful if the model represents an existing performed for this model. This is useful if the model represents an existing
@ -114,7 +113,7 @@ model handling are exactly the same as normal. This includes
unmanaged model, then the intermediate table for the many-to-many join unmanaged model, then the intermediate table for the many-to-many join
will also not be created. However, a the intermediary table between one will also not be created. However, a the intermediary table between one
managed and one unmanaged model *will* be created. managed and one unmanaged model *will* be created.
If you need to change this default behavior, create the intermediary If you need to change this default behavior, create the intermediary
table as an explicit model (with ``managed`` set as needed) and use the table as an explicit model (with ``managed`` set as needed) and use the
:attr:`ManyToManyField.through` attribute to make the relation use your :attr:`ManyToManyField.through` attribute to make the relation use your

View File

@ -51,6 +51,18 @@ comment
Ignore everything between ``{% comment %}`` and ``{% endcomment %}`` Ignore everything between ``{% comment %}`` and ``{% endcomment %}``
.. templatetag:: csrf_token
csrf_token
~~~~~~~~~~
.. versionadded:: 1.1.2
In the Django 1.1.X series, this is a no-op tag that returns an empty string for
future compatibility purposes. In Django 1.2 and later, it is used for CSRF
protection, as described in the documentation for :ref:`Cross Site Request
Forgeries <ref-contrib-csrf>`.
.. templatetag:: cycle .. templatetag:: cycle
csrf_token csrf_token

36
docs/releases/1.1.2.txt Normal file
View File

@ -0,0 +1,36 @@
.. _releases-1.1.2:
==============================================
Django 1.1.2 release notes — UNDER DEVELOPMENT
==============================================
This page documents release notes for the as-yet-unreleased Django
1.1.2. As such it is tentative and subject to change. It provides
up-to-date information for those who are following the 1.1.X branch.
This is the second "bugfix" release in the Django 1.1 series,
improving the stability and performance of the Django 1.1 codebase.
Django 1.1.2 maintains backwards compatibility with Django
1.1.0, but contain a number of fixes and other
improvements. Django 1.1.2 is a recommended upgrade for any
development or deployment currently using or targeting Django 1.1.
For full details on the new features, backwards incompatibilities, and
deprecated features in the 1.1 branch, see the :ref:`releases-1.1`.
One new feature
---------------
Ordinarily, a point release would not include new features, but in the
case of Django 1.1.2, we have made an exception to this rule. Django
1.2 (the next major release of Django) will contain a feature that
will improve protection against Cross-Site Request Forgery (CSRF)
attacks. This feature requires the use of a new :ttag:`csrf_token`
template tag in all forms that Django renders.
To make it easier to support both 1.1.X and 1.2.X versions of Django with
the same templates, we have decided to introduce the :ttag:`csrf_token` template
tag to the 1.1.X branch. In the 1.1.X branch, :ttag:`csrf_token` does nothing -
it has no effect on templates or form processing. However, it means that the
same template will work with Django 1.2.

View File

@ -14,8 +14,10 @@ fixes, and an easy upgrade path from Django 1.0.
.. _new features: `What's new in Django 1.1`_ .. _new features: `What's new in Django 1.1`_
Backwards-incompatible changes .. _backwards-incompatible-changes-1.1:
==============================
Backwards-incompatible changes in 1.1
=====================================
Django has a policy of :ref:`API stability <misc-api-stability>`. This means Django has a policy of :ref:`API stability <misc-api-stability>`. This means
that, in general, code you develop against Django 1.0 should continue to work that, in general, code you develop against Django 1.0 should continue to work
@ -150,6 +152,8 @@ Django 1.1 adds a ``permanent`` argument to the
backwards-incompatible if you were using the ``redirect_to`` view with a backwards-incompatible if you were using the ``redirect_to`` view with a
format-string key called 'permanent', which is highly unlikely. format-string key called 'permanent', which is highly unlikely.
.. _deprecated-features-1.1:
Features deprecated in 1.1 Features deprecated in 1.1
========================== ==========================

157
docs/releases/1.2.txt Normal file
View File

@ -0,0 +1,157 @@
.. _releases-1.2:
============================================
Django 1.2 release notes — UNDER DEVELOPMENT
============================================
This page documents release notes for the as-yet-unreleased Django 1.2. As such
it is tentative and subject to change. It provides up-to-date information for
those who are following trunk.
Django 1.2 includes a number of nifty `new features`_, lots of bug
fixes, and an easy upgrade path from Django 1.1.
.. _new features: `What's new in Django 1.2`_
.. _backwards-incompatible-changes-1.2:
Backwards-incompatible changes in 1.2
=====================================
CSRF Protection
---------------
There have been large changes to the way that CSRF protection works, detailed in
:ref:`the CSRF documentaton <ref-contrib-csrf>`. The following are the major
changes that developers must be aware of:
* ``CsrfResponseMiddleware`` and ``CsrfMiddleware`` have been deprecated, and
will be removed completely in Django 1.4, in favor of a template tag that
should be inserted into forms.
* All contrib apps use a ``csrf_protect`` decorator to protect the view. This
requires the use of the csrf_token template tag in the template, so if you
have used custom templates for contrib views, you MUST READ THE :ref:`UPGRADE
INSTRUCTIONS <ref-csrf-upgrading-notes>` to fix those templates.
* ``CsrfViewMiddleware`` is included in :setting:`MIDDLEWARE_CLASSES` by
default. This turns on CSRF protection by default, so that views that accept
POST requests need to be written to work with the middleware. Instructions
on how to do this are found in the CSRF docs.
* All of the CSRF has moved from contrib to core (with backwards compatible
imports in the old locations, which are deprecated).
``LazyObject``
--------------
``LazyObject`` is an undocumented utility class used for lazily wrapping other
objects of unknown type. In Django 1.1 and earlier, it handled introspection in
a non-standard way, depending on wrapped objects implementing a public method
``get_all_members()``. Since this could easily lead to name clashes, it has been
changed to use the standard method, involving ``__members__`` and ``__dir__()``.
If you used ``LazyObject`` in your own code, and implemented the
``get_all_members()`` method for wrapped objects, you need to make the following
changes:
* If your class does not have special requirements for introspection (i.e. you
have not implemented ``__getattr__()`` or other methods that allow for
attributes not discoverable by normal mechanisms), you can simply remove the
``get_all_members()`` method. The default implementation on ``LazyObject``
will do the right thing.
* If you have more complex requirements for introspection, first rename the
``get_all_members()`` method to ``__dir__()``. This is the standard method,
from Python 2.6 onwards, for supporting introspection. If you are require
support for Python < 2.6, add the following code to the class::
__members__ = property(lambda self: self.__dir__())
.. _deprecated-features-1.2:
Features deprecated in 1.2
==========================
CSRF response rewriting middleware
----------------------------------
``CsrfResponseMiddleware``, the middleware that automatically inserted CSRF
tokens into POST forms in outgoing pages, has been deprecated in favor of a
template tag method (see above), and will be removed completely in Django
1.4. ``CsrfMiddleware``, which includes the functionality of
``CsrfResponseMiddleware`` and ``CsrfViewMiddleware`` has likewise been
deprecated.
Also, the CSRF module has moved from contrib to core, and the old imports are
deprecated, as described in the :ref:`upgrading notes <ref-csrf-upgrading-notes>`.
``SMTPConnection``
------------------
The ``SMTPConnection`` class has been deprecated in favor of a generic
E-mail backend API. Old code that explicitly instantiated an instance
of an SMTPConnection::
from django.core.mail import SMTPConnection
connection = SMTPConnection()
messages = get_notification_email()
connection.send_messages(messages)
should now call :meth:`~django.core.mail.get_connection()` to
instantiate a generic e-mail connection::
from django.core.mail import get_connection
connection = get_connection()
messages = get_notification_email()
connection.send_messages(messages)
Depending on the value of the :setting:`EMAIL_BACKEND` setting, this
may not return an SMTP connection. If you explicitly require an SMTP
connection with which to send e-mail, you can explicitly request an
SMTP connection::
from django.core.mail import get_connection
connection = get_connection('django.core.mail.backends.smtp')
messages = get_notification_email()
connection.send_messages(messages)
If your call to construct an instance of ``SMTPConnection`` required
additional arguments, those arguments can be passed to the
:meth:`~django.core.mail.get_connection()` call::
connection = get_connection('django.core.mail.backends.smtp', hostname='localhost', port=1234)
What's new in Django 1.2
========================
CSRF support
------------
Django now has much improved protection against :ref:`Cross-Site
Request Forgery (CSRF) attacks<ref-contrib-csrf>`. This type of attack
occurs when a malicious Web site contains a link, a form button or
some javascript that is intended to perform some action on your Web
site, using the credentials of a logged-in user who visits the
malicious site in their browser. A related type of attack, 'login
CSRF', where an attacking site tricks a user's browser into logging
into a site with someone else's credentials, is also covered.
E-mail Backends
---------------
You can now :ref:`configure the way that Django sends e-mail
<topic-email-backends>`. Instead of using SMTP to send all e-mail, you
can now choose a configurable e-mail backend to send messages. If your
hosting provider uses a sandbox or some other non-SMTP technique for
sending mail, you can now construct an e-mail backend that will allow
Django's standard :ref:`mail sending methods<topics-email>` to use
those facilities.
This also makes it easier to debug mail sending - Django ships with
backend implementations that allow you to send e-mail to a
:ref:`file<topic-email-file-backend>`, to the
:ref:`console<topic-email-console-backend>`, or to
:ref:`memory<topic-email-memory-backend>` - you can even configure all
e-mail to be :ref:`thrown away<topic-email-dummy-backend>`.

View File

@ -1,5 +1,6 @@
.. _releases-index: .. _releases-index:
=============
Release notes Release notes
============= =============
@ -7,28 +8,60 @@ Release notes for the official Django releases. Each release note will tell you
what's new in each version, and will also describe any backwards-incompatible what's new in each version, and will also describe any backwards-incompatible
changes made in that version. changes made in that version.
For those upgrading to a new version of Django, you will need to check
all the backwards-incompatible changes and deprecated features for
each 'final' release from the one after your current Django version,
up to and including the new version.
Final releases
==============
1.2 release
-----------
.. toctree:: .. toctree::
:maxdepth: 1 :maxdepth: 1
0.95 1.2
0.96
1.0-alpha-1 1.1 release
1.0-alpha-2 -----------
1.0-beta .. toctree::
1.0-beta-2 :maxdepth: 1
1.0
1.0.1 1.1.2
1.0.2
1.1-alpha-1
1.1-beta-1
1.1-rc-1
1.1 1.1
.. seealso:: 1.0 release
-----------
.. toctree::
:maxdepth: 1
The list of `backwards-incompatible changes`_ made in the current 1.0.2
development "trunk". If you're running versions of Django newer than an 1.0.1
official release, you should keep track of new pieces pointed there. It's 1.0
also fun reading if you're looking forward to new versions of Django.
.. _backwards-incompatible changes: http://code.djangoproject.com/wiki/BackwardsIncompatibleChanges Pre-1.0 releases
----------------
.. toctree::
:maxdepth: 1
0.96
0.95
Development releases
====================
These notes are retained for historical purposes. If you are upgrading
between formal Django releases, you don't need to worry about these
notes.
.. toctree::
:maxdepth: 1
1.1-rc-1
1.1-beta-1
1.1-alpha-1
1.0-beta-2
1.0-beta
1.0-alpha-2
1.0-alpha-1

View File

@ -179,9 +179,9 @@ Local-memory caching
If you want the speed advantages of in-memory caching but don't have the If you want the speed advantages of in-memory caching but don't have the
capability of running Memcached, consider the local-memory cache backend. This capability of running Memcached, consider the local-memory cache backend. This
cache is multi-process and thread-safe. To use it, set ``CACHE_BACKEND`` to cache is multi-process and thread-safe. To use it, set ``CACHE_BACKEND`` to
``"locmem:///"``. For example:: ``"locmem://"``. For example::
CACHE_BACKEND = 'locmem:///' CACHE_BACKEND = 'locmem://'
Note that each process will have its own private cache instance, which means no Note that each process will have its own private cache instance, which means no
cross-process caching is possible. This obviously also means the local memory cross-process caching is possible. This obviously also means the local memory
@ -199,7 +199,7 @@ various places but a development/test environment where you don't want to cache
and don't want to have to change your code to special-case the latter. To and don't want to have to change your code to special-case the latter. To
activate dummy caching, set ``CACHE_BACKEND`` like so:: activate dummy caching, set ``CACHE_BACKEND`` like so::
CACHE_BACKEND = 'dummy:///' CACHE_BACKEND = 'dummy://'
Using a custom cache backend Using a custom cache backend
---------------------------- ----------------------------
@ -249,7 +249,7 @@ In this example, ``timeout`` is set to ``60``::
In this example, ``timeout`` is ``30`` and ``max_entries`` is ``400``:: In this example, ``timeout`` is ``30`` and ``max_entries`` is ``400``::
CACHE_BACKEND = "locmem:///?timeout=30&max_entries=400" CACHE_BACKEND = "locmem://?timeout=30&max_entries=400"
Invalid arguments are silently ignored, as are invalid values of known Invalid arguments are silently ignored, as are invalid values of known
arguments. arguments.
@ -451,11 +451,11 @@ The low-level cache API
Sometimes, caching an entire rendered page doesn't gain you very much and is, Sometimes, caching an entire rendered page doesn't gain you very much and is,
in fact, inconvenient overkill. in fact, inconvenient overkill.
Perhaps, for instance, your site includes a view whose results depend on Perhaps, for instance, your site includes a view whose results depend on
several expensive queries, the results of which change at different intervals. several expensive queries, the results of which change at different intervals.
In this case, it would not be ideal to use the full-page caching that the In this case, it would not be ideal to use the full-page caching that the
per-site or per-view cache strategies offer, because you wouldn't want to per-site or per-view cache strategies offer, because you wouldn't want to
cache the entire result (since some of the data changes often), but you'd still cache the entire result (since some of the data changes often), but you'd still
want to cache the results that rarely change. want to cache the results that rarely change.
For cases like this, Django exposes a simple, low-level cache API. You can use For cases like this, Django exposes a simple, low-level cache API. You can use
@ -757,10 +757,10 @@ Django comes with a few other pieces of middleware that can help optimize your
apps' performance: apps' performance:
* ``django.middleware.http.ConditionalGetMiddleware`` adds support for * ``django.middleware.http.ConditionalGetMiddleware`` adds support for
modern browsers to conditionally GET responses based on the ``ETag`` modern browsers to conditionally GET responses based on the ``ETag``
and ``Last-Modified`` headers. and ``Last-Modified`` headers.
* ``django.middleware.gzip.GZipMiddleware`` compresses responses for all * ``django.middleware.gzip.GZipMiddleware`` compresses responses for all
moderns browsers, saving bandwidth and transfer time. moderns browsers, saving bandwidth and transfer time.
Order of MIDDLEWARE_CLASSES Order of MIDDLEWARE_CLASSES

View File

@ -10,7 +10,7 @@ Sending e-mail
Although Python makes sending e-mail relatively easy via the `smtplib Although Python makes sending e-mail relatively easy via the `smtplib
library`_, Django provides a couple of light wrappers over it. These wrappers library`_, Django provides a couple of light wrappers over it. These wrappers
are provided to make sending e-mail extra quick, to make it easy to test are provided to make sending e-mail extra quick, to make it easy to test
email sending during development, and to provide support for platforms that e-mail sending during development, and to provide support for platforms that
can't use SMTP. can't use SMTP.
The code lives in the ``django.core.mail`` module. The code lives in the ``django.core.mail`` module.
@ -64,7 +64,7 @@ are required.
* ``auth_password``: The optional password to use to authenticate to the * ``auth_password``: The optional password to use to authenticate to the
SMTP server. If this isn't provided, Django will use the value of the SMTP server. If this isn't provided, Django will use the value of the
``EMAIL_HOST_PASSWORD`` setting. ``EMAIL_HOST_PASSWORD`` setting.
* ``connection``: The optional email backend to use to send the mail. * ``connection``: The optional e-mail backend to use to send the mail.
If unspecified, an instance of the default backend will be used. If unspecified, an instance of the default backend will be used.
See the documentation on :ref:`E-mail backends <topic-email-backends>` See the documentation on :ref:`E-mail backends <topic-email-backends>`
for more details. for more details.
@ -215,8 +215,8 @@ message itself. The :ref:`e-mail backend <topic-email-backends>` is then
responsible for sending the e-mail. responsible for sending the e-mail.
For convenience, :class:`~django.core.mail.EmailMessage` provides a simple For convenience, :class:`~django.core.mail.EmailMessage` provides a simple
``send()`` method for sending a single email. If you need to send multiple ``send()`` method for sending a single e-mail. If you need to send multiple
messages, the email backend API :ref:`provides an alternative messages, the e-mail backend API :ref:`provides an alternative
<topics-sending-multiple-emails>`. <topics-sending-multiple-emails>`.
EmailMessage Objects EmailMessage Objects
@ -264,7 +264,7 @@ For example::
The class has the following methods: The class has the following methods:
* ``send(fail_silently=False)`` sends the message. If a connection was * ``send(fail_silently=False)`` sends the message. If a connection was
specified when the email was constructed, that connection will be used. specified when the e-mail was constructed, that connection will be used.
Otherwise, an instance of the default backend will be instantiated and Otherwise, an instance of the default backend will be instantiated and
used. If the keyword argument ``fail_silently`` is ``True``, exceptions used. If the keyword argument ``fail_silently`` is ``True``, exceptions
raised while sending the message will be quashed. raised while sending the message will be quashed.
@ -358,9 +358,9 @@ The actual sending of an e-mail is handled by the e-mail backend.
The e-mail backend class has the following methods: The e-mail backend class has the following methods:
* ``open()`` instantiates an long-lived email-sending connection. * ``open()`` instantiates an long-lived e-mail-sending connection.
* ``close()`` closes the current email-sending connection. * ``close()`` closes the current e-mail-sending connection.
* ``send_messages(email_messages)`` sends a list of * ``send_messages(email_messages)`` sends a list of
:class:`~django.core.mail.EmailMessage` objects. If the connection is :class:`~django.core.mail.EmailMessage` objects. If the connection is
@ -379,11 +379,11 @@ instance of the e-mail backend that you can use.
.. function:: get_connection(backend=None, fail_silently=False, *args, **kwargs) .. function:: get_connection(backend=None, fail_silently=False, *args, **kwargs)
By default, a call to ``get_connection()`` will return an instance of the By default, a call to ``get_connection()`` will return an instance of the
email backend specified in :setting:`EMAIL_BACKEND`. If you specify the e-mail backend specified in :setting:`EMAIL_BACKEND`. If you specify the
``backend`` argument, an instance of that backend will be instantiated. ``backend`` argument, an instance of that backend will be instantiated.
The ``fail_silently`` argument controls how the backend should handle errors. The ``fail_silently`` argument controls how the backend should handle errors.
If ``fail_silently`` is True, exceptions during the email sending process If ``fail_silently`` is True, exceptions during the e-mail sending process
will be silently ignored. will be silently ignored.
All other arguments are passed directly to the constructor of the All other arguments are passed directly to the constructor of the
@ -391,8 +391,8 @@ e-mail backend.
Django ships with several e-mail sending backends. With the exception of the Django ships with several e-mail sending backends. With the exception of the
SMTP backend (which is the default), these backends are only useful during SMTP backend (which is the default), these backends are only useful during
testing and development. If you have special email sending requirements, you testing and development. If you have special e-mail sending requirements, you
can :ref:`write your own email backend <topic-custom-email-backend>`. can :ref:`write your own e-mail backend <topic-custom-email-backend>`.
.. _topic-email-smtp-backend: .. _topic-email-smtp-backend:
@ -401,7 +401,7 @@ SMTP backend
This is the default backend. E-mail will be sent through a SMTP server. This is the default backend. E-mail will be sent through a SMTP server.
The server address and authentication credentials are set in the The server address and authentication credentials are set in the
:setting:`EMAIL_HOST`, :setting:`EMAIL_POST`, :setting:`EMAIL_HOST_USER`, :setting:`EMAIL_HOST`, :setting:`EMAIL_PORT`, :setting:`EMAIL_HOST_USER`,
:setting:`EMAIL_HOST_PASSWORD` and :setting:`EMAIL_USE_TLS` settings in your :setting:`EMAIL_HOST_PASSWORD` and :setting:`EMAIL_USE_TLS` settings in your
settings file. settings file.
@ -414,13 +414,15 @@ want to specify it explicitly, put the following in your settings::
Prior to version 1.2, Django provided a Prior to version 1.2, Django provided a
:class:`~django.core.mail.SMTPConnection` class. This class provided a way :class:`~django.core.mail.SMTPConnection` class. This class provided a way
to directly control the use of SMTP to send email. This class has been to directly control the use of SMTP to send e-mail. This class has been
deprecated in favor of the generic email backend API. deprecated in favor of the generic e-mail backend API.
For backwards compatibility :class:`~django.core.mail.SMTPConnection` is For backwards compatibility :class:`~django.core.mail.SMTPConnection` is
still available in ``django.core.mail`` as an alias for the SMTP backend. still available in ``django.core.mail`` as an alias for the SMTP backend.
New code should use :meth:`~django.core.mail.get_connection` instead. New code should use :meth:`~django.core.mail.get_connection` instead.
.. _topic-email-console-backend:
Console backend Console backend
~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~
@ -436,6 +438,8 @@ To specify this backend, put the following in your settings::
This backend is not intended for use in production -- it is provided as a This backend is not intended for use in production -- it is provided as a
convenience that can be used during development. convenience that can be used during development.
.. _topic-email-file-backend:
File backend File backend
~~~~~~~~~~~~ ~~~~~~~~~~~~
@ -453,6 +457,8 @@ To specify this backend, put the following in your settings::
This backend is not intended for use in production -- it is provided as a This backend is not intended for use in production -- it is provided as a
convenience that can be used during development. convenience that can be used during development.
.. _topic-email-memory-backend:
In-memory backend In-memory backend
~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~
@ -469,6 +475,8 @@ To specify this backend, put the following in your settings::
This backend is not intended for use in production -- it is provided as a This backend is not intended for use in production -- it is provided as a
convenience that can be used during development and testing. convenience that can be used during development and testing.
.. _topic-email-dummy-backend:
Dummy backend Dummy backend
~~~~~~~~~~~~~ ~~~~~~~~~~~~~
@ -500,15 +508,15 @@ implementation.
.. _topics-sending-multiple-emails: .. _topics-sending-multiple-emails:
Sending multiple emails Sending multiple e-mails
----------------------- ------------------------
Establishing and closing an SMTP connection (or any other network connection, Establishing and closing an SMTP connection (or any other network connection,
for that matter) is an expensive process. If you have a lot of emails to send, for that matter) is an expensive process. If you have a lot of e-mails to send,
it makes sense to reuse an SMTP connection, rather than creating and it makes sense to reuse an SMTP connection, rather than creating and
destroying a connection every time you want to send an email. destroying a connection every time you want to send an e-mail.
There are two ways you tell an email backend to reuse a connection. There are two ways you tell an e-mail backend to reuse a connection.
Firstly, you can use the ``send_messages()`` method. ``send_messages()`` takes Firstly, you can use the ``send_messages()`` method. ``send_messages()`` takes
a list of :class:`~django.core.mail.EmailMessage` instances (or subclasses), a list of :class:`~django.core.mail.EmailMessage` instances (or subclasses),
@ -516,11 +524,11 @@ and sends them all using a single connection.
For example, if you have a function called ``get_notification_email()`` that For example, if you have a function called ``get_notification_email()`` that
returns a list of :class:`~django.core.mail.EmailMessage` objects representing returns a list of :class:`~django.core.mail.EmailMessage` objects representing
some periodic e-mail you wish to send out, you could send these emails using some periodic e-mail you wish to send out, you could send these e-mails using
a single call to send_messages:: a single call to send_messages::
from django.core import mail from django.core import mail
connection = mail.get_connection() # Use default email connection connection = mail.get_connection() # Use default e-mail connection
messages = get_notification_email() messages = get_notification_email()
connection.send_messages(messages) connection.send_messages(messages)
@ -528,7 +536,7 @@ In this example, the call to ``send_messages()`` opens a connection on the
backend, sends the list of messages, and then closes the connection again. backend, sends the list of messages, and then closes the connection again.
The second approach is to use the ``open()`` and ``close()`` methods on the The second approach is to use the ``open()`` and ``close()`` methods on the
email backend to manually control the connection. ``send_messages()`` will not e-mail backend to manually control the connection. ``send_messages()`` will not
manually open or close the connection if it is already open, so if you manually open or close the connection if it is already open, so if you
manually open the connection, you can control when it is closed. For example:: manually open the connection, you can control when it is closed. For example::
@ -538,10 +546,10 @@ manually open the connection, you can control when it is closed. For example::
# Manually open the connection # Manually open the connection
connection.open() connection.open()
# Construct an email message that uses the connection # Construct an e-mail message that uses the connection
email1 = mail.EmailMessage('Hello', 'Body goes here', 'from@example.com', email1 = mail.EmailMessage('Hello', 'Body goes here', 'from@example.com',
['to1@example.com'], connection=connection) ['to1@example.com'], connection=connection)
email1.send() # Send the email email1.send() # Send the e-mail
# Construct two more messages # Construct two more messages
email2 = mail.EmailMessage('Hello', 'Body goes here', 'from@example.com', email2 = mail.EmailMessage('Hello', 'Body goes here', 'from@example.com',
@ -549,7 +557,7 @@ manually open the connection, you can control when it is closed. For example::
email3 = mail.EmailMessage('Hello', 'Body goes here', 'from@example.com', email3 = mail.EmailMessage('Hello', 'Body goes here', 'from@example.com',
['to3@example.com']) ['to3@example.com'])
# Send the two emails in a single call - # Send the two e-mails in a single call -
connection.send_messages([email2, email3]) connection.send_messages([email2, email3])
# The connection was already open so send_messages() doesn't close it. # The connection was already open so send_messages() doesn't close it.
# We need to manually close the connection. # We need to manually close the connection.
@ -566,10 +574,10 @@ people under the right conditions, and that those e-mails will contain the
correct content. correct content.
The easiest way to test your project's use of e-mail is to use the ``console`` The easiest way to test your project's use of e-mail is to use the ``console``
email backend. This backend redirects all email to stdout, allowing you to e-mail backend. This backend redirects all e-mail to stdout, allowing you to
inspect the content of mail. inspect the content of mail.
The ``file`` email backend can also be useful during development -- this backend The ``file`` e-mail backend can also be useful during development -- this backend
dumps the contents of every SMTP connection to a file that can be inspected dumps the contents of every SMTP connection to a file that can be inspected
at your leisure. at your leisure.
@ -596,7 +604,7 @@ SMTPConnection
.. deprecated:: 1.2 .. deprecated:: 1.2
The ``SMTPConnection`` class has been deprecated in favor of the generic email The ``SMTPConnection`` class has been deprecated in favor of the generic e-mail
backend API. backend API.
For backwards compatibility ``SMTPConnection`` is still available in For backwards compatibility ``SMTPConnection`` is still available in

View File

@ -40,7 +40,8 @@ algorithm the system follows to determine which Python code to execute:
1. Django determines the root URLconf module to use. Ordinarily, 1. Django determines the root URLconf module to use. Ordinarily,
this is the value of the ``ROOT_URLCONF`` setting, but if the incoming this is the value of the ``ROOT_URLCONF`` setting, but if the incoming
``HttpRequest`` object has an attribute called ``urlconf``, its value ``HttpRequest`` object has an attribute called ``urlconf`` (set by
middleware :ref:`request processing <request-middleware>`), its value
will be used in place of the ``ROOT_URLCONF`` setting. will be used in place of the ``ROOT_URLCONF`` setting.
2. Django loads that Python module and looks for the variable 2. Django loads that Python module and looks for the variable

View File

@ -980,19 +980,21 @@ subclass::
def setUp(self): def setUp(self):
# Test definitions as before. # Test definitions as before.
call_setup_methods()
def testFluffyAnimals(self): def testFluffyAnimals(self):
# A test that uses the fixtures. # A test that uses the fixtures.
call_some_test_code()
Here's specifically what will happen: Here's specifically what will happen:
* At the start of each test case, before ``setUp()`` is run, Django will * At the start of each test case, before ``setUp()`` is run, Django will
flush the database, returning the database to the state it was in flush the database, returning the database to the state it was in
directly after ``syncdb`` was called. directly after :djadmin:`syncdb` was called.
* Then, all the named fixtures are installed. In this example, Django will * Then, all the named fixtures are installed. In this example, Django will
install any JSON fixture named ``mammals``, followed by any fixture named install any JSON fixture named ``mammals``, followed by any fixture named
``birds``. See the :djadmin:`loaddata documentation<loaddata>` for more ``birds``. See the :djadmin:`loaddata` documentation for more
details on defining and installing fixtures. details on defining and installing fixtures.
This flush/load procedure is repeated for each test in the test case, so you This flush/load procedure is repeated for each test in the test case, so you
@ -1028,6 +1030,7 @@ For example::
def testIndexPageView(self): def testIndexPageView(self):
# Here you'd test your view using ``Client``. # Here you'd test your view using ``Client``.
call_some_test_code()
This test case will use the contents of ``myapp.test_urls`` as the This test case will use the contents of ``myapp.test_urls`` as the
URLconf for the duration of the test case. URLconf for the duration of the test case.

View File

@ -1,4 +1,13 @@
""" from django.db import models
class Advertisment(models.Model):
customer = models.CharField(max_length=100)
publications = models.ManyToManyField("model_package.Publication", null=True, blank=True)
class Meta:
app_label = 'model_package'
__test__ = {'API_TESTS': """
>>> from models.publication import Publication >>> from models.publication import Publication
>>> from models.article import Article >>> from models.article import Article
>>> from django.contrib.auth.views import Site >>> from django.contrib.auth.views import Site
@ -19,7 +28,6 @@
>>> a.save() >>> a.save()
>>> a.publications.add(p) >>> a.publications.add(p)
>>> a.sites.add(current_site) >>> a.sites.add(current_site)
>>> a.save()
>>> a = Article.objects.get(id=1) >>> a = Article.objects.get(id=1)
>>> a >>> a
@ -29,6 +37,19 @@
>>> a.sites.count() >>> a.sites.count()
1 1
""" # Regression for #12248 - Models can exist in the test package, too
>>> ad = Advertisment(customer="Lawrence Journal-World")
>>> ad.save()
>>> ad.publications.add(p)
>>> ad = Advertisment.objects.get(id=1)
>>> ad
<Advertisment: Advertisment object>
>>> ad.publications.count()
1
"""}

View File

@ -26,6 +26,21 @@ class TwoAlbumFKAndAnE(models.Model):
e = models.CharField(max_length=1) e = models.CharField(max_length=1)
class Author(models.Model):
name = models.CharField(max_length=100)
class Book(models.Model):
name = models.CharField(max_length=100)
subtitle = models.CharField(max_length=100)
price = models.FloatField()
authors = models.ManyToManyField(Author, through='AuthorsBooks')
class AuthorsBooks(models.Model):
author = models.ForeignKey(Author)
book = models.ForeignKey(Book)
__test__ = {'API_TESTS':""" __test__ = {'API_TESTS':"""
@ -95,4 +110,48 @@ Exception: <class 'regressiontests.admin_validation.models.TwoAlbumFKAndAnE'> ha
>>> validate_inline(TwoAlbumFKAndAnEInline, None, Album) >>> validate_inline(TwoAlbumFKAndAnEInline, None, Album)
# Regression test for #12203/#12237 - Fail more gracefully when a M2M field that
# specifies the 'through' option is included in the 'fields' or the 'fieldsets'
# ModelAdmin options.
>>> class BookAdmin(admin.ModelAdmin):
... fields = ['authors']
>>> validate(BookAdmin, Book)
Traceback (most recent call last):
...
ImproperlyConfigured: 'BookAdmin.fields' can't include the ManyToManyField field 'authors' because 'authors' manually specifies a 'through' model.
>>> class FieldsetBookAdmin(admin.ModelAdmin):
... fieldsets = (
... ('Header 1', {'fields': ('name',)}),
... ('Header 2', {'fields': ('authors',)}),
... )
>>> validate(FieldsetBookAdmin, Book)
Traceback (most recent call last):
...
ImproperlyConfigured: 'FieldsetBookAdmin.fieldsets[1][1]['fields']' can't include the ManyToManyField field 'authors' because 'authors' manually specifies a 'through' model.
>>> class NestedFieldsetAdmin(admin.ModelAdmin):
... fieldsets = (
... ('Main', {'fields': ('price', ('name', 'subtitle'))}),
... )
>>> validate(NestedFieldsetAdmin, Book)
# Regression test for #12209 -- If the explicitly provided through model
# is specified as a string, the admin should still be able use
# Model.m2m_field.through
>>> class AuthorsInline(admin.TabularInline):
... model = Book.authors.through
>>> class BookAdmin(admin.ModelAdmin):
... inlines = [AuthorsInline]
# If the through model is still a string (and hasn't been resolved to a model)
# the validation will fail.
>>> validate(BookAdmin, Book)
"""} """}

View File

@ -0,0 +1 @@

View File

@ -0,0 +1 @@

View File

@ -0,0 +1,14 @@
import sys, os
from optparse import OptionParser, make_option
from django.core.management.base import BaseCommand
class Command(BaseCommand):
option_list = BaseCommand.option_list + (
make_option("--list", action="store_true", dest="list",
help="Print all options"),
)
def handle(self, *args, **options):
pass

View File

@ -0,0 +1 @@

View File

@ -0,0 +1,87 @@
"""
A series of tests to establish that the command-line bash completion works.
"""
import os
import unittest
import sys
import StringIO
from django.conf import settings
from django.core.management import ManagementUtility
class BashCompletionTests(unittest.TestCase):
"""
Testing the Python level bash completion code.
This requires setting up the environment as if we got passed data
from bash.
"""
def setUp(self):
self.old_DJANGO_AUTO_COMPLETE = os.environ.get('DJANGO_AUTO_COMPLETE')
os.environ['DJANGO_AUTO_COMPLETE'] = '1'
self.output = StringIO.StringIO()
self.old_stdout = sys.stdout
sys.stdout = self.output
def tearDown(self):
sys.stdout = self.old_stdout
if self.old_DJANGO_AUTO_COMPLETE:
os.environ['DJANGO_AUTO_COMPLETE'] = self.old_DJANGO_AUTO_COMPLETE
else:
del os.environ['DJANGO_AUTO_COMPLETE']
def _user_input(self, input_str):
os.environ['COMP_WORDS'] = input_str
os.environ['COMP_CWORD'] = str(len(input_str.split()) - 1)
sys.argv = input_str.split(' ')
def _run_autocomplete(self):
util = ManagementUtility(argv=sys.argv)
try:
util.autocomplete()
except SystemExit:
pass
return self.output.getvalue().strip().split('\n')
def test_django_admin_py(self):
"django_admin.py will autocomplete option flags"
self._user_input('django-admin.py sqlall --v')
output = self._run_autocomplete()
self.assertEqual(output, ['--verbosity='])
def test_manage_py(self):
"manage.py will autocomplete option flags"
self._user_input('manage.py sqlall --v')
output = self._run_autocomplete()
self.assertEqual(output, ['--verbosity='])
def test_custom_command(self):
"A custom command can autocomplete option flags"
self._user_input('django-admin.py test_command --l')
output = self._run_autocomplete()
self.assertEqual(output, ['--list'])
def test_subcommands(self):
"Subcommands can be autocompleted"
self._user_input('django-admin.py sql')
output = self._run_autocomplete()
self.assertEqual(output, ['sql sqlall sqlclear sqlcustom sqlflush sqlindexes sqlinitialdata sqlreset sqlsequencereset'])
def test_help(self):
"No errors, just an empty list if there are no autocomplete options"
self._user_input('django-admin.py help --')
output = self._run_autocomplete()
self.assertEqual(output, [''])
def test_runfcgi(self):
"Command arguments will be autocompleted"
self._user_input('django-admin.py runfcgi h')
output = self._run_autocomplete()
self.assertEqual(output, ['host='])
def test_app_completion(self):
"Application names will be autocompleted for an AppCommand"
self._user_input('django-admin.py sqlall a')
output = self._run_autocomplete()
app_labels = [name.split('.')[-1] for name in settings.INSTALLED_APPS]
self.assertEqual(output, sorted(label for label in app_labels if label.startswith('a')))

View File

@ -115,6 +115,23 @@ u'c1'
>>> results[0].second_child.name >>> results[0].second_child.name
u'c2' u'c2'
# Test for #12163 - Pickling error saving session with unsaved model instances.
>>> from django.contrib.sessions.backends.db import SessionStore
>>> SESSION_KEY = '2b1189a188b44ad18c35e1baac6ceead'
>>> item = Item()
>>> item._deferred
False
>>> s = SessionStore(SESSION_KEY)
>>> s.clear()
>>> s['item'] = item
>>> s.save()
>>> s = SessionStore(SESSION_KEY)
>>> s.modified = True
>>> s.save()
>>> i2 = s['item']
>>> i2._deferred # Item must still be non-deferred
False
# Finally, we need to flush the app cache for the defer module. # Finally, we need to flush the app cache for the defer module.
# Using only/defer creates some artifical entries in the app cache # Using only/defer creates some artifical entries in the app cache
# that messes up later tests. Purge all entries, just to be sure. # that messes up later tests. Purge all entries, just to be sure.

View File

@ -1,4 +1,29 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""
##########
# Fields #
##########
Each Field class does some sort of validation. Each Field has a clean() method,
which either raises django.forms.ValidationError or returns the "clean"
data -- usually a Unicode object, but, in some rare cases, a list.
Each Field's __init__() takes at least these parameters:
required -- Boolean that specifies whether the field is required.
True by default.
widget -- A Widget class, or instance of a Widget class, that should be
used for this Field when displaying it. Each Field has a default
Widget that it'll use if you don't specify this. In most cases,
the default widget is TextInput.
label -- A verbose name for this field, for use in displaying this field in
a form. By default, Django will use a "pretty" version of the form
field name, if the Field is part of a Form.
initial -- A value to use in this Field's initial display. This value is
*not* used as a fallback if data isn't given.
Other than that, the Field subclasses have class-specific options for
__init__(). For example, CharField has a max_length option.
"""
import datetime import datetime
import time import time
import re import re

View File

@ -102,4 +102,34 @@ u'<ul class="errorlist"><li>(Hidden field data) This field is required.</li></ul
>>> f.as_table() >>> f.as_table()
u'<tr><td colspan="2"><ul class="errorlist"><li>(Hidden field data) This field is required.</li></ul><input type="hidden" name="data" id="id_data" /></td></tr>' u'<tr><td colspan="2"><ul class="errorlist"><li>(Hidden field data) This field is required.</li></ul><input type="hidden" name="data" id="id_data" /></td></tr>'
###################################################
# Tests for XSS vulnerabilities in error messages #
###################################################
# The forms layer doesn't escape input values directly because error messages
# might be presented in non-HTML contexts. Instead, the message is just marked
# for escaping by the template engine. So we'll need to construct a little
# silly template to trigger the escaping.
>>> from django.template import Template, Context
>>> t = Template('{{ form.errors }}')
>>> class SomeForm(Form):
... field = ChoiceField(choices=[('one', 'One')])
>>> f = SomeForm({'field': '<script>'})
>>> t.render(Context({'form': f}))
u'<ul class="errorlist"><li>field<ul class="errorlist"><li>Select a valid choice. &lt;script&gt; is not one of the available choices.</li></ul></li></ul>'
>>> class SomeForm(Form):
... field = MultipleChoiceField(choices=[('one', 'One')])
>>> f = SomeForm({'field': ['<script>']})
>>> t.render(Context({'form': f}))
u'<ul class="errorlist"><li>field<ul class="errorlist"><li>Select a valid choice. &lt;script&gt; is not one of the available choices.</li></ul></li></ul>'
>>> from regressiontests.forms.models import ChoiceModel
>>> class SomeForm(Form):
... field = ModelMultipleChoiceField(ChoiceModel.objects.all())
>>> f = SomeForm({'field': ['<script>']})
>>> t.render(Context({'form': f}))
u'<ul class="errorlist"><li>field<ul class="errorlist"><li>&quot;&lt;script&gt;&quot; is not a valid value for a primary key.</li></ul></li></ul>'
""" """

View File

@ -167,4 +167,10 @@ Traceback (most recent call last):
... ...
ValueError: Cannot assign "<Child: Child object>": "Child.parent" must be a "Parent" instance. ValueError: Cannot assign "<Child: Child object>": "Child.parent" must be a "Parent" instance.
# Regression for #12190 -- Should be able to instantiate a FK
# outside of a model, and interrogate its related field.
>>> cat = models.ForeignKey(Category)
>>> cat.rel.get_related_field().name
'id'
"""} """}

View File

@ -755,10 +755,20 @@ Bug #6180, #6203 -- dates with limits and/or counts
>>> Item.objects.dates('created', 'day')[0] >>> Item.objects.dates('created', 'day')[0]
datetime.datetime(2007, 12, 19, 0, 0) datetime.datetime(2007, 12, 19, 0, 0)
Bug #7087 -- dates with extra select columns Bug #7087/#12242 -- dates with extra select columns
>>> Item.objects.dates('created', 'day').extra(select={'a': 1}) >>> Item.objects.dates('created', 'day').extra(select={'a': 1})
[datetime.datetime(2007, 12, 19, 0, 0), datetime.datetime(2007, 12, 20, 0, 0)] [datetime.datetime(2007, 12, 19, 0, 0), datetime.datetime(2007, 12, 20, 0, 0)]
>>> Item.objects.extra(select={'a': 1}).dates('created', 'day')
[datetime.datetime(2007, 12, 19, 0, 0), datetime.datetime(2007, 12, 20, 0, 0)]
>>> name="one"
>>> Item.objects.dates('created', 'day').extra(where=['name=%s'], params=[name])
[datetime.datetime(2007, 12, 19, 0, 0)]
>>> Item.objects.extra(where=['name=%s'], params=[name]).dates('created', 'day')
[datetime.datetime(2007, 12, 19, 0, 0)]
Bug #7155 -- nullable dates Bug #7155 -- nullable dates
>>> Item.objects.dates('modified', 'day') >>> Item.objects.dates('modified', 'day')
[datetime.datetime(2007, 12, 19, 0, 0)] [datetime.datetime(2007, 12, 19, 0, 0)]

View File

@ -0,0 +1,7 @@
from django.core.urlresolvers import set_urlconf
import urlconf_inner
class ChangeURLconfMiddleware(object):
def process_request(self, request):
request.urlconf = urlconf_inner.__name__

View File

@ -16,11 +16,16 @@ ImproperlyConfigured: The included urlconf regressiontests.urlpatterns_reverse.n
import unittest import unittest
from django.conf import settings
from django.core.urlresolvers import reverse, resolve, NoReverseMatch, Resolver404 from django.core.urlresolvers import reverse, resolve, NoReverseMatch, Resolver404
from django.http import HttpResponseRedirect, HttpResponsePermanentRedirect from django.http import HttpResponseRedirect, HttpResponsePermanentRedirect
from django.shortcuts import redirect from django.shortcuts import redirect
from django.test import TestCase from django.test import TestCase
import urlconf_outer
import urlconf_inner
import middleware
test_data = ( test_data = (
('places', '/places/3/', [3], {}), ('places', '/places/3/', [3], {}),
('places', '/places/3/', ['3'], {}), ('places', '/places/3/', ['3'], {}),
@ -239,3 +244,35 @@ class NamespaceTests(TestCase):
self.assertEquals('/other1/inner/37/42/', reverse('nodefault:urlobject-view', args=[37,42], current_app='other-ns1')) self.assertEquals('/other1/inner/37/42/', reverse('nodefault:urlobject-view', args=[37,42], current_app='other-ns1'))
self.assertEquals('/other1/inner/42/37/', reverse('nodefault:urlobject-view', kwargs={'arg1':42, 'arg2':37}, current_app='other-ns1')) self.assertEquals('/other1/inner/42/37/', reverse('nodefault:urlobject-view', kwargs={'arg1':42, 'arg2':37}, current_app='other-ns1'))
class RequestURLconfTests(TestCase):
def setUp(self):
self.root_urlconf = settings.ROOT_URLCONF
self.middleware_classes = settings.MIDDLEWARE_CLASSES
settings.ROOT_URLCONF = urlconf_outer.__name__
def tearDown(self):
settings.ROOT_URLCONF = self.root_urlconf
settings.MIDDLEWARE_CLASSES = self.middleware_classes
def test_urlconf(self):
response = self.client.get('/test/me/')
self.assertEqual(response.status_code, 200)
self.assertEqual(response.content, 'outer:/test/me/,'
'inner:/inner_urlconf/second_test/')
response = self.client.get('/inner_urlconf/second_test/')
self.assertEqual(response.status_code, 200)
response = self.client.get('/second_test/')
self.assertEqual(response.status_code, 404)
def test_urlconf_overridden(self):
settings.MIDDLEWARE_CLASSES += (
'%s.ChangeURLconfMiddleware' % middleware.__name__,
)
response = self.client.get('/test/me/')
self.assertEqual(response.status_code, 404)
response = self.client.get('/inner_urlconf/second_test/')
self.assertEqual(response.status_code, 404)
response = self.client.get('/second_test/')
self.assertEqual(response.status_code, 200)
self.assertEqual(response.content, 'outer:,inner:/second_test/')

View File

@ -0,0 +1,12 @@
from django.conf.urls.defaults import *
from django.template import Template, Context
from django.http import HttpResponse
def inner_view(request):
content = Template('{% url outer as outer_url %}outer:{{ outer_url }},'
'{% url inner as inner_url %}inner:{{ inner_url }}').render(Context())
return HttpResponse(content)
urlpatterns = patterns('',
url(r'^second_test/$', inner_view, name='inner'),
)

View File

@ -0,0 +1,9 @@
from django.conf.urls.defaults import *
import urlconf_inner
urlpatterns = patterns('',
url(r'^test/me/$', urlconf_inner.inner_view, name='outer'),
url(r'^inner_urlconf/', include(urlconf_inner.__name__))
)