diff --git a/AUTHORS b/AUTHORS index 50ca0af541..aab479b947 100644 --- a/AUTHORS +++ b/AUTHORS @@ -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. diff --git a/django/conf/locale/ca/LC_MESSAGES/django.mo b/django/conf/locale/ca/LC_MESSAGES/django.mo index 3039bd8fad..457a828047 100644 Binary files a/django/conf/locale/ca/LC_MESSAGES/django.mo and b/django/conf/locale/ca/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/ca/LC_MESSAGES/django.po b/django/conf/locale/ca/LC_MESSAGES/django.po index 57f8764938..f8cd8aa609 100644 --- a/django/conf/locale/ca/LC_MESSAGES/django.po +++ b/django/conf/locale/ca/LC_MESSAGES/django.po @@ -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 \n" "Language-Team: Catalan \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" diff --git a/django/conf/locale/es/LC_MESSAGES/django.mo b/django/conf/locale/es/LC_MESSAGES/django.mo index a8bd5f985d..cfb7952cae 100644 Binary files a/django/conf/locale/es/LC_MESSAGES/django.mo and b/django/conf/locale/es/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/es/LC_MESSAGES/django.po b/django/conf/locale/es/LC_MESSAGES/django.po index fb5eec0c43..7a9e4eca73 100644 --- a/django/conf/locale/es/LC_MESSAGES/django.po +++ b/django/conf/locale/es/LC_MESSAGES/django.po @@ -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 Language-" "Team: Django Spanish Team 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" diff --git a/django/conf/locale/hr/LC_MESSAGES/django.mo b/django/conf/locale/hr/LC_MESSAGES/django.mo index 7c702db7c7..354a04d78a 100644 Binary files a/django/conf/locale/hr/LC_MESSAGES/django.mo and b/django/conf/locale/hr/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/hr/LC_MESSAGES/django.po b/django/conf/locale/hr/LC_MESSAGES/django.po index 36e50cb36d..c4137e161e 100644 --- a/django/conf/locale/hr/LC_MESSAGES/django.po +++ b/django/conf/locale/hr/LC_MESSAGES/django.po @@ -1,20 +1,17 @@ -# translation of django.po to Hrvatski jezik -# This file is distributed under the same license as the Django package. -# msgid "" msgstr "" "Project-Id-Version: Django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2009-05-08 18:09+0200\n" -"PO-Revision-Date: 2009-05-08 18:19+0200\n" -"Last-Translator: Aljosa Mohorovic \n" +"POT-Creation-Date: 2009-11-23 22:06+0100\n" +"PO-Revision-Date: 2009-11-10 15:27+0100\n" +"Last-Translator: Davor Lučić \n" "Language-Team: Hrvatski Jezik >\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || " "n%100==4 ? 2 : 3);\n" -"X-Generator: Vim 7.2\n" +"X-Generator: Lokalize 1.0\n" #: conf/global_settings.py:44 msgid "Arabic" @@ -220,16 +217,16 @@ msgstr "Pojednostavljeni kineski" msgid "Traditional Chinese" msgstr "Tradicionalni kineski" -#: contrib/admin/actions.py:56 +#: contrib/admin/actions.py:60 #, python-format msgid "Successfully deleted %(count)d %(items)s." msgstr "Uspješno izbrisano %(count)d %(items)s." -#: contrib/admin/actions.py:63 contrib/admin/options.py:1025 +#: contrib/admin/actions.py:67 contrib/admin/options.py:1033 msgid "Are you sure?" msgstr "Jeste li sigurni?" -#: contrib/admin/actions.py:81 +#: contrib/admin/actions.py:85 #, python-format msgid "Delete selected %(verbose_name_plural)s" msgstr "Izbrišite odabrane %(verbose_name_plural)s" @@ -268,15 +265,15 @@ msgstr "Ovaj mjesec" msgid "This year" msgstr "Ova godina" -#: contrib/admin/filterspecs.py:147 forms/widgets.py:434 +#: contrib/admin/filterspecs.py:147 forms/widgets.py:435 msgid "Yes" msgstr "Da" -#: contrib/admin/filterspecs.py:147 forms/widgets.py:434 +#: contrib/admin/filterspecs.py:147 forms/widgets.py:435 msgid "No" msgstr "Ne" -#: contrib/admin/filterspecs.py:154 forms/widgets.py:434 +#: contrib/admin/filterspecs.py:154 forms/widgets.py:435 msgid "Unknown" msgstr "Nepoznat pojam" @@ -312,86 +309,86 @@ msgstr "zapis" msgid "log entries" msgstr "zapisi" -#: contrib/admin/options.py:133 contrib/admin/options.py:147 +#: contrib/admin/options.py:134 contrib/admin/options.py:148 msgid "None" msgstr "Nijedan" -#: contrib/admin/options.py:519 +#: contrib/admin/options.py:521 #, python-format msgid "Changed %s." msgstr "Promijenjeno %s." -#: contrib/admin/options.py:519 contrib/admin/options.py:529 +#: contrib/admin/options.py:521 contrib/admin/options.py:531 #: contrib/comments/templates/comments/preview.html:16 forms/models.py: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 "Dodano %(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 "Promijeni %(list)s za %(name)s \"%(object)s\"." -#: contrib/admin/options.py:533 +#: contrib/admin/options.py:535 #, python-format msgid "Deleted %(name)s \"%(object)s\"." msgstr "Izbrisani %(name)s \"%(object)s\"." -#: contrib/admin/options.py:537 +#: contrib/admin/options.py:539 msgid "No fields changed." msgstr "Nije bilo promjena polja." -#: 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 "%(name)s \"%(obj)s\" uspješno je dodano." -#: 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 "Možete ponovo urediti unos dolje." -#: 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 "Možete dodati još jedan %s ispod." -#: contrib/admin/options.py:633 +#: contrib/admin/options.py:636 #, python-format msgid "The %(name)s \"%(obj)s\" was changed successfully." msgstr "%(name)s \"%(obj)s\" uspješno promijenjeno." -#: 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 "" -"%(name)s \"%(obj)s\" uspješno dodan. Možete ponovo urediti unos dolje." +msgstr "%(name)s \"%(obj)s\" uspješno dodan. Možete ponovo urediti unos dolje." -#: contrib/admin/options.py:772 +#: contrib/admin/options.py:777 #, python-format msgid "Add %s" msgstr "Novi unos (%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 "Unos %(name)s sa primarnim ključem %(key)r ne postoji." -#: contrib/admin/options.py:860 +#: contrib/admin/options.py:866 #, python-format msgid "Change %s" msgstr "Promijeni %s" -#: contrib/admin/options.py:904 +#: contrib/admin/options.py:910 msgid "Database error" msgstr "Pogreška u bazi" -#: 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." @@ -400,17 +397,17 @@ msgstr[1] "%(count)s %(name)s uspješno promijenjeno." msgstr[2] "%(count)s %(name)s uspješno promijenjeno." msgstr[3] "%(count)s %(name)s uspješno promijenjeno." -#: contrib/admin/options.py:1018 +#: contrib/admin/options.py:1026 #, python-format msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "%(name)s \"%(obj)s\" uspješno izbrisan." -#: contrib/admin/options.py:1054 +#: contrib/admin/options.py:1063 #, python-format msgid "Change history: %s" msgstr "Promijeni povijest: %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-" @@ -419,11 +416,11 @@ msgstr "" "Molim unesite ispravno korisničko ime i lozinku. Uzmite u obzir da oba polja " "razlikuju velika/mala slova." -#: 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 "Molim prijavite se ponovo jer je vaš session istekao." -#: 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." @@ -431,27 +428,27 @@ msgstr "" "Izgleda da vaš browser nije podešen da prihvaća kolačiće (cookies). Molimo " "promijenite postavke, ponovno učitajte stranicu i pokušajte ponovo." -#: 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 "Korisnička imena ne mogu sadržavati '@' znak." -#: 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 "Vaša e-mail adresa nije vaše korisničko ime. Pokušajte sa '%s'" -#: contrib/admin/sites.py:360 +#: contrib/admin/sites.py:374 msgid "Site administration" msgstr "Administracija stranica" -#: 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 "Prijavi se" -#: contrib/admin/sites.py:417 +#: contrib/admin/sites.py:433 #, python-format msgid "%s administration" msgstr "%s administracija" @@ -466,27 +463,27 @@ msgstr "Jedan ili više %(fieldname)s u %(name)s: %(obj)s" msgid "One or more %(fieldname)s in %(name)s:" msgstr "Jedan ili više %(fieldname)s u %(name)s:" -#: contrib/admin/widgets.py:71 +#: contrib/admin/widgets.py:72 msgid "Date:" msgstr "Datum:" -#: contrib/admin/widgets.py:71 +#: contrib/admin/widgets.py:72 msgid "Time:" msgstr "Vrijeme:" -#: contrib/admin/widgets.py:95 +#: contrib/admin/widgets.py:96 msgid "Currently:" msgstr "Trenutno:" -#: contrib/admin/widgets.py:95 +#: contrib/admin/widgets.py:96 msgid "Change:" msgstr "Promijeni:" -#: contrib/admin/widgets.py:124 +#: contrib/admin/widgets.py:125 msgid "Lookup" msgstr "Potraži" -#: contrib/admin/widgets.py:236 +#: contrib/admin/widgets.py:237 msgid "Add Another" msgstr "Unesi još" @@ -501,7 +498,7 @@ msgstr "Ispričavamo se, ali tražena stranica nije pronađena." #: 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 @@ -554,18 +551,18 @@ msgstr "Idi" msgid "%(name)s" msgstr "%(name)s" -#: contrib/admin/templates/admin/base.html:26 +#: contrib/admin/templates/admin/base.html:27 msgid "Welcome," msgstr "Dobrodošli," -#: 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 "Dokumentacija" -#: 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 @@ -573,7 +570,7 @@ msgstr "Dokumentacija" msgid "Change password" msgstr "Promijeni lozinku" -#: 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" @@ -599,7 +596,7 @@ msgstr "Povijest" #: 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 "Pogledaj na stranicama" @@ -669,9 +666,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 "" -"Jeste li sigurni da želite izbrisati %(object_name)s? Svi navedeni objekti i " +"Jeste li sigurni da želite izbrisati odabrane %(object_name)s? Svi navedeni objekti i " "povezani unosi biti će izbrisani:" #: contrib/admin/templates/admin/filter.html:2 @@ -734,14 +731,13 @@ msgid "User" msgstr "Korisnik" #: contrib/admin/templates/admin/object_history.html:24 -#: contrib/comments/templates/comments/moderation_queue.html:33 msgid "Action" msgstr "Akcija" #: contrib/admin/templates/admin/object_history.html:30 #: utils/translation/trans_real.py:400 msgid "DATETIME_FORMAT" -msgstr "DATETIME_FORMAT" +msgstr "j. N Y. G:i T" #: contrib/admin/templates/admin/object_history.html:38 msgid "" @@ -755,6 +751,11 @@ msgstr "" msgid "Show all" msgstr "Prikaži sve" +#: contrib/admin/templates/admin/pagination.html:11 +#: contrib/admin/templates/admin/submit_line.html:3 +msgid "Save" +msgstr "Spremi" + #: contrib/admin/templates/admin/search_form.html:8 msgid "Search" msgstr "Traži" @@ -771,10 +772,6 @@ msgstr[1] "%(counter)s rezultata" msgid "%(full_result_count)s total" msgstr "%(full_result_count)s ukupno" -#: contrib/admin/templates/admin/submit_line.html:3 -msgid "Save" -msgstr "Spremi" - #: contrib/admin/templates/admin/submit_line.html:5 msgid "Save as new" msgstr "Spremi kao novi unos" @@ -978,7 +975,7 @@ msgstr "E-mail adresa:" msgid "Reset my password" msgstr "Resetiraj moju lozinku" -#: contrib/admin/templatetags/admin_list.py:299 +#: contrib/admin/templatetags/admin_list.py:304 msgid "All dates" msgstr "Svi datumi" @@ -1000,144 +997,144 @@ msgstr "stranica" msgid "template" msgstr "template" -#: 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 "tag:" -#: 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 "filter:" -#: 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 "prikaz:" -#: contrib/admindocs/views.py:187 +#: contrib/admindocs/views.py:190 #, python-format msgid "App %r not found" msgstr "Aplikacija %r nije pronađena" -#: 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 "Model %(model_name)r nije pronađen u aplikaciji %(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 "povezani `%(app_label)s.%(data_type)s` objekt" -#: contrib/admindocs/views.py:206 contrib/admindocs/views.py:228 -#: contrib/admindocs/views.py:242 contrib/admindocs/views.py:247 +#: 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:237 +#: contrib/admindocs/views.py:224 contrib/admindocs/views.py:256 #, python-format msgid "related `%(app_label)s.%(object_name)s` objects" msgstr "povezani `%(app_label)s.%(object_name)s` objekti" -#: contrib/admindocs/views.py:242 +#: contrib/admindocs/views.py:228 contrib/admindocs/views.py:261 #, python-format msgid "all %s" msgstr "svi %s" -#: contrib/admindocs/views.py:247 +#: contrib/admindocs/views.py:233 contrib/admindocs/views.py:266 #, python-format msgid "number of %s" msgstr "broj %s" -#: contrib/admindocs/views.py:252 +#: contrib/admindocs/views.py:271 #, python-format msgid "Fields on %s objects" msgstr "Polja na %s objektima" -#: contrib/admindocs/views.py:315 contrib/admindocs/views.py:326 -#: contrib/admindocs/views.py:328 contrib/admindocs/views.py:334 -#: contrib/admindocs/views.py:335 contrib/admindocs/views.py:337 +#: 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 "Cijeli broj" -#: contrib/admindocs/views.py:316 +#: contrib/admindocs/views.py:335 msgid "Boolean (Either True or False)" msgstr "Boolean (True ili False)" -#: contrib/admindocs/views.py:317 contrib/admindocs/views.py:336 +#: contrib/admindocs/views.py:336 contrib/admindocs/views.py:355 #, python-format msgid "String (up to %(max_length)s)" msgstr "Slova (do %(max_length)s)" -#: contrib/admindocs/views.py:318 +#: contrib/admindocs/views.py:337 msgid "Comma-separated integers" msgstr "Cijeli brojevi odvojeni zarezom" -#: contrib/admindocs/views.py:319 +#: contrib/admindocs/views.py:338 msgid "Date (without time)" msgstr "Datum (bez vremena/sati)" -#: contrib/admindocs/views.py:320 +#: contrib/admindocs/views.py:339 msgid "Date (with time)" msgstr "Datum (sa vremenom/satima)" -#: contrib/admindocs/views.py:321 +#: contrib/admindocs/views.py:340 msgid "Decimal number" msgstr "Decimalni broj" -#: contrib/admindocs/views.py:322 +#: contrib/admindocs/views.py:341 msgid "E-mail address" msgstr "E-mail adresa" -#: contrib/admindocs/views.py:323 contrib/admindocs/views.py:324 -#: contrib/admindocs/views.py:327 +#: contrib/admindocs/views.py:342 contrib/admindocs/views.py:343 +#: contrib/admindocs/views.py:346 msgid "File path" msgstr "Put do datoteke" -#: contrib/admindocs/views.py:325 +#: contrib/admindocs/views.py:344 msgid "Floating point number" msgstr "Broj s pomičnim zarezom (floating point number)" -#: contrib/admindocs/views.py:329 contrib/comments/models.py:60 +#: contrib/admindocs/views.py:348 contrib/comments/models.py:60 msgid "IP address" msgstr "IP adresa" -#: contrib/admindocs/views.py:331 +#: contrib/admindocs/views.py:350 msgid "Boolean (Either True, False or None)" msgstr "Boolean (True, False ili None)" -#: contrib/admindocs/views.py:332 +#: contrib/admindocs/views.py:351 msgid "Relation to parent model" msgstr "Relacija na roditeljski model (parent model)" -#: contrib/admindocs/views.py:333 +#: contrib/admindocs/views.py:352 msgid "Phone number" msgstr "Telefonski broj" -#: contrib/admindocs/views.py:338 +#: contrib/admindocs/views.py:357 msgid "Text" msgstr "Tekst" -#: contrib/admindocs/views.py:339 +#: contrib/admindocs/views.py:358 msgid "Time" msgstr "Vrijeme" -#: contrib/admindocs/views.py:340 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:341 +#: contrib/admindocs/views.py:360 msgid "U.S. state (two uppercase letters)" msgstr "Država S.A.D.-a (dva velika slova)" -#: contrib/admindocs/views.py:342 +#: contrib/admindocs/views.py:361 msgid "XML text" msgstr "XML tekst" -#: contrib/admindocs/views.py:368 +#: contrib/admindocs/views.py:387 #, python-format msgid "%s does not appear to be a urlpattern object" msgstr "izgleda da %s nije urlpattern objekt" @@ -1429,22 +1426,55 @@ msgstr "korisnici" msgid "message" msgstr "poruka" -#: contrib/auth/views.py:56 +#: contrib/auth/views.py:60 msgid "Logged out" msgstr "Niste logirani" -#: 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 "Unesite ispravnu e-mail adresu." -#: contrib/comments/admin.py:11 +#: contrib/comments/admin.py:12 msgid "Content" msgstr "Sadržaj" -#: contrib/comments/admin.py:14 +#: contrib/comments/admin.py:15 msgid "Metadata" msgstr "Metadata" +#: contrib/comments/admin.py:39 +msgid "flagged" +msgstr "oznaka" + +#: contrib/comments/admin.py:40 +msgid "Flag selected comments" +msgstr "Označi ovaj komentar" + +#: contrib/comments/admin.py:43 +msgid "approved" +msgstr "odobreno" + +#: contrib/comments/admin.py:44 +msgid "Approve selected comments" +msgstr "Odobri odabrane komentare" + +#: contrib/comments/admin.py:47 +msgid "removed" +msgstr "uklonjeno" + +#: contrib/comments/admin.py:48 +msgid "Remove selected comments" +msgstr "Ukloni odabrane komentare" + +#: 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] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" + #: contrib/comments/feeds.py:13 #, python-format msgid "%(site_name)s comments" @@ -1456,7 +1486,6 @@ msgid "Latest comments on %(site_name)s" msgstr "Najnoviji komentari na %(site_name)s" #: contrib/comments/forms.py:93 -#: contrib/comments/templates/comments/moderation_queue.html:34 msgid "Name" msgstr "Ime" @@ -1465,7 +1494,6 @@ msgid "Email address" msgstr "E-mail adresa" #: contrib/comments/forms.py:96 -#: contrib/comments/templates/comments/moderation_queue.html:35 msgid "Comment" msgstr "Komentar" @@ -1594,7 +1622,6 @@ msgid "Really make this comment public?" msgstr "Učini komentar javno dostupnim?" #: contrib/comments/templates/comments/approve.html:12 -#: contrib/comments/templates/comments/moderation_queue.html:49 msgid "Approve" msgstr "Odobri" @@ -1620,7 +1647,6 @@ msgid "Really remove this comment?" msgstr "Stvarno ukloni ovaj komentar?" #: contrib/comments/templates/comments/delete.html:12 -#: contrib/comments/templates/comments/moderation_queue.html:53 msgid "Remove" msgstr "Ukloni" @@ -1654,39 +1680,6 @@ msgstr "Unos" msgid "Preview" msgstr "Prikaz" -#: contrib/comments/templates/comments/moderation_queue.html:4 -#: contrib/comments/templates/comments/moderation_queue.html:19 -msgid "Comment moderation queue" -msgstr "Komentari koji zahtjevaju moderiranje" - -#: contrib/comments/templates/comments/moderation_queue.html:26 -msgid "No comments to moderate" -msgstr "Nema komentara koji zahtjevaju moderiranje" - -#: contrib/comments/templates/comments/moderation_queue.html:36 -msgid "Email" -msgstr "Email" - -#: contrib/comments/templates/comments/moderation_queue.html:38 -msgid "Authenticated?" -msgstr "Ovjeren (authenticated)?" - -#: contrib/comments/templates/comments/moderation_queue.html:39 -msgid "IP Address" -msgstr "IP adresa" - -#: contrib/comments/templates/comments/moderation_queue.html:40 -msgid "Date posted" -msgstr "Datum unosa" - -#: contrib/comments/templates/comments/moderation_queue.html:63 -msgid "yes" -msgstr "da" - -#: contrib/comments/templates/comments/moderation_queue.html:63 -msgid "no" -msgstr "ne" - #: contrib/comments/templates/comments/posted.html:4 msgid "Thanks for commenting" msgstr "Hvala što ste komentirali" @@ -1783,7 +1776,7 @@ msgstr "statična stranica" msgid "flat pages" msgstr "statične stranice" -#: 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." @@ -1808,7 +1801,8 @@ msgid "" "An error occurred when transforming the geometry to the SRID of the geometry " "form field." msgstr "" -"Došlo je do greške pri transformaciji geometrije na SRID geometrijskog polja forme." +"Došlo je do greške pri transformaciji geometrije na SRID geometrijskog polja " +"forme." #: contrib/humanize/templatetags/humanize.py:19 msgid "th" @@ -2598,6 +2592,10 @@ msgstr "Neispravan checksum za broj bankovnog računa." msgid "Enter a valid Finnish social security number." msgstr "Unesite ispravan broj finskog socijalnog osiguranja." +#: contrib/localflavor/fr/forms.py:30 +msgid "Phone numbers must be in 0X XX XX XX XX format." +msgstr "Telefonski brojevi moraju biti formata 0X XX XX XX XX." + #: contrib/localflavor/in_/forms.py:14 msgid "Enter a zip code in the format XXXXXXX." msgstr "Unesi ispravan zip kod formata XXXXXXX." @@ -3926,14 +3924,14 @@ msgstr "Vrijednost mora biti None, True ili False." msgid "Enter a valid time in HH:MM[:ss[.uuuuuu]] format." msgstr "Unesite ispravno vrijeme formata HH:MM[:ss[.uuuuuu]]." -#: db/models/fields/related.py:792 +#: db/models/fields/related.py:869 msgid "" "Hold down \"Control\", or \"Command\" on a Mac, to select more than one." msgstr "" "Držite \"Control\", ili \"Command\" na Mac-u, da bi odabrali više od jednog " "objekta." -#: db/models/fields/related.py:870 +#: db/models/fields/related.py:930 #, python-format msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid." msgid_plural "" @@ -3943,86 +3941,86 @@ msgstr[0] "" msgstr[1] "" "Molim unesite ispravan %(self)s ID-eve. Vrijednosti %(value)r su neispravne." -#: forms/fields.py:54 +#: forms/fields.py:53 msgid "This field is required." msgstr "Unos za ovo polje je obavezan." -#: forms/fields.py:55 +#: forms/fields.py:54 msgid "Enter a valid value." msgstr "Unesite ispravnu vrijednost." -#: 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 "" "Osigurajte da ova vrijednost ima najviše %(max)d znakova (ima %(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 "" "Osigurajte da ova vrijednost ima najmanje %(min)d znakova (ima %(length)d)." -#: forms/fields.py:166 +#: forms/fields.py:165 msgid "Enter a whole number." msgstr "Unesite cijeli broj." -#: 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 "Osigurajte da je ova vrijednost manja ili jednaka %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 "Osigurajte da je ova vrijednost veća ili jednaka %s." -#: forms/fields.py:195 forms/fields.py:224 +#: forms/fields.py:194 forms/fields.py:223 msgid "Enter a number." msgstr "Unesite broj." -#: forms/fields.py:227 +#: forms/fields.py:226 #, python-format msgid "Ensure that there are no more than %s digits in total." msgstr "Osigurajte da ukupno nema više od %s numeričkih znakova." -#: forms/fields.py:228 +#: forms/fields.py:227 #, python-format msgid "Ensure that there are no more than %s decimal places." msgstr "Osigurajte da ukupno nema više od %s decimalnih mjesta." -#: 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 "" "Osigurajte da ukupno nema više od %s numeričkih znakova prije decimalne " "točke." -#: forms/fields.py:288 forms/fields.py:863 +#: forms/fields.py:287 forms/fields.py:862 msgid "Enter a valid date." msgstr "Unesite ispravan datum." -#: forms/fields.py:322 forms/fields.py:864 +#: forms/fields.py:321 forms/fields.py:863 msgid "Enter a valid time." msgstr "Unesite ispravno vrijeme." -#: forms/fields.py:361 +#: forms/fields.py:360 msgid "Enter a valid date/time." msgstr "Unesite ispravan datum/vrijeme." -#: forms/fields.py:447 +#: forms/fields.py:446 msgid "No file was submitted. Check the encoding type on the form." msgstr "Datoteka nije poslana. Provjerite 'encoding type' forme." -#: forms/fields.py:448 +#: forms/fields.py:447 msgid "No file was submitted." msgstr "Datoteka nije poslana." -#: forms/fields.py:449 +#: forms/fields.py:448 msgid "The submitted file is empty." msgstr "Poslana datoteka je prazna." -#: forms/fields.py:450 +#: forms/fields.py:449 #, python-format msgid "" "Ensure this filename has at most %(max)d characters (it has %(length)d)." @@ -4030,7 +4028,7 @@ msgstr "" "Osigurajte da ova datoteka ima najviše %(max)d znakova (trenutno ima %" "(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." @@ -4038,28 +4036,28 @@ msgstr "" "Upload-ajte ispravnu sliku. Datoteka koju ste upload-ali ili nije slika ili " "je oštečena." -#: forms/fields.py:544 +#: forms/fields.py:543 msgid "Enter a valid URL." msgstr "Unesite ispravan URL." -#: forms/fields.py:545 +#: forms/fields.py:544 msgid "This URL appears to be a broken link." msgstr "Izgleda da je URL neispravan." -#: 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 "Odaberite iz ponuđenog. %(value)s nije ponuđen kao opcija." -#: forms/fields.py:704 forms/fields.py:765 forms/models.py:863 +#: forms/fields.py:703 forms/fields.py:764 forms/models.py:999 msgid "Enter a list of values." msgstr "Unesite listu vrijednosti." -#: forms/fields.py:892 +#: forms/fields.py:891 msgid "Enter a valid IPv4 address." msgstr "Unesite ispravnu IPv4 adresu." -#: forms/fields.py:902 +#: forms/fields.py:901 msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" @@ -4073,27 +4071,49 @@ msgstr "Redoslijed:" #: forms/models.py:363 #, python-format msgid "%(field_name)s must be unique for %(date_field)s %(lookup)s." -msgstr "%(field_name)s mora biti jedinstven pojam za %(date_field)s %(lookup)s." +msgstr "" +"%(field_name)s mora biti jedinstven pojam za %(date_field)s %(lookup)s." #: forms/models.py:377 forms/models.py:385 #, python-format msgid "%(model_name)s with this %(field_label)s already exists." msgstr "%(model_name)s sa navedenim %(field_label)s već postoji." -#: forms/models.py:738 +#: forms/models.py:590 +#, python-format +msgid "Please correct the duplicate data for %(field)s." +msgstr "Ispravite duplicirane podatke za %(field)s." + +#: forms/models.py:594 +#, python-format +msgid "Please correct the duplicate data for %(field)s, which must be unique." +msgstr "" + +#: 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 "" + +#: forms/models.py:608 +msgid "Please correct the duplicate values below." +msgstr "Molimo ispravite duplicirane vrijednosti ispod." + +#: forms/models.py:863 msgid "The inline foreign key did not match the parent instance primary key." msgstr "The inline foreign key did not match the parent instance primary key." -#: forms/models.py:793 +#: forms/models.py:926 msgid "Select a valid choice. That choice is not one of the available choices." msgstr "Izaberite ispravnu opciju. Ta opcija nije jedna od dostupnih opcija." -#: forms/models.py:864 +#: forms/models.py:1000 #, python-format msgid "Select a valid choice. %s is not one of the available choices." msgstr "Odaberite iz ponuđenog. %s nije ponuđen kao opcija." -#: forms/models.py:866 +#: forms/models.py:1002 #, python-format msgid "\"%s\" is not a valid value for a primary key." msgstr "\"%s\" nije ispravna vrijednost za primarni kluč." @@ -4124,27 +4144,27 @@ msgstr "%.1f MB" msgid "%.1f GB" msgstr "%.1f GB" -#: utils/dateformat.py:41 +#: utils/dateformat.py:42 msgid "p.m." msgstr "popodne" -#: utils/dateformat.py:42 +#: utils/dateformat.py:43 msgid "a.m." msgstr "ujutro" -#: utils/dateformat.py:47 +#: utils/dateformat.py:48 msgid "PM" msgstr "popodne" -#: utils/dateformat.py:48 +#: utils/dateformat.py:49 msgid "AM" msgstr "ujutro" -#: utils/dateformat.py:97 +#: utils/dateformat.py:98 msgid "midnight" msgstr "ponoć" -#: utils/dateformat.py:99 +#: utils/dateformat.py:100 msgid "noon" msgstr "podne" @@ -4384,19 +4404,19 @@ msgstr ", %(number)d %(type)s" #: utils/translation/trans_real.py:399 msgid "DATE_FORMAT" -msgstr "DATE_FORMAT" +msgstr "j. N Y." #: utils/translation/trans_real.py:401 msgid "TIME_FORMAT" -msgstr "TIME_FORMAT" +msgstr "G:i" #: utils/translation/trans_real.py:417 msgid "YEAR_MONTH_FORMAT" -msgstr "YEAR_MONTH_FORMAT" +msgstr "F Y." #: utils/translation/trans_real.py:418 msgid "MONTH_DAY_FORMAT" -msgstr "MONTH_DAY_FORMAT" +msgstr "j. F" #: views/generic/create_update.py:114 #, python-format @@ -4412,406 +4432,3 @@ msgstr "%(verbose_name)s je uspješno promijenjeno." #, python-format msgid "The %(verbose_name)s was deleted." msgstr "%(verbose_name)s je izbrisano." - -#~ msgid "Gaeilge" -#~ msgstr "Gaeilge" - -#~ msgid "Brazilian" -#~ msgstr "Brazilski" - -#~ msgid "Ordering" -#~ msgstr "Redoslijed" - -#~ msgid "DATE_WITH_TIME_FULL" -#~ msgstr "DATE_WITH_TIME_FULL" - -#~ msgid "Your new password is: %(new_password)s" -#~ msgstr "Vaša nova lozinka je: %(new_password)s" - -#~ msgid "Feel free to change this password by going to this page:" -#~ msgstr "Slobodno promijenite lozinku odlaskom na ovu stranicu:" - -#~ msgid "Added %s." -#~ msgstr "Dodano %s" - -#~ msgid "Deleted %s." -#~ msgstr "Izbrisano %s." - -#~ msgid "The two 'new password' fields didn't match." -#~ msgstr "Dva polja 'nova lozinka' nisu jednaka." - -#~ msgid "headline" -#~ msgstr "naslov" - -#~ msgid "rating #1" -#~ msgstr "ocjena #1" - -#~ msgid "rating #2" -#~ msgstr "ocjena #2" - -#~ msgid "rating #3" -#~ msgstr "ocjena #3" - -#~ msgid "rating #4" -#~ msgstr "ocjena #4" - -#~ msgid "rating #5" -#~ msgstr "ocjena #5" - -#~ msgid "rating #6" -#~ msgstr "ocjena #6" - -#~ msgid "rating #7" -#~ msgstr "ocjena #7" - -#~ msgid "rating #8" -#~ msgstr "ocjena #8" - -#~ msgid "is valid rating" -#~ msgstr "ocjena je ispravana" - -#~ msgid "Content object" -#~ msgstr "Objekt sadržaja" - -#~ msgid "person's name" -#~ msgstr "ime osobe" - -#~ msgid "ip address" -#~ msgstr "ip adresa" - -#~ msgid "approved by staff" -#~ msgstr "odobreno od strane osoblja" - -#~ msgid "free comments" -#~ msgstr "slobodni komentari" - -#~ msgid "score" -#~ msgstr "rezultat" - -#~ msgid "score date" -#~ msgstr "datum rezultata" - -#~ msgid "karma score" -#~ msgstr "karma rezultat" - -#~ msgid "karma scores" -#~ msgstr "karma rezultati" - -#~ msgid "%(score)d rating by %(user)s" -#~ msgstr "%(score)d ocjena po %(user)s" - -#~ msgid "" -#~ "This comment was flagged by %(user)s:\n" -#~ "\n" -#~ "%(text)s" -#~ msgstr "" -#~ "Ovaj komentar je označio %(user)s:\n" -#~ "\n" -#~ "%(text)s" - -#~ msgid "flag date" -#~ msgstr "označeno datuma" - -#~ msgid "user flag" -#~ msgstr "korisnička oznaka" - -#~ msgid "user flags" -#~ msgstr "korisničke oznake" - -#~ msgid "Flag by %r" -#~ msgstr "Oznaka po %r" - -#~ msgid "deletion date" -#~ msgstr "datum brisanja" - -#~ msgid "moderator deletion" -#~ msgstr "brisanje moderatora" - -#~ msgid "moderator deletions" -#~ msgstr "brisanja moderatora" - -#~ msgid "Moderator deletion by %r" -#~ msgstr "Brisanje moderatora po %r" - -#~ msgid "Forgotten your password?" -#~ msgstr "Zaboravili ste lozinku?" - -#~ msgid "Ratings" -#~ msgstr "Ocjene" - -#~ msgid "Required" -#~ msgstr "Obavezno" - -#~ msgid "Optional" -#~ msgstr "Izborno, nije obavezno" - -#~ msgid "Post a photo" -#~ msgstr "Pošalji sliku" - -#~ msgid "Your name:" -#~ msgstr "Vaše ime:" - -#~ msgid "" -#~ "This rating is required because you've entered at least one other rating." -#~ msgstr "Ova ocjena je obavezna jer ste bar jednom već ocjenjivali" - -#~ msgid "" -#~ "This comment was posted by a user who has posted fewer than %(count)s " -#~ "comment:\n" -#~ "\n" -#~ "%(text)s" -#~ msgid_plural "" -#~ "This comment was posted by a user who has posted fewer than %(count)s " -#~ "comments:\n" -#~ "\n" -#~ "%(text)s" -#~ msgstr[0] "" -#~ "Ovaj komentar je napisao korisnik koji je napisao manje od %(count)s " -#~ "komentara:\n" -#~ "\n" -#~ "%(text)s" -#~ msgstr[1] "" -#~ "Ovaj komentar je napisao korisnik koji je napisao manje od %(count)s " -#~ "komentara:\n" -#~ "\n" -#~ "%(text)s" - -#~ msgid "Only POSTs are allowed" -#~ msgstr "Samo POST dopušten." - -#~ msgid "One or more of the required fields wasn't submitted" -#~ msgstr "Jedno ili više obaveznih polja nisu poslana" - -#~ msgid "Somebody tampered with the comment form (security violation)" -#~ msgstr "Netko je mijenjao formu komentara (sigurnosni propust)" - -#~ msgid "" -#~ "The comment form had an invalid 'target' parameter -- the object ID was " -#~ "invalid" -#~ msgstr "" -#~ "Forma komentara ima nepravilni 'target' parametar -- ID objekta je bio " -#~ "nepravilan" - -#~ msgid "The comment form didn't provide either 'preview' or 'post'" -#~ msgstr "Forma komentara nije imala ni 'preview' ni 'post'" - -#~ msgid "Anonymous users cannot vote" -#~ msgstr "Anonimni korisnici ne mogu glasati" - -#~ msgid "No voting for yourself" -#~ msgstr "Ne možete glasati za sebe" - -#~ msgid "Uppercase letters are not allowed here." -#~ msgstr "Velika slova ovdje nisu dopuštena." - -#~ msgid "Lowercase letters are not allowed here." -#~ msgstr "Mala slova ovdje nisu dopuštena" - -#~ msgid "Enter valid e-mail addresses separated by commas." -#~ msgstr "Unesite ispravne e-mail adrese odvojene zarezom." - -#~ msgid "Please enter a valid IP address." -#~ msgstr "Unesite ispravnu IP adresu." - -#~ msgid "Empty values are not allowed here." -#~ msgstr "Prazne vrijednosti nisu dopuštene ovdje." - -#~ msgid "Non-numeric characters aren't allowed here." -#~ msgstr "Dozvoljeni su samo numerički znakovi." - -#~ msgid "This value can't be comprised solely of digits." -#~ msgstr "Ova vrijednost ne može sadržavati samo brojeve." - -#~ msgid "Only alphabetical characters are allowed here." -#~ msgstr "Dozvoljena su samo slova abecede." - -#~ msgid "Year must be 1900 or later." -#~ msgstr "Godina mora biti 1900 ili poslije." - -#~ msgid "The URL %s does not point to a valid image." -#~ msgstr "URL %s ne vodi na ispravnu sliku." - -#~ msgid "Phone numbers must be in XXX-XXX-XXXX format. \"%s\" is invalid." -#~ msgstr "" -#~ "Telefonski brojevi moraju biti u formatu XXX-XXX-XXXX. \"%s\" nije " -#~ "ispravan format." - -#~ msgid "The URL %s does not point to a valid QuickTime video." -#~ msgstr "URL %s ne vodi na ispravan QuickTime video." - -#~ msgid "A valid URL is required." -#~ msgstr "Ispravan URL je obavezan." - -#~ msgid "" -#~ "Valid HTML is required. Specific errors are:\n" -#~ "%s" -#~ msgstr "Ispravan HTML je obavezan. Pogreške:
%s" - -#~ msgid "Badly formed XML: %s" -#~ msgstr "Loše formatiran XML: %s" - -#~ msgid "Invalid URL: %s" -#~ msgstr "Neispravan URL: %s" - -#~ msgid "The URL %s is a broken link." -#~ msgstr "URL %s je neispravan (broken) link." - -#~ msgid "Enter a valid U.S. state abbreviation." -#~ msgstr "Unesite ispravanu kraticu za državu S.A.D.-a." - -#~ msgid "This field must match the '%s' field." -#~ msgstr "Ovo polje mora biti jednako %s polju." - -#~ msgid "Please enter something for at least one field." -#~ msgstr "Molim unesite nešto bar za jedno polje." - -#~ msgid "Please enter both fields or leave them both empty." -#~ msgstr "" -#~ "Molim unesite vrijednosti za oba polja ili ostavite oba polja prazna." - -#~ msgid "This field must be given if %(field)s is not %(value)s" -#~ msgstr "Ovo polje je obavezno ako je %(field)s različito od %(value)s" - -#~ msgid "Duplicate values are not allowed." -#~ msgstr "Duplicirane vrijednosti nisu dopuštene." - -#~ msgid "This value must be between %(lower)s and %(upper)s." -#~ msgstr "Vrijednost mora biti između %(lower)s i %(upper)s." - -#~ msgid "This value must be no more than %s." -#~ msgstr "Vrijednost ne može biti veća od %s." - -#~ msgid "This value must be a power of %s." -#~ msgstr "Ova vrijednost mora biti %s na kvadrat." - -#~ msgid "Please enter a valid decimal number." -#~ msgstr "Molim unesite ispravan decimalni broj." - -#~ msgid "Please enter a valid decimal number with at most %s total digit." -#~ msgid_plural "" -#~ "Please enter a valid decimal number with at most %s total digits." -#~ msgstr[0] "" -#~ "Molim unesite ispravan decimalni broj sa najviše %s numerička znaka." -#~ msgstr[1] "" -#~ "Molim unesite ispravan decimalni broj sa najviše %s numeričkih znakova." - -#~ msgid "" -#~ "Please enter a valid decimal number with a whole part of at most %s digit." -#~ msgid_plural "" -#~ "Please enter a valid decimal number with a whole part of at most %s " -#~ "digits." -#~ msgstr[0] "" -#~ "Molim unesite ispravan decimalni broj sa najviše %s numerička znaka u " -#~ "cijelom dijelu." -#~ msgstr[1] "" -#~ "Molim unesite ispravan decimalni broj sa najviše %s numeričkih znakova u " -#~ "cijelom dijelu." - -#~ msgid "Please enter a valid decimal number with at most %s decimal place." -#~ msgid_plural "" -#~ "Please enter a valid decimal number with at most %s decimal places." -#~ msgstr[0] "" -#~ "Molim unesite ispravan decimalni broj sa najviše %s decimalna mjesta." -#~ msgstr[1] "" -#~ "Molim unesite ispravan decimalni broj sa najviše %s decimalnih mjesta." - -#~ msgid "Please enter a valid floating point number." -#~ msgstr "" -#~ "Molim unesite ispravan broj sa pomičnim zarezom (floating point number)." - -#~ msgid "Make sure your uploaded file is at least %s bytes big." -#~ msgstr "Provjerite je li Vaša upload-ana datoteka bar %s byte-ova velika." - -#~ msgid "Make sure your uploaded file is at most %s bytes big." -#~ msgstr "" -#~ "Provjerite je li Vaša upload-ana datoteka najviše %s byte-ova velika." - -#~ msgid "The format for this field is wrong." -#~ msgstr "Format za ovo polje je pogrešno." - -#~ msgid "This field is invalid." -#~ msgstr "Ovo polje je neispravno." - -#~ msgid "Could not retrieve anything from %s." -#~ msgstr "Ništa nije izvučeno iz %s." - -#~ msgid "" -#~ "The URL %(url)s returned the invalid Content-Type header '%(contenttype)" -#~ "s'." -#~ msgstr "" -#~ "URL %(url)s je vratio neispravan Content-Type header '%(contenttype)s'." - -#~ msgid "" -#~ "Please close the unclosed %(tag)s tag from line %(line)s. (Line starts " -#~ "with \"%(start)s\".)" -#~ msgstr "" -#~ "Molim zatvorite %(tag)s na liniji %(line)s. (Linija počinje sa \"%(start)s" -#~ "\".)" - -#~ msgid "" -#~ "Some text starting on line %(line)s is not allowed in that context. (Line " -#~ "starts with \"%(start)s\".)" -#~ msgstr "" -#~ "Neki tekst koji počinje na liniji %(line)s nije dopušten u tom kontekstu. " -#~ "(Linija počinje sa \"%(start)s\".)" - -#~ msgid "" -#~ "\"%(attr)s\" on line %(line)s is an invalid attribute. (Line starts with " -#~ "\"%(start)s\".)" -#~ msgstr "" -#~ "\"%(attr)s\" na liniji %(line)s je neispravan atribut. (Linija počinje sa " -#~ "\"%(start)s\".)" - -#~ msgid "" -#~ "\"<%(tag)s>\" on line %(line)s is an invalid tag. (Line starts with \"%" -#~ "(start)s\".)" -#~ msgstr "" -#~ "\"<%(tag)s>\" na liniji %(line)s je neispravan tag. (Linija počinje sa \"%" -#~ "(start)s\".)" - -#~ msgid "" -#~ "A tag on line %(line)s is missing one or more required attributes. (Line " -#~ "starts with \"%(start)s\".)" -#~ msgstr "" -#~ "Tagu na liniji %(line)s nedostaje jedan ili više obaveznih atributa. " -#~ "(Linija počinje sa \"%(start)s\".)" - -#~ msgid "" -#~ "The \"%(attr)s\" attribute on line %(line)s has an invalid value. (Line " -#~ "starts with \"%(start)s\".)" -#~ msgstr "" -#~ "\"%(attr)s\" atribut na liniji %(line)s ima neispravnu vrijednost. " -#~ "(Linija počinje sa \"%(start)s\".)" - -#~ msgid "" -#~ "%(object)s with this %(type)s already exists for the given %(field)s." -#~ msgstr "%(object)s sa %(type)s već postoji za navedeno %(field)s." - -#~ msgid "Enter a valid filename." -#~ msgstr "Unesite ime datoteke koja postoji." - -#~ msgid "Please enter a valid %s." -#~ msgstr "Molim unesite ispravan %s." - -#~ msgid "Separate multiple IDs with commas." -#~ msgstr "Odvojite više ID-a zarezom." - -#~ msgid "Ensure your text is less than %s character." -#~ msgid_plural "Ensure your text is less than %s characters." -#~ msgstr[0] "Osigurajte da tekst sadrži manje od %s znaka." -#~ msgstr[1] "Osigurajte da tekst sadrži manje od %s znakova." - -#~ msgid "Line breaks are not allowed here." -#~ msgstr "Novi redovi ovdje nisu dozvoljeni." - -#~ msgid "Select a valid choice; '%(data)s' is not in %(choices)s." -#~ msgstr "Odaberite iz ponuđenog; '%(data)s' nije u %(choices)s." - -#~ msgid "Enter a whole number between -32,768 and 32,767." -#~ msgstr "Unesite cijeli broj između -32,768 i 32,767." - -#~ msgid "Enter a positive number." -#~ msgstr "Unesite pozitivan broj." - -#~ msgid "Enter a whole number between 0 and 32,767." -#~ msgstr "Unesite cijeli broj između 0 i 32,767." diff --git a/django/contrib/admin/sites.py b/django/contrib/admin/sites.py index 52ef57370d..68621e333e 100644 --- a/django/contrib/admin/sites.py +++ b/django/contrib/admin/sites.py @@ -452,7 +452,7 @@ class AdminSite(object): import warnings warnings.warn( "AdminSite.root() is deprecated; use include(admin.site.urls) instead.", - PendingDeprecationWarning + DeprecationWarning ) # diff --git a/django/contrib/admin/validation.py b/django/contrib/admin/validation.py index 05e5c6d300..726da650a6 100644 --- a/django/contrib/admin/validation.py +++ b/django/contrib/admin/validation.py @@ -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): diff --git a/django/contrib/gis/db/backend/mysql/query.py b/django/contrib/gis/db/backend/mysql/query.py index 2fa984f325..3dfa743dd8 100644 --- a/django/contrib/gis/db/backend/mysql/query.py +++ b/django/contrib/gis/db/backend/mysql/query.py @@ -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. diff --git a/django/contrib/gis/db/backend/oracle/query.py b/django/contrib/gis/db/backend/oracle/query.py index dcf6f67ae2..ab53bedacd 100644 --- a/django/contrib/gis/db/backend/oracle/query.py +++ b/django/contrib/gis/db/backend/oracle/query.py @@ -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': diff --git a/django/contrib/gis/db/backend/postgis/__init__.py b/django/contrib/gis/db/backend/postgis/__init__.py index 7833376d1e..323fef3f95 100644 --- a/django/contrib/gis/db/backend/postgis/__init__.py +++ b/django/contrib/gis/db/backend/postgis/__init__.py @@ -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, diff --git a/django/contrib/gis/db/backend/postgis/adaptor.py b/django/contrib/gis/db/backend/postgis/adaptor.py index 7deada45b7..d8d4dfd4ea 100644 --- a/django/contrib/gis/db/backend/postgis/adaptor.py +++ b/django/contrib/gis/db/backend/postgis/adaptor.py @@ -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 diff --git a/django/contrib/gis/db/backend/postgis/query.py b/django/contrib/gis/db/backend/postgis/query.py index 7491676057..3279f24b18 100644 --- a/django/contrib/gis/db/backend/postgis/query.py +++ b/django/contrib/gis/db/backend/postgis/query.py @@ -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') diff --git a/django/contrib/gis/db/models/aggregates.py b/django/contrib/gis/db/models/aggregates.py index 7c8ab694c4..fc359393b3 100644 --- a/django/contrib/gis/db/models/aggregates.py +++ b/django/contrib/gis/db/models/aggregates.py @@ -24,6 +24,9 @@ class Collect(GeoAggregate): class Extent(GeoAggregate): name = 'Extent' +class Extent3D(GeoAggregate): + name = 'Extent3D' + class MakeLine(GeoAggregate): name = 'MakeLine' diff --git a/django/contrib/gis/db/models/manager.py b/django/contrib/gis/db/models/manager.py index eac66f4a83..d3d7f6be97 100644 --- a/django/contrib/gis/db/models/manager.py +++ b/django/contrib/gis/db/models/manager.py @@ -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) diff --git a/django/contrib/gis/db/models/query.py b/django/contrib/gis/db/models/query.py index ad2cd8ceda..d4bc206d9b 100644 --- a/django/contrib/gis/db/models/query.py +++ b/django/contrib/gis/db/models/query.py @@ -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), diff --git a/django/contrib/gis/db/models/sql/aggregates.py b/django/contrib/gis/db/models/sql/aggregates.py index b534288891..7e91869ca3 100644 --- a/django/contrib/gis/db/models/sql/aggregates.py +++ b/django/contrib/gis/db/models/sql/aggregates.py @@ -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 diff --git a/django/contrib/gis/db/models/sql/query.py b/django/contrib/gis/db/models/sql/query.py index 094fc5815f..1691637c1e 100644 --- a/django/contrib/gis/db/models/sql/query.py +++ b/django/contrib/gis/db/models/sql/query.py @@ -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: diff --git a/django/contrib/gis/gdal/geometries.py b/django/contrib/gis/gdal/geometries.py index 91d6a6493d..b301cc1c27 100644 --- a/django/contrib/gis/gdal/geometries.py +++ b/django/contrib/gis/gdal/geometries.py @@ -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, } diff --git a/django/contrib/gis/gdal/geomtype.py b/django/contrib/gis/gdal/geomtype.py index b3309531c0..3bf94d4815 100644 --- a/django/contrib/gis/gdal/geomtype.py +++ b/django/contrib/gis/gdal/geomtype.py @@ -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': diff --git a/django/contrib/gis/gdal/layer.py b/django/contrib/gis/gdal/layer.py index cf5e57866e..a2163bc3c8 100644 --- a/django/contrib/gis/gdal/layer.py +++ b/django/contrib/gis/gdal/layer.py @@ -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): """ diff --git a/django/contrib/gis/gdal/prototypes/ds.py b/django/contrib/gis/gdal/prototypes/ds.py index b64183eeb3..44828ee5f9 100644 --- a/django/contrib/gis/gdal/prototypes/ds.py +++ b/django/contrib/gis/gdal/prototypes/ds.py @@ -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]) diff --git a/django/contrib/gis/gdal/tests/test_ds.py b/django/contrib/gis/gdal/tests/test_ds.py index 30ce462475..1abea785ca 100644 --- a/django/contrib/gis/gdal/tests/test_ds.py +++ b/django/contrib/gis/gdal/tests/test_ds.py @@ -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)) diff --git a/django/contrib/gis/gdal/tests/test_geom.py b/django/contrib/gis/gdal/tests/test_geom.py index b5d8046c0e..02305f97b4 100644 --- a/django/contrib/gis/gdal/tests/test_geom.py +++ b/django/contrib/gis/gdal/tests/test_geom.py @@ -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)) diff --git a/django/contrib/gis/geos/geometry.py b/django/contrib/gis/geos/geometry.py index 866a852d49..68c116657c 100644 --- a/django/contrib/gis/geos/geometry.py +++ b/django/contrib/gis/geos/geometry.py @@ -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: diff --git a/django/contrib/gis/geos/io.py b/django/contrib/gis/geos/io.py index e5314c7911..2f895fbc2d 100644 --- a/django/contrib/gis/geos/io.py +++ b/django/contrib/gis/geos/io.py @@ -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 diff --git a/django/contrib/gis/geos/prototypes/geom.py b/django/contrib/gis/geos/prototypes/geom.py index a177f0df9f..e3f2417cd2 100644 --- a/django/contrib/gis/geos/prototypes/geom.py +++ b/django/contrib/gis/geos/prototypes/geom.py @@ -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) diff --git a/django/contrib/gis/geos/tests/test_geos.py b/django/contrib/gis/geos/tests/test_geos.py index 070ccf6d5f..440075dd49 100644 --- a/django/contrib/gis/geos/tests/test_geos.py +++ b/django/contrib/gis/geos/tests/test_geos.py @@ -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: diff --git a/django/contrib/gis/tests/__init__.py b/django/contrib/gis/tests/__init__.py index 75b8fc9d0d..5b172a3cef 100644 --- a/django/contrib/gis/tests/__init__.py +++ b/django/contrib/gis/tests/__init__.py @@ -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') diff --git a/django/contrib/gis/tests/data/test_vrt/test_vrt.vrt b/django/contrib/gis/tests/data/test_vrt/test_vrt.vrt index 85e6be8e27..979c179bb0 100644 --- a/django/contrib/gis/tests/data/test_vrt/test_vrt.vrt +++ b/django/contrib/gis/tests/data/test_vrt/test_vrt.vrt @@ -1,7 +1,7 @@ test_vrt.csv -wkbPoint +wkbPoint25D \ No newline at end of file diff --git a/django/contrib/gis/tests/geo3d/__init__.py b/django/contrib/gis/tests/geo3d/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/django/contrib/gis/tests/geo3d/models.py b/django/contrib/gis/tests/geo3d/models.py new file mode 100644 index 0000000000..3c4f77ee05 --- /dev/null +++ b/django/contrib/gis/tests/geo3d/models.py @@ -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() diff --git a/django/contrib/gis/tests/geo3d/tests.py b/django/contrib/gis/tests/geo3d/tests.py new file mode 100644 index 0000000000..034a979a4c --- /dev/null +++ b/django/contrib/gis/tests/geo3d/tests.py @@ -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'^-95.363\d+,29.763\d+,18$') + 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 diff --git a/django/contrib/gis/tests/geo3d/views.py b/django/contrib/gis/tests/geo3d/views.py new file mode 100644 index 0000000000..60f00ef0ef --- /dev/null +++ b/django/contrib/gis/tests/geo3d/views.py @@ -0,0 +1 @@ +# Create your views here. diff --git a/django/contrib/gis/tests/geoapp/test_regress.py b/django/contrib/gis/tests/geoapp/test_regress.py index e2500308bb..7efa4e6206 100644 --- a/django/contrib/gis/tests/geoapp/test_regress.py +++ b/django/contrib/gis/tests/geoapp/test_regress.py @@ -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) diff --git a/django/contrib/gis/tests/geometries.py b/django/contrib/gis/tests/geometries.py index 950ffdb0e5..701741316d 100644 --- a/django/contrib/gis/tests/geometries.py +++ b/django/contrib/gis/tests/geometries.py @@ -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' diff --git a/django/contrib/gis/utils/__init__.py b/django/contrib/gis/utils/__init__.py index 2c9f2f3ce6..f336bcadbf 100644 --- a/django/contrib/gis/utils/__init__.py +++ b/django/contrib/gis/utils/__init__.py @@ -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 diff --git a/django/contrib/gis/utils/layermapping.py b/django/contrib/gis/utils/layermapping.py index 57c957811d..e2c66740eb 100644 --- a/django/contrib/gis/utils/layermapping.py +++ b/django/contrib/gis/utils/layermapping.py @@ -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] diff --git a/django/core/handlers/base.py b/django/core/handlers/base.py index e6ef6e2f9e..f144ce4bb1 100644 --- a/django/core/handlers/base.py +++ b/django/core/handlers/base.py @@ -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('

Permission denied

') - 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('

Permission denied

') + 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): """ diff --git a/django/core/mail/__init__.py b/django/core/mail/__init__.py index b02575793d..9a629035cf 100644 --- a/django/core/mail/__init__.py +++ b/django/core/mail/__init__.py @@ -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) diff --git a/django/core/management/__init__.py b/django/core/management/__init__.py index 60dcf727e4..7af1a81d0a 100644 --- a/django/core/management/__init__.py +++ b/django/core/management/__init__.py @@ -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 diff --git a/django/core/management/commands/syncdb.py b/django/core/management/commands/syncdb.py index 165006efd1..989a946151 100644 --- a/django/core/management/commands/syncdb.py +++ b/django/core/management/commands/syncdb.py @@ -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) diff --git a/django/core/urlresolvers.py b/django/core/urlresolvers.py index eedb8f126c..a924afeaf8 100644 --- a/django/core/urlresolvers.py +++ b/django/core/urlresolvers.py @@ -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 diff --git a/django/db/models/base.py b/django/db/models/base.py index f8a8f8ae23..a348cf6de7 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -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 diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index 8c82ddb42c..cb3fb0ef5c 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -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) }) diff --git a/django/db/models/sql/subqueries.py b/django/db/models/sql/subqueries.py index 0cd393756d..f00f1bd68a 100644 --- a/django/db/models/sql/subqueries.py +++ b/django/db/models/sql/subqueries.py @@ -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] diff --git a/docs/index.txt b/docs/index.txt index d03f90c117..0ba727280c 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -201,7 +201,5 @@ The Django open-source project * **Django over time:** :ref:`API stability ` | - :ref:`Archive of release notes ` | `Backwards-incompatible changes`_ | + :ref:`Release notes and upgrading instructions ` | :ref:`Deprecation Timeline ` - -.. _Backwards-incompatible changes: http://code.djangoproject.com/wiki/BackwardsIncompatibleChanges diff --git a/docs/internals/committers.txt b/docs/internals/committers.txt index 803c3140c7..d2eb80c710 100644 --- a/docs/internals/committers.txt +++ b/docs/internals/committers.txt @@ -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 ----------- diff --git a/docs/ref/contrib/csrf.txt b/docs/ref/contrib/csrf.txt index c1bdb59cd1..ea76fc3739 100644 --- a/docs/ref/contrib/csrf.txt +++ b/docs/ref/contrib/csrf.txt @@ -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 ``
`` element if the form is for an internal URL, e.g.:: {% 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 diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index f657db20f4..80e368286e 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -78,11 +78,9 @@ Examples of output:: Displaying debug output ----------------------- -.. django-admin-option:: --verbosity - -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 `. +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 +.. 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 +-------------------------------------------- -.. django-admin:: dumpdata +.. 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 - 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 - 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 ------------------------------ +.. 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 ` 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 --------------------------- +.. 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 ` 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 ------------------------- +.. django-admin:: sql + Prints the CREATE TABLE SQL statements for the given app name(s). sqlall ---------------------------- +.. 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 ------------------------------ +.. django-admin:: sqlclear + Prints the DROP TABLE SQL statements for the given app name(s). sqlcustom ------------------------------- +.. 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 -------------------------------- +.. django-admin:: sqlindexes + Prints the CREATE INDEX SQL statements for the given app name(s). sqlreset ------------------------------ +.. django-admin:: sqlreset + Prints the DROP TABLE SQL, then the CREATE TABLE SQL, for the given app name(s). sqlsequencereset -------------------------------------- +.. 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 ------------------ +.. django-admin:: startapp + Creates a Django app directory structure for the given app name in the current directory. startproject -------------------------- +.. 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 +----------------------------- -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 -------------------------------- +.. 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. diff --git a/docs/ref/models/options.txt b/docs/ref/models/options.txt index 9bf73f5b85..d74f8350e8 100644 --- a/docs/ref/models/options.txt +++ b/docs/ref/models/options.txt @@ -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 -` 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 diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index 8266224c39..20591311be 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -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 `. + .. templatetag:: cycle csrf_token diff --git a/docs/releases/1.1.2.txt b/docs/releases/1.1.2.txt new file mode 100644 index 0000000000..64bbdd5dde --- /dev/null +++ b/docs/releases/1.1.2.txt @@ -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. diff --git a/docs/releases/1.1.txt b/docs/releases/1.1.txt index cd4bdc5e96..c69a4de2e4 100644 --- a/docs/releases/1.1.txt +++ b/docs/releases/1.1.txt @@ -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 `. 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 ========================== diff --git a/docs/releases/1.2.txt b/docs/releases/1.2.txt new file mode 100644 index 0000000000..122b2f4927 --- /dev/null +++ b/docs/releases/1.2.txt @@ -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 `. 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 ` 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 `. + +``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`. 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 +`. 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` 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`, to the +:ref:`console`, or to +:ref:`memory` - you can even configure all +e-mail to be :ref:`thrown away`. + diff --git a/docs/releases/index.txt b/docs/releases/index.txt index e5c4fde537..868ff5bd63 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -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 diff --git a/docs/topics/cache.txt b/docs/topics/cache.txt index 31900cd49f..b0e325b5c5 100644 --- a/docs/topics/cache.txt +++ b/docs/topics/cache.txt @@ -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 diff --git a/docs/topics/email.txt b/docs/topics/email.txt index 92e3c0263d..eee77cb4a0 100644 --- a/docs/topics/email.txt +++ b/docs/topics/email.txt @@ -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 ` for more details. @@ -215,8 +215,8 @@ message itself. The :ref:`e-mail backend ` 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 `. 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 `. +testing and development. If you have special e-mail sending requirements, you +can :ref:`write your own e-mail 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 diff --git a/docs/topics/http/urls.txt b/docs/topics/http/urls.txt index 0b2257cefe..fd45e79876 100644 --- a/docs/topics/http/urls.txt +++ b/docs/topics/http/urls.txt @@ -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 `), its value will be used in place of the ``ROOT_URLCONF`` setting. 2. Django loads that Python module and looks for the variable diff --git a/docs/topics/testing.txt b/docs/topics/testing.txt index 6648461014..a9ba66ece0 100644 --- a/docs/topics/testing.txt +++ b/docs/topics/testing.txt @@ -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` 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. diff --git a/tests/modeltests/model_package/tests.py b/tests/modeltests/model_package/tests.py index 6e8c158a68..7fd4b6f679 100644 --- a/tests/modeltests/model_package/tests.py +++ b/tests/modeltests/model_package/tests.py @@ -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 + + +>>> ad.publications.count() +1 + +"""} diff --git a/tests/regressiontests/admin_validation/models.py b/tests/regressiontests/admin_validation/models.py index 5506114841..eb53a9dd6e 100644 --- a/tests/regressiontests/admin_validation/models.py +++ b/tests/regressiontests/admin_validation/models.py @@ -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: 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) + """} diff --git a/tests/regressiontests/bash_completion/__init__.py b/tests/regressiontests/bash_completion/__init__.py new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/tests/regressiontests/bash_completion/__init__.py @@ -0,0 +1 @@ + diff --git a/tests/regressiontests/bash_completion/management/__init__.py b/tests/regressiontests/bash_completion/management/__init__.py new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/tests/regressiontests/bash_completion/management/__init__.py @@ -0,0 +1 @@ + diff --git a/tests/regressiontests/bash_completion/management/commands/__init__.py b/tests/regressiontests/bash_completion/management/commands/__init__.py new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/tests/regressiontests/bash_completion/management/commands/__init__.py @@ -0,0 +1 @@ + diff --git a/tests/regressiontests/bash_completion/management/commands/test_command.py b/tests/regressiontests/bash_completion/management/commands/test_command.py new file mode 100644 index 0000000000..5cb8820a8f --- /dev/null +++ b/tests/regressiontests/bash_completion/management/commands/test_command.py @@ -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 diff --git a/tests/regressiontests/bash_completion/models.py b/tests/regressiontests/bash_completion/models.py new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/tests/regressiontests/bash_completion/models.py @@ -0,0 +1 @@ + diff --git a/tests/regressiontests/bash_completion/tests.py b/tests/regressiontests/bash_completion/tests.py new file mode 100644 index 0000000000..24c8b1d3f3 --- /dev/null +++ b/tests/regressiontests/bash_completion/tests.py @@ -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'))) diff --git a/tests/regressiontests/defer_regress/models.py b/tests/regressiontests/defer_regress/models.py index da9822ab88..a1cd19788d 100644 --- a/tests/regressiontests/defer_regress/models.py +++ b/tests/regressiontests/defer_regress/models.py @@ -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. diff --git a/tests/regressiontests/forms/fields.py b/tests/regressiontests/forms/fields.py index b02b4612a2..eeff56761d 100644 --- a/tests/regressiontests/forms/fields.py +++ b/tests/regressiontests/forms/fields.py @@ -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 diff --git a/tests/regressiontests/forms/regressions.py b/tests/regressiontests/forms/regressions.py index 51aa41d2fb..9471932057 100644 --- a/tests/regressiontests/forms/regressions.py +++ b/tests/regressiontests/forms/regressions.py @@ -102,4 +102,34 @@ u'
  • (Hidden field data) This field is required.
>> f.as_table() u'
  • (Hidden field data) This field is required.
' +################################################### +# 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': '