1
0
mirror of https://github.com/django/django.git synced 2025-07-06 18:59:13 +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
* Karen Tracey
* Jannis Leidel
* James Tauber
More information on the main contributors to Django can be found in
docs/internals/committers.txt.

View File

@ -5,7 +5,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Django\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"
"Last-Translator: Django Catalan Group <django-cat@googlegroups.com>\n"
"Language-Team: Catalan <ca@li.org>\n"
@ -223,7 +223,7 @@ msgstr "xinès tradicional"
msgid "Successfully deleted %(count)d %(items)s."
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?"
msgstr "Esteu segurs?"
@ -266,15 +266,15 @@ msgstr "Aquest mes"
msgid "This year"
msgstr "Aquest any"
#: contrib/admin/filterspecs.py:147 forms/widgets.py:434
#: contrib/admin/filterspecs.py:147 forms/widgets.py:435
msgid "Yes"
msgstr "Si"
#: contrib/admin/filterspecs.py:147 forms/widgets.py:434
#: contrib/admin/filterspecs.py:147 forms/widgets.py:435
msgid "No"
msgstr "No"
#: contrib/admin/filterspecs.py:154 forms/widgets.py:434
#: contrib/admin/filterspecs.py:154 forms/widgets.py:435
msgid "Unknown"
msgstr "Desconegut"
@ -310,61 +310,61 @@ msgstr "entrada del registre"
msgid "log entries"
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"
msgstr "cap"
#: contrib/admin/options.py:519
#: contrib/admin/options.py:521
#, python-format
msgid "Changed %s."
msgstr "Modificat %s."
#: contrib/admin/options.py:519 contrib/admin/options.py:529
#: contrib/comments/templates/comments/preview.html:16 forms/models.py:388
#: forms/models.py:600
#: contrib/admin/options.py:521 contrib/admin/options.py:531
#: contrib/comments/templates/comments/preview.html:16 forms/models.py:384
#: forms/models.py:596
msgid "and"
msgstr "i"
#: contrib/admin/options.py:524
#: contrib/admin/options.py:526
#, python-format
msgid "Added %(name)s \"%(object)s\"."
msgstr "Afegit %(name)s \"%(object)s\""
#: contrib/admin/options.py:528
#: contrib/admin/options.py:530
#, python-format
msgid "Changed %(list)s for %(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
msgid "Deleted %(name)s \"%(object)s\"."
msgstr "Eliminat %(name)s \"%(object)s\"."
#: contrib/admin/options.py:537
#: contrib/admin/options.py:539
msgid "No fields changed."
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
msgid "The %(name)s \"%(obj)s\" was added successfully."
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
msgid "You may edit it again below."
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
msgid "You may add another %s below."
msgstr "Podeu afegir un altre %s a baix."
#: contrib/admin/options.py:633
#: contrib/admin/options.py:636
#, python-format
msgid "The %(name)s \"%(obj)s\" was changed successfully."
msgstr "S'ha modificat amb èxit el/la %(name)s \"%(obj)s."
#: contrib/admin/options.py:641
#: contrib/admin/options.py:644
#, python-format
msgid ""
"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 "
"abaix."
#: contrib/admin/options.py:772
#: contrib/admin/options.py:777
#, python-format
msgid "Add %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
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."
#: contrib/admin/options.py:860
#: contrib/admin/options.py:866
#, python-format
msgid "Change %s"
msgstr "Modificar %s"
#: contrib/admin/options.py:904
#: contrib/admin/options.py:910
msgid "Database error"
msgstr "Error de base de dades"
#: contrib/admin/options.py:940
#: contrib/admin/options.py:946
#, python-format
msgid "%(count)s %(name)s was changed successfully."
msgid_plural "%(count)s %(name)s were changed successfully."
msgstr[0] "%(count)s %(name)s s'ha 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
msgid "The %(name)s \"%(obj)s\" was deleted successfully."
msgstr "El/la %(name)s \"%(obj)s\" ha estat eliminat amb èxit."
#: contrib/admin/options.py:1054
#: contrib/admin/options.py:1063
#, python-format
msgid "Change history: %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
msgid ""
"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 "
"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."
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 ""
"Looks like your browser isn't configured to accept cookies. Please enable "
"cookies, reload this page, and try again."
@ -430,29 +430,29 @@ msgstr ""
"'cookies' (galetes). Si us plau, habiliteu les 'cookies', recarregueu "
"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
msgid "Usernames cannot contain the '@' character."
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
msgid "Your e-mail address is not your username. Try '%s' instead."
msgstr ""
"La vostra adreça de correu no és el vostre nom d'usuari. Provi '%s' en tot "
"cas."
#: contrib/admin/sites.py:360
#: contrib/admin/sites.py:374
msgid "Site administration"
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/views/decorators.py:20
msgid "Log in"
msgstr "Iniciar sessió"
#: contrib/admin/sites.py:417
#: contrib/admin/sites.py:433
#, python-format
msgid "%s administration"
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:"
msgstr "Un o més %(fieldname)s en %(name)s:"
#: contrib/admin/widgets.py:71
#: contrib/admin/widgets.py:72
msgid "Date:"
msgstr "Data:"
#: contrib/admin/widgets.py:71
#: contrib/admin/widgets.py:72
msgid "Time:"
msgstr "Hora:"
#: contrib/admin/widgets.py:95
#: contrib/admin/widgets.py:96
msgid "Currently:"
msgstr "Actualment:"
#: contrib/admin/widgets.py:95
#: contrib/admin/widgets.py:96
msgid "Change:"
msgstr "Modificar:"
#: contrib/admin/widgets.py:124
#: contrib/admin/widgets.py:125
msgid "Lookup"
msgstr "Cercar"
#: contrib/admin/widgets.py:236
#: contrib/admin/widgets.py:237
msgid "Add Another"
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/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_list.html:25
#: contrib/admin/templates/admin/delete_confirmation.html:6
@ -555,18 +555,18 @@ msgstr "Anar"
msgid "%(name)s"
msgstr "%(name)s"
#: contrib/admin/templates/admin/base.html:26
#: contrib/admin/templates/admin/base.html:27
msgid "Welcome,"
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_form.html:3
#: contrib/admindocs/templates/admin_doc/bookmarklets.html:3
msgid "Documentation"
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:47
#: contrib/admin/templates/registration/password_change_done.html:3
@ -574,7 +574,7 @@ msgstr "Documentació"
msgid "Change password"
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_form.html:3
msgid "Log out"
@ -600,7 +600,7 @@ msgstr "Històric"
#: contrib/admin/templates/admin/change_form.html:28
#: 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"
msgstr "Veure al lloc"
@ -670,9 +670,9 @@ msgstr ""
#, python-format
msgid ""
"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 ""
"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:"
#: contrib/admin/templates/admin/filter.html:2
@ -736,7 +736,6 @@ msgid "User"
msgstr "Usuari"
#: contrib/admin/templates/admin/object_history.html:24
#: contrib/comments/templates/comments/moderation_queue.html:33
msgid "Action"
msgstr "Acció"
@ -985,7 +984,7 @@ msgstr "Adreça de correu electrònic:"
msgid "Reset my password"
msgstr "Restablir la meva contrasenya"
#: contrib/admin/templatetags/admin_list.py:299
#: contrib/admin/templatetags/admin_list.py:304
msgid "All dates"
msgstr "Totes les dates"
@ -1007,145 +1006,144 @@ msgstr "lloc"
msgid "template"
msgstr "plantilla"
#: contrib/admindocs/views.py:58 contrib/admindocs/views.py:60
#: contrib/admindocs/views.py:62
#: contrib/admindocs/views.py:61 contrib/admindocs/views.py:63
#: contrib/admindocs/views.py:65
msgid "tag:"
msgstr "etiqueta:"
#: contrib/admindocs/views.py:91 contrib/admindocs/views.py:93
#: contrib/admindocs/views.py:95
#: contrib/admindocs/views.py:94 contrib/admindocs/views.py:96
#: contrib/admindocs/views.py:98
msgid "filter:"
msgstr "filtre:"
#: contrib/admindocs/views.py:155 contrib/admindocs/views.py:157
#: contrib/admindocs/views.py:159
#: contrib/admindocs/views.py:158 contrib/admindocs/views.py:160
#: contrib/admindocs/views.py:162
msgid "view:"
msgstr "vista:"
#: contrib/admindocs/views.py:187
#: contrib/admindocs/views.py:190
#, python-format
msgid "App %r not found"
msgstr "No s'ha pogut trobar l'aplicació %r"
#: contrib/admindocs/views.py:194
#: contrib/admindocs/views.py:197
#, python-format
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"
#: contrib/admindocs/views.py:206
#: contrib/admindocs/views.py:209
#, python-format
msgid "the related `%(app_label)s.%(data_type)s` object"
msgstr "l'objecte relacionat `%(app_label)s.%(data_type)s`"
#: contrib/admindocs/views.py:206 contrib/admindocs/views.py:225
#: contrib/admindocs/views.py:230 contrib/admindocs/views.py:244
#: contrib/admindocs/views.py:258 contrib/admindocs/views.py:263
#: contrib/admindocs/views.py:209 contrib/admindocs/views.py:228
#: contrib/admindocs/views.py:233 contrib/admindocs/views.py:247
#: contrib/admindocs/views.py:261 contrib/admindocs/views.py:266
msgid "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
msgid "related `%(app_label)s.%(object_name)s` objects"
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
msgid "all %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
msgid "number of %s"
msgstr "nombre de %s"
#: contrib/admindocs/views.py:268
#: contrib/admindocs/views.py:271
#, python-format
msgid "Fields on %s objects"
msgstr "Camps en objectes %s"
#: contrib/admindocs/views.py:331 contrib/admindocs/views.py:342
#: contrib/admindocs/views.py:344 contrib/admindocs/views.py:350
#: contrib/admindocs/views.py:351 contrib/admindocs/views.py:353
#: contrib/admindocs/views.py:334 contrib/admindocs/views.py:345
#: contrib/admindocs/views.py:347 contrib/admindocs/views.py:353
#: contrib/admindocs/views.py:354 contrib/admindocs/views.py:356
msgid "Integer"
msgstr "Enter"
#: contrib/admindocs/views.py:332
#: contrib/admindocs/views.py:335
msgid "Boolean (Either True or False)"
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
msgid "String (up to %(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"
msgstr "Enters separats per comes"
#: contrib/admindocs/views.py:335
#: contrib/admindocs/views.py:338
msgid "Date (without time)"
msgstr "Data (sense hora)"
#: contrib/admindocs/views.py:336
#: contrib/admindocs/views.py:339
msgid "Date (with time)"
msgstr "Data (amb hora)"
#: contrib/admindocs/views.py:337
#: contrib/admindocs/views.py:340
msgid "Decimal number"
msgstr "Número decimal"
#: contrib/admindocs/views.py:338
#: contrib/admindocs/views.py:341
msgid "E-mail address"
msgstr "Adreça de correu electrònic"
#: contrib/admindocs/views.py:339 contrib/admindocs/views.py:340
#: contrib/admindocs/views.py:343
#: contrib/admindocs/views.py:342 contrib/admindocs/views.py:343
#: contrib/admindocs/views.py:346
msgid "File path"
msgstr "Ruta del fitxer"
#: contrib/admindocs/views.py:341
#: contrib/admindocs/views.py:344
msgid "Floating point number"
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"
msgstr "Adreça IP"
#: contrib/admindocs/views.py:347
#: contrib/admindocs/views.py:350
msgid "Boolean (Either True, False or None)"
msgstr "Booleà (Verdader, Fals o 'None' (cap))"
#: contrib/admindocs/views.py:348
#: contrib/admindocs/views.py:351
msgid "Relation to parent model"
msgstr "Relació amb el model pare"
#: contrib/admindocs/views.py:349
#: contrib/admindocs/views.py:352
msgid "Phone number"
msgstr "Número de telèfon"
#: contrib/admindocs/views.py:354
#: contrib/admindocs/views.py:357
msgid "Text"
msgstr "Text"
#: contrib/admindocs/views.py:355
#: contrib/admindocs/views.py:358
msgid "Time"
msgstr "Hora"
#: contrib/admindocs/views.py:356 contrib/comments/forms.py:95
#: contrib/comments/templates/comments/moderation_queue.html:37
#: contrib/admindocs/views.py:359 contrib/comments/forms.py:95
#: contrib/flatpages/admin.py:8 contrib/flatpages/models.py:7
msgid "URL"
msgstr "URL"
#: contrib/admindocs/views.py:357
#: contrib/admindocs/views.py:360
msgid "U.S. state (two uppercase letters)"
msgstr "Estat dels E.U.A. (dues lletres majúscules)"
#: contrib/admindocs/views.py:358
#: contrib/admindocs/views.py:361
msgid "XML text"
msgstr "Text XML"
#: contrib/admindocs/views.py:384
#: contrib/admindocs/views.py:387
#, python-format
msgid "%s does not appear to be a urlpattern object"
msgstr "%s no sembla ser un objecte 'urlpattern'"
@ -1438,22 +1436,54 @@ msgstr "usuaris"
msgid "message"
msgstr "missatge"
#: contrib/auth/views.py:56
#: contrib/auth/views.py:60
msgid "Logged out"
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."
msgstr "Introduïu una adreça de correu vàlida."
#: contrib/comments/admin.py:11
#: contrib/comments/admin.py:12
msgid "Content"
msgstr "contingut"
#: contrib/comments/admin.py:14
#: contrib/comments/admin.py:15
msgid "Metadata"
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
#, python-format
msgid "%(site_name)s comments"
@ -1465,7 +1495,6 @@ msgid "Latest comments on %(site_name)s"
msgstr "Últims comentaris a %(site_name)s."
#: contrib/comments/forms.py:93
#: contrib/comments/templates/comments/moderation_queue.html:34
msgid "Name"
msgstr "nom"
@ -1474,7 +1503,6 @@ msgid "Email address"
msgstr "Adreça de correu electrònic"
#: contrib/comments/forms.py:96
#: contrib/comments/templates/comments/moderation_queue.html:35
msgid "Comment"
msgstr "Comentari"
@ -1606,7 +1634,6 @@ msgid "Really make this comment public?"
msgstr "Realment vol fer aquest comentari públic?"
#: contrib/comments/templates/comments/approve.html:12
#: contrib/comments/templates/comments/moderation_queue.html:49
msgid "Approve"
msgstr "Aprovar"
@ -1631,7 +1658,6 @@ msgid "Really remove this comment?"
msgstr "Realment vol eliminar aquest comentari?"
#: contrib/comments/templates/comments/delete.html:12
#: contrib/comments/templates/comments/moderation_queue.html:53
msgid "Remove"
msgstr "Eliminar"
@ -1666,39 +1692,6 @@ msgstr "Publicar"
msgid "Preview"
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
msgid "Thanks for commenting"
msgstr "Gràcies per comentar"
@ -1793,7 +1786,7 @@ msgstr "pàgina estàtica"
msgid "flat pages"
msgstr "pàgines estàtiques"
#: contrib/formtools/wizard.py:130
#: contrib/formtools/wizard.py:132
msgid ""
"We apologize, but your form has expired. Please continue filling out the "
"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."
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
msgid "Enter a zip code in the 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
msgid "National Business Register Number (REGON) consists of 9 or 14 digits."
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
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."
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 ""
"Hold down \"Control\", or \"Command\" on a Mac, to select more than one."
msgstr ""
"Premeu la tecla \"Control\" -o \"Command\" en un Mac- per seleccionar més "
"d'un valor."
#: db/models/fields/related.py:894
#: db/models/fields/related.py:930
#, python-format
msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid."
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 "
"invàlids."
#: forms/fields.py:54
#: forms/fields.py:53
msgid "This field is required."
msgstr "Aquest camp és obligatori."
#: forms/fields.py:55
#: forms/fields.py:54
msgid "Enter a valid value."
msgstr "Introduïu un valor vàlid."
#: forms/fields.py:138
#: forms/fields.py:137
#, python-format
msgid "Ensure this value has at most %(max)d characters (it has %(length)d)."
msgstr ""
"Assegureu-vos de que el valor té com a màxim %(max)d caràcters (en té %"
"(length)d)."
#: forms/fields.py:139
#: forms/fields.py:138
#, python-format
msgid "Ensure this value has at least %(min)d characters (it has %(length)d)."
msgstr ""
"Assegureu-vos de que el valor té com a mínim %(min)d caràcters (en té %"
"(length)d)."
#: forms/fields.py:166
#: forms/fields.py:165
msgid "Enter a whole number."
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
msgid "Ensure this value is less than or equal to %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
msgid "Ensure this value is greater than or equal to %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."
msgstr "Introduïu un número."
#: forms/fields.py:227
#: forms/fields.py:226
#, python-format
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."
#: forms/fields.py:228
#: forms/fields.py:227
#, python-format
msgid "Ensure that there are no more than %s decimal places."
msgstr "Assegureu-vos de que no hi ha més de %s decimals."
#: forms/fields.py:229
#: forms/fields.py:228
#, python-format
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."
#: forms/fields.py:288 forms/fields.py:863
#: forms/fields.py:287 forms/fields.py:862
msgid "Enter a valid date."
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."
msgstr "Introduïu una hora vàlida."
#: forms/fields.py:361
#: forms/fields.py:360
msgid "Enter a valid date/time."
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."
msgstr ""
"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."
msgstr "No s'ha enviat cap fitxer."
#: forms/fields.py:449
#: forms/fields.py:448
msgid "The submitted file is empty."
msgstr "El fitxer enviat està buit."
#: forms/fields.py:450
#: forms/fields.py:449
#, python-format
msgid ""
"Ensure this filename has at most %(max)d characters (it has %(length)d)."
msgstr ""
"Assegureu-vos de que el valor té com a màxim %(max)d caràcters "
"(en té %(length)d)."
"Assegureu-vos de que el valor té com a màxim %(max)d caràcters (en té %"
"(length)d)."
#: forms/fields.py:483
#: forms/fields.py:482
msgid ""
"Upload a valid image. The file you uploaded was either not an image or a "
"corrupted image."
@ -4058,28 +4056,28 @@ msgstr ""
"Envieu una imatge vàlida. El fitxer que heu enviat no era una imatge o "
"estava corrupte."
#: forms/fields.py:544
#: forms/fields.py:543
msgid "Enter a valid URL."
msgstr "Introduïu una URL vàlida."
#: forms/fields.py:545
#: forms/fields.py:544
msgid "This URL appears to be a broken link."
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
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."
#: 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."
msgstr "Introduïu una llista de valors."
#: forms/fields.py:892
#: forms/fields.py:891
msgid "Enter a valid IPv4 address."
msgstr "Introduïu una adreça IPv4 vàlida."
#: forms/fields.py:902
#: forms/fields.py:901
msgid ""
"Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."
msgstr ""
@ -4090,56 +4088,58 @@ msgstr ""
msgid "Order"
msgstr "Ordre"
#: forms/models.py:367
#: forms/models.py:363
#, python-format
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."
#: forms/models.py:381 forms/models.py:389
#: forms/models.py:377 forms/models.py:385
#, python-format
msgid "%(model_name)s with this %(field_label)s already exists."
msgstr "Ja existeix %(model_name)s amb aquest %(field_label)s."
#: forms/models.py:594
#: forms/models.py:590
#, python-format
msgid "Please correct the duplicate data for %(field)s."
msgstr "Si us plau, corregiu la dada duplicada per a %(field)s."
#: forms/models.py:598
#: forms/models.py:594
#, python-format
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
msgid ""
"Please correct the duplicate data for %(field_name)s which must be unique "
"for the %(lookup)s in %(date_field)s."
msgstr ""
"Si us plau, corregiu la dada duplicada per a %(field_name)s, "
"la qual ha de ser única per a la cerca %(lookup)s en %(date_field)s."
"Si us plau, corregiu la dada duplicada per a %(field_name)s, la qual ha de "
"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."
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."
msgstr ""
"La clau forànea en línea no coincideix amb la clau primària de la instància "
"del pare"
#: forms/models.py:930
#: forms/models.py:926
msgid "Select a valid choice. That choice is not one of the available choices."
msgstr ""
"Escolli una opció vàlida; Aquesta opció no és una de les opcions disponibles."
#: forms/models.py:1004
#: forms/models.py:1000
#, python-format
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."
#: forms/models.py:1006
#: forms/models.py:1002
#, python-format
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."
@ -4459,6 +4459,30 @@ msgstr "El/la %(verbose_name)s s'ha actualtzat amb èxit."
msgid "The %(verbose_name)s was deleted."
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
#~ msgid "verbose_name"
#~ msgid_plural "verbose_name_plural"

View File

@ -5,8 +5,8 @@ msgid ""
msgstr ""
"Project-Id-Version: Django\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2009-07-07 15:15+0200\n"
"PO-Revision-Date: 2009-07-07 15:22+0200\n"
"POT-Creation-Date: 2009-11-30 11:27+0100\n"
"PO-Revision-Date: 2009-11-30 11:31+0100\n"
"Last-Translator: Django Spanish Team <django-cat@googlegroups.com>Language-"
"Team: Django Spanish Team <django-cat@googlegroups.com>MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@ -222,7 +222,7 @@ msgstr "chino tradicional"
msgid "Successfully deleted %(count)d %(items)s."
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?"
msgstr "¿Está seguro?"
@ -265,15 +265,15 @@ msgstr "Este mes"
msgid "This year"
msgstr "Este año"
#: contrib/admin/filterspecs.py:147 forms/widgets.py:434
#: contrib/admin/filterspecs.py:147 forms/widgets.py:435
msgid "Yes"
msgstr "Sí"
#: contrib/admin/filterspecs.py:147 forms/widgets.py:434
#: contrib/admin/filterspecs.py:147 forms/widgets.py:435
msgid "No"
msgstr "No"
#: contrib/admin/filterspecs.py:154 forms/widgets.py:434
#: contrib/admin/filterspecs.py:154 forms/widgets.py:435
msgid "Unknown"
msgstr "Desconocido"
@ -309,104 +309,104 @@ msgstr "entrada de registro"
msgid "log entries"
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"
msgstr "Ninguno"
#: contrib/admin/options.py:519
#: contrib/admin/options.py:521
#, python-format
msgid "Changed %s."
msgstr "Modificado/a %s."
#: contrib/admin/options.py:519 contrib/admin/options.py:529
#: contrib/comments/templates/comments/preview.html:16 forms/models.py:388
#: forms/models.py:600
#: contrib/admin/options.py:521 contrib/admin/options.py:531
#: contrib/comments/templates/comments/preview.html:16 forms/models.py:384
#: forms/models.py:596
msgid "and"
msgstr "y"
#: contrib/admin/options.py:524
#: contrib/admin/options.py:526
#, python-format
msgid "Added %(name)s \"%(object)s\"."
msgstr "Añadido/a \"%(object)s\" %(name)s."
#: contrib/admin/options.py:528
#: contrib/admin/options.py:530
#, python-format
msgid "Changed %(list)s for %(name)s \"%(object)s\"."
msgstr "Modificados %(list)s para \"%(object)s\" %(name)s."
#: contrib/admin/options.py:533
#: contrib/admin/options.py:535
#, python-format
msgid "Deleted %(name)s \"%(object)s\"."
msgstr "Eliminado/a \"%(object)s\" %(name)s."
#: contrib/admin/options.py:537
#: contrib/admin/options.py:539
msgid "No fields changed."
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
msgid "The %(name)s \"%(obj)s\" was added successfully."
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
msgid "You may edit it again below."
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
msgid "You may add another %s below."
msgstr "Puede añadir otro %s abajo."
#: contrib/admin/options.py:633
#: contrib/admin/options.py:636
#, python-format
msgid "The %(name)s \"%(obj)s\" was changed successfully."
msgstr "Se modificó con éxito el %(name)s \"%(obj)s\"."
#: contrib/admin/options.py:641
#: contrib/admin/options.py:644
#, python-format
msgid ""
"The %(name)s \"%(obj)s\" was added successfully. You may edit it again below."
msgstr ""
"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
msgid "Add %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
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."
#: contrib/admin/options.py:860
#: contrib/admin/options.py:866
#, python-format
msgid "Change %s"
msgstr "Modificar %s"
#: contrib/admin/options.py:904
#: contrib/admin/options.py:910
msgid "Database error"
msgstr "Error en la base de datos"
#: contrib/admin/options.py:940
#: contrib/admin/options.py:946
#, python-format
msgid "%(count)s %(name)s was changed successfully."
msgid_plural "%(count)s %(name)s were changed successfully."
msgstr[0] "%(count)s %(name)s fué modificado con éxito."
msgstr[1] "%(count)s %(name)s fueron modificados con éxito."
#: contrib/admin/options.py:1018
#: contrib/admin/options.py:1026
#, python-format
msgid "The %(name)s \"%(obj)s\" was deleted successfully."
msgstr "Se eliminó con éxito el %(name)s \"%(obj)s\"."
#: contrib/admin/options.py:1054
#: contrib/admin/options.py:1063
#, python-format
msgid "Change history: %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
msgid ""
"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 "
"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."
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 ""
"Looks like your browser isn't configured to accept cookies. Please enable "
"cookies, reload this page, and try again."
@ -427,29 +427,29 @@ msgstr ""
"Parece que su navegador no está configurado para aceptar cookies. "
"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
msgid "Usernames cannot contain the '@' character."
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
msgid "Your e-mail address is not your username. Try '%s' instead."
msgstr ""
"Su dirección de correo no es su nombre de usuario. Pruebe con '%s' en su "
"lugar."
#: contrib/admin/sites.py:360
#: contrib/admin/sites.py:374
msgid "Site administration"
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/views/decorators.py:20
msgid "Log in"
msgstr "Iniciar sesión"
#: contrib/admin/sites.py:417
#: contrib/admin/sites.py:433
#, python-format
msgid "%s administration"
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:"
msgstr "Uno o más %(fieldname)s en %(name)s:"
#: contrib/admin/widgets.py:71
#: contrib/admin/widgets.py:72
msgid "Date:"
msgstr "Fecha:"
#: contrib/admin/widgets.py:71
#: contrib/admin/widgets.py:72
msgid "Time:"
msgstr "Hora:"
#: contrib/admin/widgets.py:95
#: contrib/admin/widgets.py:96
msgid "Currently:"
msgstr "Actualmente:"
#: contrib/admin/widgets.py:95
#: contrib/admin/widgets.py:96
msgid "Change:"
msgstr "Modificar:"
#: contrib/admin/widgets.py:124
#: contrib/admin/widgets.py:125
msgid "Lookup"
msgstr "Buscar"
#: contrib/admin/widgets.py:236
#: contrib/admin/widgets.py:237
msgid "Add Another"
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/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_list.html:25
#: contrib/admin/templates/admin/delete_confirmation.html:6
@ -553,18 +553,18 @@ msgstr "Ir"
msgid "%(name)s"
msgstr "%(name)s"
#: contrib/admin/templates/admin/base.html:26
#: contrib/admin/templates/admin/base.html:27
msgid "Welcome,"
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_form.html:3
#: contrib/admindocs/templates/admin_doc/bookmarklets.html:3
msgid "Documentation"
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:47
#: contrib/admin/templates/registration/password_change_done.html:3
@ -572,7 +572,7 @@ msgstr "Documentación"
msgid "Change password"
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_form.html:3
msgid "Log out"
@ -598,7 +598,7 @@ msgstr "Histórico"
#: contrib/admin/templates/admin/change_form.html:28
#: 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"
msgstr "Ver en el sitio"
@ -668,9 +668,9 @@ msgstr ""
#, python-format
msgid ""
"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 ""
"¿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:"
#: contrib/admin/templates/admin/filter.html:2
@ -734,7 +734,6 @@ msgid "User"
msgstr "Usuario"
#: contrib/admin/templates/admin/object_history.html:24
#: contrib/comments/templates/comments/moderation_queue.html:33
msgid "Action"
msgstr "Acción"
@ -987,7 +986,7 @@ msgstr "Dirección de correo electrónico:"
msgid "Reset my password"
msgstr "Restablecer mi contraseña"
#: contrib/admin/templatetags/admin_list.py:299
#: contrib/admin/templatetags/admin_list.py:304
msgid "All dates"
msgstr "Todas las fechas"
@ -1009,146 +1008,145 @@ msgstr "sitio"
msgid "template"
msgstr "plantilla"
#: contrib/admindocs/views.py:58 contrib/admindocs/views.py:60
#: contrib/admindocs/views.py:62
#: contrib/admindocs/views.py:61 contrib/admindocs/views.py:63
#: contrib/admindocs/views.py:65
msgid "tag:"
msgstr "etiqueta:"
#: contrib/admindocs/views.py:91 contrib/admindocs/views.py:93
#: contrib/admindocs/views.py:95
#: contrib/admindocs/views.py:94 contrib/admindocs/views.py:96
#: contrib/admindocs/views.py:98
msgid "filter:"
msgstr "filtro:"
#: contrib/admindocs/views.py:155 contrib/admindocs/views.py:157
#: contrib/admindocs/views.py:159
#: contrib/admindocs/views.py:158 contrib/admindocs/views.py:160
#: contrib/admindocs/views.py:162
msgid "view:"
msgstr "vista:"
#: contrib/admindocs/views.py:187
#: contrib/admindocs/views.py:190
#, python-format
msgid "App %r not found"
msgstr "Aplicación %r no encontrada"
#: contrib/admindocs/views.py:194
#: contrib/admindocs/views.py:197
#, python-format
msgid "Model %(model_name)r not found in app %(app_label)r"
msgstr ""
"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
msgid "the related `%(app_label)s.%(data_type)s` object"
msgstr "el objeto relacionado `%(app_label)s.%(data_type)s`"
#: contrib/admindocs/views.py:206 contrib/admindocs/views.py:225
#: contrib/admindocs/views.py:230 contrib/admindocs/views.py:244
#: contrib/admindocs/views.py:258 contrib/admindocs/views.py:263
#: contrib/admindocs/views.py:209 contrib/admindocs/views.py:228
#: contrib/admindocs/views.py:233 contrib/admindocs/views.py:247
#: contrib/admindocs/views.py:261 contrib/admindocs/views.py:266
msgid "model:"
msgstr "modelo:"
#: contrib/admindocs/views.py:221 contrib/admindocs/views.py:253
#: contrib/admindocs/views.py:224 contrib/admindocs/views.py:256
#, python-format
msgid "related `%(app_label)s.%(object_name)s` objects"
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
msgid "all %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
msgid "number of %s"
msgstr "número de %s"
#: contrib/admindocs/views.py:268
#: contrib/admindocs/views.py:271
#, python-format
msgid "Fields on %s objects"
msgstr "Campos en %s objetos"
#: contrib/admindocs/views.py:331 contrib/admindocs/views.py:342
#: contrib/admindocs/views.py:344 contrib/admindocs/views.py:350
#: contrib/admindocs/views.py:351 contrib/admindocs/views.py:353
#: contrib/admindocs/views.py:334 contrib/admindocs/views.py:345
#: contrib/admindocs/views.py:347 contrib/admindocs/views.py:353
#: contrib/admindocs/views.py:354 contrib/admindocs/views.py:356
msgid "Integer"
msgstr "Entero"
#: contrib/admindocs/views.py:332
#: contrib/admindocs/views.py:335
msgid "Boolean (Either True or False)"
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
msgid "String (up to %(max_length)s)"
msgstr "Cadena (máximo %(max_length)s)"
#: contrib/admindocs/views.py:334
#: contrib/admindocs/views.py:337
msgid "Comma-separated integers"
msgstr "Enteros separados por comas"
#: contrib/admindocs/views.py:335
#: contrib/admindocs/views.py:338
msgid "Date (without time)"
msgstr "Fecha (sin hora)"
#: contrib/admindocs/views.py:336
#: contrib/admindocs/views.py:339
msgid "Date (with time)"
msgstr "Fecha (con hora)"
#: contrib/admindocs/views.py:337
#: contrib/admindocs/views.py:340
msgid "Decimal number"
msgstr "Número decimal"
#: contrib/admindocs/views.py:338
#: contrib/admindocs/views.py:341
msgid "E-mail address"
msgstr "Dirección de correo electrónico"
#: contrib/admindocs/views.py:339 contrib/admindocs/views.py:340
#: contrib/admindocs/views.py:343
#: contrib/admindocs/views.py:342 contrib/admindocs/views.py:343
#: contrib/admindocs/views.py:346
msgid "File path"
msgstr "Ruta de fichero"
#: contrib/admindocs/views.py:341
#: contrib/admindocs/views.py:344
msgid "Floating point number"
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"
msgstr "Dirección IP"
#: contrib/admindocs/views.py:347
#: contrib/admindocs/views.py:350
msgid "Boolean (Either True, False or None)"
msgstr "Booleano (Verdadero, Falso o Nulo)"
#: contrib/admindocs/views.py:348
#: contrib/admindocs/views.py:351
msgid "Relation to parent model"
msgstr "Relación con el modelo padre"
#: contrib/admindocs/views.py:349
#: contrib/admindocs/views.py:352
msgid "Phone number"
msgstr "Número de teléfono"
#: contrib/admindocs/views.py:354
#: contrib/admindocs/views.py:357
msgid "Text"
msgstr "Texto"
#: contrib/admindocs/views.py:355
#: contrib/admindocs/views.py:358
msgid "Time"
msgstr "Hora"
#: contrib/admindocs/views.py:356 contrib/comments/forms.py:95
#: contrib/comments/templates/comments/moderation_queue.html:37
#: contrib/admindocs/views.py:359 contrib/comments/forms.py:95
#: contrib/flatpages/admin.py:8 contrib/flatpages/models.py:7
msgid "URL"
msgstr "URL"
#: contrib/admindocs/views.py:357
#: contrib/admindocs/views.py:360
msgid "U.S. state (two uppercase letters)"
msgstr "Estado de los EEUU (dos letras mayúsculas)"
#: contrib/admindocs/views.py:358
#: contrib/admindocs/views.py:361
msgid "XML text"
msgstr "Texto XML"
#: contrib/admindocs/views.py:384
#: contrib/admindocs/views.py:387
#, python-format
msgid "%s does not appear to be a urlpattern object"
msgstr "%s no parece ser un objeto urlpattern"
@ -1441,22 +1439,53 @@ msgstr "usuarios"
msgid "message"
msgstr "mensaje"
#: contrib/auth/views.py:56
#: contrib/auth/views.py:60
msgid "Logged out"
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."
msgstr "Introduzca una dirección de correo electrónico válida."
#: contrib/comments/admin.py:11
#: contrib/comments/admin.py:12
msgid "Content"
msgstr "contenido"
#: contrib/comments/admin.py:14
#: contrib/comments/admin.py:15
msgid "Metadata"
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
#, python-format
msgid "%(site_name)s comments"
@ -1468,7 +1497,6 @@ msgid "Latest comments on %(site_name)s"
msgstr "Últimos comentarios en %(site_name)s"
#: contrib/comments/forms.py:93
#: contrib/comments/templates/comments/moderation_queue.html:34
msgid "Name"
msgstr "Nombre"
@ -1477,7 +1505,6 @@ msgid "Email address"
msgstr "dirección de correo electrónico"
#: contrib/comments/forms.py:96
#: contrib/comments/templates/comments/moderation_queue.html:35
msgid "Comment"
msgstr "Comentario"
@ -1598,14 +1625,13 @@ msgstr "marcas de comentario"
#: contrib/comments/templates/comments/approve.html:4
msgid "Approve a comment"
msgstr "Aprovar un comentario"
msgstr "Aprobar un comentario"
#: contrib/comments/templates/comments/approve.html:7
msgid "Really make this comment public?"
msgstr "Realmente desea hacer este comentario público?"
#: contrib/comments/templates/comments/approve.html:12
#: contrib/comments/templates/comments/moderation_queue.html:49
msgid "Approve"
msgstr "Aprobar"
@ -1631,7 +1657,6 @@ msgid "Really remove this comment?"
msgstr "¿Realmente desea eliminar este comentario?"
#: contrib/comments/templates/comments/delete.html:12
#: contrib/comments/templates/comments/moderation_queue.html:53
msgid "Remove"
msgstr "Eliminar"
@ -1665,39 +1690,6 @@ msgstr "Enviar"
msgid "Preview"
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
msgid "Thanks for commenting"
msgstr "Gracias por comentar"
@ -1790,7 +1782,7 @@ msgstr "página estática"
msgid "flat pages"
msgstr "páginas estáticas"
#: contrib/formtools/wizard.py:130
#: contrib/formtools/wizard.py:132
msgid ""
"We apologize, but your form has expired. Please continue filling out the "
"form from this page."
@ -1815,8 +1807,8 @@ msgid ""
"An error occurred when transforming the geometry to the SRID of the geometry "
"form field."
msgstr ""
"Ocurrió un error al transformar la geometria al SRID de la geometria "
"del campo de formulario."
"Ocurrió un error al transformar la geometria al SRID de la geometria del "
"campo de formulario."
#: contrib/humanize/templatetags/humanize.py:19
msgid "th"
@ -2611,6 +2603,10 @@ msgstr "El número de cuenta bancaria es incorrecto."
msgid "Enter a valid Finnish social security number."
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
msgid "Enter a zip code in the format 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
msgid "National Business Register Number (REGON) consists of 9 or 14 digits."
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
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."
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 ""
"Hold down \"Control\", or \"Command\" on a Mac, to select more than one."
msgstr ""
"Mantenga presionado \"Control\", o \"Command\" en un Mac, para seleccionar "
"más de una opción."
#: db/models/fields/related.py:894
#: db/models/fields/related.py:930
#, python-format
msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid."
msgid_plural ""
@ -3960,88 +3957,88 @@ msgstr[1] ""
"Por favor, introduzca IDs de %(self)s válidos. Los valores %(value)r no son "
"válidos."
#: forms/fields.py:54
#: forms/fields.py:53
msgid "This field is required."
msgstr "Este campo es obligatorio."
#: forms/fields.py:55
#: forms/fields.py:54
msgid "Enter a valid value."
msgstr "Introduzca un valor correcto."
#: forms/fields.py:138
#: forms/fields.py:137
#, python-format
msgid "Ensure this value has at most %(max)d characters (it has %(length)d)."
msgstr ""
"Asegúrese de que su texto tiene a lo más %(max)d caracteres (actualmente "
"tiene %(length)d)."
#: forms/fields.py:139
#: forms/fields.py:138
#, python-format
msgid "Ensure this value has at least %(min)d characters (it has %(length)d)."
msgstr ""
"Asegúrese de que su texto tiene al menos %(min)d caracteres (actualmente "
"tiene %(length)d)."
#: forms/fields.py:166
#: forms/fields.py:165
msgid "Enter a whole number."
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
msgid "Ensure this value is less than or equal to %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
msgid "Ensure this value is greater than or equal to %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."
msgstr "Introduzca un número."
#: forms/fields.py:227
#: forms/fields.py:226
#, python-format
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."
#: forms/fields.py:228
#: forms/fields.py:227
#, python-format
msgid "Ensure that there are no more than %s decimal places."
msgstr "Asegúrese de que no hay más de %s decimales."
#: forms/fields.py:229
#: forms/fields.py:228
#, python-format
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."
#: forms/fields.py:288 forms/fields.py:863
#: forms/fields.py:287 forms/fields.py:862
msgid "Enter a valid date."
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."
msgstr "Introduzca una hora válida."
#: forms/fields.py:361
#: forms/fields.py:360
msgid "Enter a valid date/time."
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."
msgstr ""
"No se ha enviado ningún fichero. Compruebe el tipo de codificación en el "
"formulario."
#: forms/fields.py:448
#: forms/fields.py:447
msgid "No file was submitted."
msgstr "No se ha enviado ningún fichero"
#: forms/fields.py:449
#: forms/fields.py:448
msgid "The submitted file is empty."
msgstr "El fichero enviado está vacío."
#: forms/fields.py:450
#: forms/fields.py:449
#, python-format
msgid ""
"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 "
"tiene %(length)d)."
#: forms/fields.py:483
#: forms/fields.py:482
msgid ""
"Upload a valid image. The file you uploaded was either not an image or a "
"corrupted image."
@ -4057,29 +4054,29 @@ msgstr ""
"Envíe una imagen válida. El fichero que ha enviado no era una imagen o se "
"trataba de una imagen corrupta."
#: forms/fields.py:544
#: forms/fields.py:543
msgid "Enter a valid URL."
msgstr "Introduzca una URL válida."
#: forms/fields.py:545
#: forms/fields.py:544
msgid "This URL appears to be a broken link."
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
msgid "Select a valid choice. %(value)s is not one of the available choices."
msgstr ""
"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."
msgstr "Introduzca una lista de valores."
#: forms/fields.py:892
#: forms/fields.py:891
msgid "Enter a valid IPv4 address."
msgstr "Introduzca una dirección IPv4 válida."
#: forms/fields.py:902
#: forms/fields.py:901
msgid ""
"Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."
msgstr ""
@ -4090,28 +4087,28 @@ msgstr ""
msgid "Order"
msgstr "Orden"
#: forms/models.py:367
#: forms/models.py:363
#, python-format
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"
#: forms/models.py:381 forms/models.py:389
#: forms/models.py:377 forms/models.py:385
#, python-format
msgid "%(model_name)s with this %(field_label)s already exists."
msgstr "Ya existe %(model_name)s con este %(field_label)s."
#: forms/models.py:594
#: forms/models.py:590
#, python-format
msgid "Please correct the duplicate data for %(field)s."
msgstr "Por favor, corrija el dato duplicado para %(field)s."
#: forms/models.py:598
#: forms/models.py:594
#, python-format
msgid "Please correct the duplicate data for %(field)s, which must be unique."
msgstr ""
"Por favor corriga el dato duplicado para %(field)s, el cual debe ser único."
#: forms/models.py:604
#: forms/models.py:600
#, python-format
msgid ""
"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 "
"único para %(lookup)s en %(date_field)s."
#: forms/models.py:612
#: forms/models.py:608
msgid "Please correct the duplicate values below."
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."
msgstr ""
"La clave foránea en linea no coincide con la clave primaria de la instancia "
"padre."
#: forms/models.py:930
#: forms/models.py:926
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."
#: forms/models.py:1004
#: forms/models.py:1000
#, python-format
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."
#: forms/models.py:1006
#: forms/models.py:1002
#, python-format
msgid "\"%s\" is not a valid value for a primary key."
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."
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_plural "verbose_name_plural"
#~ 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
warnings.warn(
"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)
def validate_inline(cls, parent, parent_model):
# model is already verified to exist and be a Model
if cls.fk_name: # default value is None
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)
for field in cls.fields:
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:
raise ImproperlyConfigured('Both fieldsets and fields are specified in %s.' % cls.__name__)
if len(cls.fields) > len(set(cls.fields)):
@ -214,11 +219,28 @@ def validate_base(cls, model):
raise ImproperlyConfigured("'fields' key is required in "
"%s.fieldsets[%d][1] field options dict."
% (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)
if len(flattened_fieldsets) > len(set(flattened_fieldsets)):
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
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
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.
"""
from django.db import connection
@ -38,7 +38,7 @@ MISC_TERMS = ['isnull']
# Assacceptable lookup types for Oracle spatial.
MYSQL_GIS_TERMS = MYSQL_GIS_FUNCTIONS.keys()
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):
"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)
if lookup_info:
return "%s(%s, %%s)" % (lookup_info, geo_col)
# Handling 'isnull' lookup type
# TODO: Is this needed because MySQL cannot handle NULL
# geometries in its spatial indices.

View File

@ -3,8 +3,8 @@
routine for Oracle Spatial.
Please note that WKT support is broken on the XE version, and thus
this backend will not work on such platforms. Specifically, XE lacks
support for an internal JVM, and Java libraries are required to use
this backend will not work on such platforms. Specifically, XE lacks
support for an internal JVM, and Java libraries are required to use
the WKT constructors.
"""
import re
@ -31,10 +31,10 @@ TRANSFORM = 'SDO_CS.TRANSFORM'
UNION = 'SDO_GEOM.SDO_UNION'
UNIONAGG = 'SDO_AGGR_UNION'
# We want to get SDO Geometries as WKT because it is much easier to
# instantiate GEOS proxies from WKT than SDO_GEOMETRY(...) strings.
# However, this adversely affects performance (i.e., Java is called
# to convert to WKT on every query). If someone wishes to write a
# We want to get SDO Geometries as WKT because it is much easier to
# instantiate GEOS proxies from WKT than SDO_GEOMETRY(...) strings.
# However, this adversely affects performance (i.e., Java is called
# to convert to WKT on every query). If someone wishes to write a
# SDO_GEOMETRY(...) parser in Python, let me know =)
GEOM_SELECT = 'SDO_UTIL.TO_WKTGEOMETRY(%s)'
@ -50,7 +50,7 @@ class SDOOperation(SpatialFunction):
class SDODistance(SpatialFunction):
"Class for Distance queries."
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')
class SDOGeomRelate(SpatialFunction):
@ -59,7 +59,7 @@ class SDOGeomRelate(SpatialFunction):
# 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').
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)
class SDORelate(SpatialFunction):
@ -81,7 +81,7 @@ DISTANCE_FUNCTIONS = {
'distance_gte' : (SDODistance('>='), dtypes),
'distance_lt' : (SDODistance('<'), dtypes),
'distance_lte' : (SDODistance('<='), dtypes),
'dwithin' : (SDOOperation('SDO_WITHIN_DISTANCE',
'dwithin' : (SDOOperation('SDO_WITHIN_DISTANCE',
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
lookup_info = ORACLE_GEOMETRY_FUNCTIONS.get(lookup_type, False)
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.
if isinstance(lookup_info, tuple):
# 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
if not isinstance(geo_annot.value, tuple):
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)
# Ensuring the argument type matches what we expect.
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])))
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.
return sdo_op(geo_annot.value[1]).as_sql(geo_col)
else:
@ -144,7 +144,7 @@ def get_geo_where_clause(table_alias, name, lookup_type, geo_annot):
return sdo_op.as_sql(geo_col)
else:
# 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'
return lookup_info.as_sql(geo_col)
elif lookup_type == 'isnull':

View File

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

View File

@ -2,7 +2,7 @@
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.extensions import ISQLQuote
@ -11,7 +11,7 @@ class PostGISAdaptor(object):
"Initializes on the geometry."
# Getting the WKB (in string form, to allow easy pickling of
# the adaptor) and the SRID from the geometry.
self.wkb = str(geom.wkb)
self.ewkb = str(geom.ewkb)
self.srid = geom.srid
def __conform__(self, proto):
@ -30,7 +30,7 @@ class PostGISAdaptor(object):
def getquoted(self):
"Returns a properly quoted string for use in PostgreSQL/PostGIS."
# 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):
return self

View File

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

View File

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

View File

@ -34,6 +34,9 @@ class GeoManager(Manager):
def extent(self, *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):
return self.get_query_set().geojson(*args, **kwargs)

View File

@ -110,6 +110,14 @@ class GeoQuerySet(QuerySet):
"""
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):
"""
Returns a GeoJSON representation of the geomtry field in a `geojson`
@ -524,12 +532,14 @@ class GeoQuerySet(QuerySet):
else:
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'
length = func == 'length'
perimeter = func == 'perimeter'
if not (distance or length or perimeter):
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
# extra distance parameters. Here we set up the
@ -604,7 +614,7 @@ class GeoQuerySet(QuerySet):
# some error checking is required.
if not isinstance(geo_field, PointField):
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')
# The `function` procedure argument needs to be set differently for
# geodetic distance calculations.
@ -617,9 +627,16 @@ class GeoQuerySet(QuerySet):
elif length or perimeter:
procedure_fmt = '%(geo_col)s'
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_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`.
s = {'select_field' : DistanceField(dist_att),

View File

@ -11,6 +11,9 @@ geo_template = '%(function)s(%(field)s)'
def convert_extent(box):
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):
raise NotImplementedError('Aggregate method not implemented for this spatial backend.')
@ -23,6 +26,14 @@ if SpatialBackend.postgis:
xmax, ymax = map(float, ur.split())
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):
if hex: return SpatialBackend.Geometry(hex)
else: return None
@ -94,7 +105,7 @@ class Collect(GeoAggregate):
sql_function = SpatialBackend.collect
class Extent(GeoAggregate):
is_extent = True
is_extent = '2D'
sql_function = SpatialBackend.extent
if SpatialBackend.oracle:
@ -102,6 +113,10 @@ if SpatialBackend.oracle:
Extent.conversion_class = GeomField
Extent.sql_template = '%(function)s(%(field)s)'
class Extent3D(GeoAggregate):
is_extent = '3D'
sql_function = SpatialBackend.extent3d
class MakeLine(GeoAggregate):
conversion_class = GeomField
sql_function = SpatialBackend.make_line

View File

@ -262,7 +262,10 @@ class GeoQuery(sql.Query):
"""
if isinstance(aggregate, self.aggregates_module.GeoAggregate):
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:
return self.aggregates_module.convert_geom(value, aggregate.source)
else:

View File

@ -214,13 +214,7 @@ class OGRGeometry(GDALBase):
@property
def geom_type(self):
"Returns the Type for this Geometry."
try:
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))
return OGRGeomType(capi.get_geom_type(self.ptr))
@property
def geom_name(self):
@ -684,4 +678,11 @@ GEO_CLASSES = {1 : Point,
6 : MultiPolygon,
7 : GeometryCollection,
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):
"Encapulates OGR Geometry Types."
wkb25bit = -2147483648
# Dictionary of acceptable OGRwkbGeometryType s and their string names.
_types = {0 : 'Unknown',
1 : 'Point',
@ -15,6 +17,13 @@ class OGRGeomType(object):
7 : 'GeometryCollection',
100 : 'None',
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.
_str_types = dict([(v.lower(), k) for k, v in _types.items()])
@ -68,7 +77,7 @@ class OGRGeomType(object):
@property
def django(self):
"Returns the Django GeometryField for this OGR Type."
s = self.name
s = self.name.replace('25D', '')
if s in ('LinearRing', 'None'):
return None
elif s == 'Unknown':

View File

@ -1,5 +1,5 @@
# Needed ctypes routines
from ctypes import byref
from ctypes import c_double, byref
# Other GDAL imports.
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.feature import Feature
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
# 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:
# 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))
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 ####
def get_fields(self, field_name):
"""

View File

@ -3,7 +3,7 @@
related data structures. OGR_Dr_*, OGR_DS_*, OGR_L_*, OGR_F_*,
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.libgdal import lgdal
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])
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])
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 ###
get_fd_geom_type = int_output(lgdal.OGR_FD_GetGeomType, [c_void_p])

View File

@ -1,13 +1,11 @@
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 import gis
# Path for SHP files
data_path = os.path.join(os.path.dirname(gis.__file__), 'tests' + os.sep + 'data')
def get_ds_file(name, ext):
return os.sep.join([data_path, name, name + '.%s' % ext])
# 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]]',
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)),
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.
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']},
@ -191,7 +189,41 @@ class DataSourceTest(unittest.TestCase):
if hasattr(source, '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():
s = unittest.TestSuite()
s.addTest(unittest.makeSuite(DataSourceTest))

View File

@ -46,6 +46,13 @@ class OGRGeomTest(unittest.TestCase):
self.assertEqual(0, gt.num)
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):
"Testing WKT output."
for g in wkt_out:
@ -418,6 +425,17 @@ class OGRGeomTest(unittest.TestCase):
xmax, ymax = max(x), max(y)
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():
s = unittest.TestSuite()
s.addTest(unittest.makeSuite(OGRGeomTest))

View File

@ -357,33 +357,53 @@ class GEOSGeometry(GEOSBase, ListMixin):
#### Output Routines ####
@property
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)
else: return self.wkt
@property
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)
@property
def hex(self):
"""
Returns the HEX of the Geometry -- please note that the SRID is not
included in this representation, because the GEOS C library uses
-1 by default, even if the SRID is set.
Returns the WKB of this Geometry in hexadecimal form. Please note
that the SRID and Z values are not included in this representation
because it is not a part of the OGC specification (use the `hexewkb`
property instead).
"""
# A possible faster, all-python, implementation:
# str(self.wkb).encode('hex')
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
def json(self):
"""
Returns GeoJSON representation of this Geometry if GDAL 1.5+
is installed.
"""
if gdal.GEOJSON:
if gdal.GEOJSON:
return self.ogr.json
else:
raise GEOSException('GeoJSON output only supported on GDAL 1.5+.')
@ -391,9 +411,28 @@ class GEOSGeometry(GEOSBase, ListMixin):
@property
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)
@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
def kml(self):
"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.
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 GEOS_PREPARE:

View File

@ -14,19 +14,19 @@ class IOBase(GEOSBase):
"Base class for GEOS I/O objects."
def __init__(self):
# Getting the pointer with the constructor.
self.ptr = self.constructor()
self.ptr = self._constructor()
def __del__(self):
# 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 ###
# Non-public class for internal use because its `read` method returns
# _pointers_ instead of a GEOSGeometry object.
class _WKTReader(IOBase):
constructor = capi.wkt_reader_create
destructor = capi.wkt_reader_destroy
_constructor = capi.wkt_reader_create
_destructor = capi.wkt_reader_destroy
ptr_type = capi.WKT_READ_PTR
def read(self, wkt):
@ -39,8 +39,8 @@ class WKTReader(_WKTReader):
return GEOSGeometry(super(WKTReader, self).read(wkt))
class WKTWriter(IOBase):
constructor = capi.wkt_writer_create
destructor = capi.wkt_writer_destroy
_constructor = capi.wkt_writer_create
_destructor = capi.wkt_writer_destroy
ptr_type = capi.WKT_WRITE_PTR
def write(self, geom):
@ -51,8 +51,8 @@ class WKTWriter(IOBase):
# Non-public class for the same reason as _WKTReader above.
class _WKBReader(IOBase):
constructor = capi.wkb_reader_create
destructor = capi.wkb_reader_destroy
_constructor = capi.wkb_reader_create
_destructor = capi.wkb_reader_destroy
ptr_type = capi.WKB_READ_PTR
def read(self, wkb):
@ -71,8 +71,8 @@ class WKBReader(_WKBReader):
return GEOSGeometry(super(WKBReader, self).read(wkb))
class WKBWriter(IOBase):
constructor = capi.wkb_writer_create
destructor = capi.wkb_writer_destroy
_constructor = capi.wkb_writer_create
_destructor = capi.wkb_writer_destroy
ptr_type = capi.WKB_WRITE_PTR
def write(self, geom):
@ -121,3 +121,10 @@ wkt_r = _WKTReader()
wkt_w = WKTWriter()
wkb_r = _WKBReader()
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 ###
# Deprecated creation routines from WKB, HEX, WKT
# Deprecated creation and output routines from WKB, HEX, WKT
from_hex = bin_constructor(lgeos.GEOSGeomFromHEX_buf)
from_wkb = bin_constructor(lgeos.GEOSGeomFromWKB_buf)
from_wkt = geom_output(lgeos.GEOSGeomFromWKT, [c_char_p])
# Output routines
to_hex = bin_output(lgeos.GEOSGeomToHEX_buf)
to_wkb = bin_output(lgeos.GEOSGeomToWKB_buf)
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_type = string_from_geom(lgeos.GEOSGeomType)
geos_typeid = int_from_geom(lgeos.GEOSGeomTypeId)

View File

@ -71,6 +71,49 @@ class GEOSTest(unittest.TestCase):
geom = fromstr(g.wkt)
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):
"Testing KML output."
for tg in wkt_out:

View File

@ -9,9 +9,10 @@ def geo_suite():
some backends).
"""
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.utils import HAS_GEOIP
from django.contrib.gis.tests.utils import mysql
from django.contrib.gis.tests.utils import postgis, mysql
# The test suite.
s = unittest.TestSuite()
@ -32,6 +33,10 @@ def geo_suite():
if not mysql:
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:
# These tests require GDAL.
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
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>
<OGRVRTLayer name="test_vrt">
<SrcDataSource relativeToVRT="1">test_vrt.csv</SrcDataSource>
<GeometryType>wkbPoint</GeometryType>
<GeometryType>wkbPoint25D</GeometryType>
<GeometryField encoding="PointFromColumns" x="POINT_X" y="POINT_Y" z="NUM"/>
</OGRVRTLayer>
</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."
pnt = City.objects.get(name='Pueblo').point
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,
),
)
# 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:
# LayerMapping requires DJANGO_SETTINGS_MODULE to be set,
# 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:
pass

View File

@ -133,6 +133,9 @@ class LayerMapping(object):
MULTI_TYPES = {1 : OGRGeomType('MultiPoint'),
2 : OGRGeomType('MultiLineString'),
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
@ -282,19 +285,28 @@ class LayerMapping(object):
if self.geom_field:
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:
gtype = OGRGeomType(ogr_name)
if coord_dim == 3:
gtype = OGRGeomType(ogr_name + '25D')
else:
gtype = OGRGeomType(ogr_name)
except OGRException:
raise LayerMapError('Invalid mapping for GeometryField "%s".' % field_name)
# Making sure that the OGR Layer's Geometry is compatible.
ltype = self.layer.geom_type
if not (gtype == ltype or self.make_multi(ltype, model_field)):
raise LayerMapError('Invalid mapping geometry; model has %s, feature has %s.' % (fld_name, gtype))
if not (ltype.name.startswith(gtype.name) or self.make_multi(ltype, model_field)):
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
# that is a Geometry.
# that is a Geometry. Also setting the coordinate dimension
# attribute.
self.geom_field = field_name
self.coord_dim = coord_dim
fields_val = model_field
elif isinstance(model_field, models.ForeignKey):
if isinstance(ogr_name, dict):
@ -482,6 +494,10 @@ class LayerMapping(object):
if necessary (for example if the model field is MultiPolygonField while
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):
# Constructing a multi-geometry type to contain the single geometry
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.conf import settings
# Reset the urlconf for this thread.
urlresolvers.set_urlconf(None)
# Apply request middleware
for middleware_method in self._request_middleware:
response = middleware_method(request)
@ -77,61 +80,69 @@ class BaseHandler(object):
# Get urlconf from request object, if available. Otherwise use default.
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)
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:
response = callback(request, *callback_args, **callback_kwargs)
except Exception, e:
# If the view raised an exception, run it through exception
# 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)
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
raise
# Complain if the view returned None (a common error).
if response is None:
try:
view_name = callback.func_name # If it's a function
except AttributeError:
view_name = callback.__class__.__name__ + '.__call__' # If it's a class
raise ValueError, "The view %s.%s didn't return an HttpResponse object." % (callback.__module__, view_name)
response = callback(request, *callback_args, **callback_kwargs)
except Exception, e:
# If the view raised an exception, run it through exception
# 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
except http.Http404, e:
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:
# Complain if the view returned None (a common error).
if response is None:
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)
view_name = callback.func_name # If it's a function
except AttributeError:
view_name = callback.__class__.__name__ + '.__call__' # If it's a class
raise ValueError, "The view %s.%s didn't return an HttpResponse object." % (callback.__module__, view_name)
return response
except http.Http404, e:
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:
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):
"""

View File

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

View File

@ -299,7 +299,7 @@ class ManagementUtility(object):
# subcommand
if cword == 1:
print ' '.join(filter(lambda x: x.startswith(curr), subcommands))
print ' '.join(sorted(filter(lambda x: x.startswith(curr), subcommands)))
# subcommand options
# special case: the 'help' subcommand has no options
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)
# 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:
opt_label = option[0]
# append '=' to options which require args

View File

@ -65,7 +65,7 @@ class Command(NoArgsCommand):
opts = model._meta
if (connection.introspection.table_name_converter(opts.db_table) in tables or
(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
sql, references = connection.creation.sql_create_model(model, self.style, seen_models)
seen_models.add(model)

View File

@ -10,6 +10,7 @@ a string) and returns a tuple in this format:
import re
from django.http import Http404
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist
from django.utils.datastructures import MultiValueDict
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.
_prefixes = {}
# Overridden URLconfs for each thread are stored here.
_urlconfs = {}
class Resolver404(Http404):
pass
@ -300,9 +304,13 @@ class RegexURLResolver(object):
"arguments '%s' not found." % (lookup_view_s, args, kwargs))
def resolve(path, urlconf=None):
if urlconf is None:
urlconf = get_urlconf()
return get_resolver(urlconf).resolve(path)
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)
args = args or []
kwargs = kwargs or {}
@ -371,3 +379,25 @@ def get_script_prefix():
"""
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 = []
pk_val = None
if self._deferred:
from django.db.models.query_utils import deferred_class_factory
factory = deferred_class_factory
for field in self._meta.fields:
if isinstance(self.__class__.__dict__.get(field.attname),
DeferredAttribute):
@ -374,8 +376,9 @@ class Model(object):
# once.
obj = self.__class__.__dict__[field.attname]
model = obj.model_ref()
return (model_unpickle, (model, defers), data)
else:
factory = simple_class_factory
return (model_unpickle, (model, defers, factory), data)
def _get_pk_val(self, meta=None):
if not meta:
@ -849,12 +852,20 @@ def get_absolute_url(opts, func, self, *args, **kwargs):
class Empty(object):
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.
"""
from django.db.models.query_utils import deferred_class_factory
cls = deferred_class_factory(model, attrs)
cls = factory(model, attrs)
return cls.__new__(cls)
model_unpickle.__safe_for_unpickle__ = True

View File

@ -586,9 +586,13 @@ class ReverseManyRelatedObjectsDescriptor(object):
# ReverseManyRelatedObjectsDescriptor instance.
def __init__(self, m2m_field):
self.field = m2m_field
def _through(self):
# through is provided so that you have easy access to the through
# model (Book.authors.through) for inlines, etc.
self.through = m2m_field.rel.through
# model (Book.authors.through) for inlines, etc. This is done as
# 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):
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)
else:
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['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),
'managed': managed,
'auto_created': klass,
'app_label': klass._meta.app_label,
'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.
return type(name, (models.Model,), {
'Meta': meta,
'__module__': module,
'__module__': klass.__module__,
from_: models.ForeignKey(klass, 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_fields = [None]
self.select_related = False # See #7097.
self.extra = {}
self.set_extra_mask([])
self.distinct = True
self.order_by = order == 'ASC' and [1] or [-1]

View File

@ -201,7 +201,5 @@ The Django open-source project
* **Django over time:**
: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>`
.. _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/
.. _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
-----------

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
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.::
<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
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
``CsrfViewMiddleware``.
Use of the ``CsrfResponseMiddleware`` is not recommended because of the
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
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.
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
page doesn't also contain internal forms that require the token.
.. _ref-csrf-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
customised templates to any of the view functions of contrib apps (whether
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
some reason, you will be forced to use ``CsrfResponseMiddleware`` for these
views to continue working).
@ -364,7 +366,7 @@ exactly that.
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
a ``Vary: Cookie`` header to the response. Similarly,
``CsrfResponseMiddleware`` will send the ``Vary: Cookie`` header if it inserted

View File

@ -78,11 +78,9 @@ Examples of output::
Displaying debug output
-----------------------
.. django-admin-option:: --verbosity <amount>
Use ``--verbosity`` to specify the amount of notification and debug information
Use :djadminopt:`--verbosity` to specify the amount of notification and debug information
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
=====================
@ -90,6 +88,8 @@ Available subcommands
cleanup
-------
.. django-admin:: cleanup
.. versionadded:: 1.0
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
---------------
.. django-admin:: compilemessages
.. versionchanged:: 1.0
Before 1.0 this was the "bin/compile-messages.py" command.
Compiles .po files created with ``makemessages`` to .mo files for use with
the builtin gettext support. See :ref:`topics-i18n`.
--locale
~~~~~~~~
Use the ``--locale`` or ``-l`` option to specify the locale to process.
If not provided all locales are processed.
Use the :djadminopt:`--locale`` option to specify the locale to process.
If not provided, all locales are processed.
Example usage::
@ -117,7 +116,7 @@ Example usage::
createcachetable
----------------
.. django-admin:: createcachetable <tablename>
.. django-admin:: createcachetable
Creates a cache table named ``tablename`` for use with the database cache
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``,
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
application(s).
@ -215,18 +214,17 @@ directives::
django-admin.py dumpdata --exclude=auth --exclude=contenttypes
.. django-admin-option:: --format <fmt>
By default, ``dumpdata`` will format its output in JSON, but you can use the
``--format`` option to specify another format. Currently supported formats
are listed in :ref:`serialization-formats`.
By default, ``dumpdata`` will format its output in JSON, but you can use the
``--format`` option to specify another format. Currently supported formats
are listed in :ref:`serialization-formats`.
.. django-admin-option:: --indent <num>
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
pretty-print the output with a number of indentation spaces.
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
pretty-print the output with a number of indentation spaces.
.. versionadded:: 1.1
@ -239,22 +237,21 @@ model names.
flush
-----
.. django-admin: flush
.. django-admin:: flush
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
post-synchronization handlers will be re-executed, and the ``initial_data``
fixture will be re-installed.
.. 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.
The :djadminopt:`--noinput` option may be provided to suppress all user
prompts.
inspectdb
---------
.. django-admin:: inspectdb
Introspects the database tables in the database pointed-to by the
``DATABASE_NAME`` setting and outputs a Django model module (a ``models.py``
file) to standard output.
@ -296,6 +293,8 @@ only works in PostgreSQL and with certain types of MySQL tables.
loaddata <fixture fixture ...>
------------------------------
.. django-admin:: loaddata
Searches for and loads the contents of the named fixture into the database.
What's a "fixture"?
@ -382,6 +381,8 @@ installation will be aborted, and any data installed in the call to
makemessages
------------
.. django-admin:: makemessages
.. versionchanged:: 1.0
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
: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
available languages.
@ -402,8 +402,7 @@ Example usage::
django-admin.py makemessages --all
--extension
~~~~~~~~~~~
.. django-admin-option:: --extension
Use the ``--extension`` or ``-e`` option to specify a list of file extensions
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
--locale
~~~~~~~~
Use the ``--locale`` or ``-l`` option to specify the locale to process.
Use the :djadminopt:`--locale` option to specify the locale to process.
Example usage::
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.
Currently supported:
@ -434,23 +429,21 @@ Currently supported:
* ``django`` for all ``*.py`` and ``*.html`` files (default)
* ``djangojs`` for ``*.js`` files
.. _django-admin-reset:
reset <appname appname ...>
---------------------------
.. django-admin:: reset
Executes the equivalent of ``sqlreset`` for the given app name(s).
--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.
The :djadminopt:`--noinput` option may be provided to suppress all user
prompts.
runfcgi [options]
-----------------
.. django-admin:: runfcgi
Starts a set of FastCGI processes suitable for use with any Web server that
supports the FastCGI protocol. See the :ref:`FastCGI deployment documentation
<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/
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,
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
-----
.. django-admin:: shell
Starts the Python interactive interpreter.
Django will use IPython_, if it's installed. If you have IPython installed and
@ -557,11 +552,15 @@ option, like so::
sql <appname appname ...>
-------------------------
.. django-admin:: sql
Prints the CREATE TABLE SQL statements for the given app name(s).
sqlall <appname appname ...>
----------------------------
.. django-admin:: sqlall
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
@ -570,11 +569,15 @@ specify initial data.
sqlclear <appname appname ...>
------------------------------
.. django-admin:: sqlclear
Prints the DROP TABLE SQL statements for the given app name(s).
sqlcustom <appname appname ...>
-------------------------------
.. django-admin:: sqlcustom
Prints the custom SQL statements for the given app name(s).
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
--------
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 ...>
--------------------------------
.. django-admin:: sqlindexes
Prints the CREATE INDEX SQL statements for the given app name(s).
sqlreset <appname appname ...>
------------------------------
.. django-admin:: sqlreset
Prints the DROP TABLE SQL, then the CREATE TABLE SQL, for the given app name(s).
sqlsequencereset <appname appname ...>
--------------------------------------
.. django-admin:: sqlsequencereset
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
@ -620,12 +632,16 @@ of sync with its automatically incremented field data.
startapp <appname>
------------------
.. django-admin:: startapp
Creates a Django app directory structure for the given app name in the current
directory.
startproject <projectname>
--------------------------
.. django-admin:: startproject
Creates a Django project directory structure for the given project name in the
current directory.
@ -635,11 +651,11 @@ This command is disabled when the ``--settings`` option to
situations, either omit the ``--settings`` option or unset
``DJANGO_SETTINGS_MODULE``.
.. _django-admin-syncdb:
syncdb
------
.. django-admin:: syncdb
Creates the database tables for all apps in ``INSTALLED_APPS`` whose tables
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
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
"Are you sure?" confirmation messages. This is useful if ``django-admin.py``
is being executed as an unattended, automated script.
test <app or test identifier>
-----------------------------
test
----
.. django-admin:: test
Runs tests for all installed models. See :ref:`topics-testing` for more
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 ...>
--------------------------------
.. django-admin:: testserver
.. versionadded:: 1.0
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
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
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
--------
.. django-admin:: validate
Validates all installed models (according to the ``INSTALLED_APPS`` setting)
and prints validation errors to standard output.
@ -761,8 +771,7 @@ Default options
Although some subcommands may allow their own custom options, every subcommand
allows for the following options:
--pythonpath
------------
.. django-admin-option:: --pythonpath
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
--settings
----------
.. django-admin-option:: --settings
Example usage::
@ -792,8 +800,7 @@ variable.
Note that this option is unnecessary in ``manage.py``, because it uses
``settings.py`` from the current project by default.
--traceback
-----------
.. django-admin-option:: --traceback
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
output a full stack trace whenever an exception is raised.
.. _django-admin-verbosity:
--verbosity
-----------
.. django-admin-option:: --verbosity
Example usage::
@ -819,6 +823,23 @@ that ``django-admin.py`` should print to the console.
* ``1`` means normal output (default).
* ``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
==============
@ -844,5 +865,4 @@ distribution. It enables tab-completion of ``django-admin.py`` and
with ``sql``.
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
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
<django-admin-reset>` management command. That is, Django *manages* the
database tables' lifecycles.
tables in :djadmin:`syncdb` and remove them as part of a :djadmin:`reset`
management command. That is, Django *manages* the database tables' lifecycles.
If ``False``, no database table creation or deletion operations will be
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
will also not be created. However, a the intermediary table between one
managed and one unmanaged model *will* be created.
If you need to change this default behavior, create the intermediary
table as an explicit model (with ``managed`` set as needed) and use the
:attr:`ManyToManyField.through` attribute to make the relation use your

View File

@ -51,6 +51,18 @@ comment
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
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`_
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
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
format-string key called 'permanent', which is highly unlikely.
.. _deprecated-features-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:
=============
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
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::
:maxdepth: 1
0.95
0.96
1.0-alpha-1
1.0-alpha-2
1.0-beta
1.0-beta-2
1.0
1.0.1
1.0.2
1.1-alpha-1
1.1-beta-1
1.1-rc-1
1.2
1.1 release
-----------
.. toctree::
:maxdepth: 1
1.1.2
1.1
.. seealso::
1.0 release
-----------
.. toctree::
:maxdepth: 1
The list of `backwards-incompatible changes`_ made in the current
development "trunk". If you're running versions of Django newer than an
official release, you should keep track of new pieces pointed there. It's
also fun reading if you're looking forward to new versions of Django.
1.0.2
1.0.1
1.0
.. _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
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
``"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
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
activate dummy caching, set ``CACHE_BACKEND`` like so::
CACHE_BACKEND = 'dummy:///'
CACHE_BACKEND = 'dummy://'
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``::
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
arguments.
@ -451,11 +451,11 @@ The low-level cache API
Sometimes, caching an entire rendered page doesn't gain you very much and is,
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.
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
cache the entire result (since some of the data changes often), but you'd still
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
cache the entire result (since some of the data changes often), but you'd still
want to cache the results that rarely change.
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:
* ``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.
* ``django.middleware.gzip.GZipMiddleware`` compresses responses for all
* ``django.middleware.gzip.GZipMiddleware`` compresses responses for all
moderns browsers, saving bandwidth and transfer time.
Order of MIDDLEWARE_CLASSES

View File

@ -10,7 +10,7 @@ Sending e-mail
Although Python makes sending e-mail relatively easy via the `smtplib
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
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.
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
SMTP server. If this isn't provided, Django will use the value of the
``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.
See the documentation on :ref:`E-mail backends <topic-email-backends>`
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.
For convenience, :class:`~django.core.mail.EmailMessage` provides a simple
``send()`` method for sending a single email. If you need to send multiple
messages, the email backend API :ref:`provides an alternative
``send()`` method for sending a single e-mail. If you need to send multiple
messages, the e-mail backend API :ref:`provides an alternative
<topics-sending-multiple-emails>`.
EmailMessage Objects
@ -264,7 +264,7 @@ For example::
The class has the following methods:
* ``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
used. If the keyword argument ``fail_silently`` is ``True``, exceptions
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:
* ``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
: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)
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.
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.
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
SMTP backend (which is the default), these backends are only useful during
testing and development. If you have special email sending requirements, you
can :ref:`write your own email backend <topic-custom-email-backend>`.
testing and development. If you have special e-mail sending requirements, you
can :ref:`write your own e-mail backend <topic-custom-email-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.
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
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
: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
deprecated in favor of the generic email backend API.
to directly control the use of SMTP to send e-mail. This class has been
deprecated in favor of the generic e-mail backend API.
For backwards compatibility :class:`~django.core.mail.SMTPConnection` is
still available in ``django.core.mail`` as an alias for the SMTP backend.
New code should use :meth:`~django.core.mail.get_connection` instead.
.. _topic-email-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
convenience that can be used during development.
.. _topic-email-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
convenience that can be used during development.
.. _topic-email-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
convenience that can be used during development and testing.
.. _topic-email-dummy-backend:
Dummy backend
~~~~~~~~~~~~~
@ -500,15 +508,15 @@ implementation.
.. _topics-sending-multiple-emails:
Sending multiple emails
-----------------------
Sending multiple e-mails
------------------------
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
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
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
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::
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()
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.
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 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
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',
['to1@example.com'], connection=connection)
email1.send() # Send the email
email1.send() # Send the e-mail
# Construct two more messages
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',
['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])
# The connection was already open so send_messages() doesn't close it.
# 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.
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.
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
at your leisure.
@ -596,7 +604,7 @@ SMTPConnection
.. 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.
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,
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.
2. Django loads that Python module and looks for the variable

View File

@ -980,19 +980,21 @@ subclass::
def setUp(self):
# Test definitions as before.
call_setup_methods()
def testFluffyAnimals(self):
# A test that uses the fixtures.
call_some_test_code()
Here's specifically what will happen:
* 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
directly after ``syncdb`` was called.
directly after :djadmin:`syncdb` was called.
* Then, all the named fixtures are installed. In this example, Django will
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.
This flush/load procedure is repeated for each test in the test case, so you
@ -1028,6 +1030,7 @@ For example::
def testIndexPageView(self):
# 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
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.article import Article
>>> from django.contrib.auth.views import Site
@ -19,7 +28,6 @@
>>> a.save()
>>> a.publications.add(p)
>>> a.sites.add(current_site)
>>> a.save()
>>> a = Article.objects.get(id=1)
>>> a
@ -29,6 +37,19 @@
>>> a.sites.count()
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)
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':"""
@ -95,4 +110,48 @@ Exception: <class 'regressiontests.admin_validation.models.TwoAlbumFKAndAnE'> ha
>>> 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
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.
# Using only/defer creates some artifical entries in the app cache
# that messes up later tests. Purge all entries, just to be sure.

View File

@ -1,4 +1,29 @@
# -*- 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 time
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()
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.
# 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]
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})
[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
>>> Item.objects.dates('modified', 'day')
[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
from django.conf import settings
from django.core.urlresolvers import reverse, resolve, NoReverseMatch, Resolver404
from django.http import HttpResponseRedirect, HttpResponsePermanentRedirect
from django.shortcuts import redirect
from django.test import TestCase
import urlconf_outer
import urlconf_inner
import middleware
test_data = (
('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/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__))
)