From 3b895d4a9a68a841ec517b84451da496bb258b3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Honza=20Kr=C3=A1l?= Date: Mon, 7 Dec 2009 01:41:26 +0000 Subject: [PATCH] [soc2009/model-validation] Merged to trunk at r11791 git-svn-id: http://code.djangoproject.com/svn/django/branches/soc2009/model-validation@11798 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- AUTHORS | 1 + django/conf/locale/ca/LC_MESSAGES/django.mo | Bin 67049 -> 67366 bytes django/conf/locale/ca/LC_MESSAGES/django.po | 374 ++++---- django/conf/locale/es/LC_MESSAGES/django.mo | Bin 67328 -> 67661 bytes django/conf/locale/es/LC_MESSAGES/django.po | 369 ++++---- django/conf/locale/hr/LC_MESSAGES/django.mo | Bin 64929 -> 65180 bytes django/conf/locale/hr/LC_MESSAGES/django.po | 797 +++++------------- django/contrib/admin/sites.py | 2 +- django/contrib/admin/validation.py | 28 +- django/contrib/gis/db/backend/mysql/query.py | 6 +- django/contrib/gis/db/backend/oracle/query.py | 28 +- .../gis/db/backend/postgis/__init__.py | 3 + .../contrib/gis/db/backend/postgis/adaptor.py | 6 +- .../contrib/gis/db/backend/postgis/query.py | 6 +- django/contrib/gis/db/models/aggregates.py | 3 + django/contrib/gis/db/models/manager.py | 3 + django/contrib/gis/db/models/query.py | 23 +- .../contrib/gis/db/models/sql/aggregates.py | 17 +- django/contrib/gis/db/models/sql/query.py | 5 +- django/contrib/gis/gdal/geometries.py | 15 +- django/contrib/gis/gdal/geomtype.py | 11 +- django/contrib/gis/gdal/layer.py | 30 +- django/contrib/gis/gdal/prototypes/ds.py | 5 +- django/contrib/gis/gdal/tests/test_ds.py | 40 +- django/contrib/gis/gdal/tests/test_geom.py | 18 + django/contrib/gis/geos/geometry.py | 55 +- django/contrib/gis/geos/io.py | 27 +- django/contrib/gis/geos/prototypes/geom.py | 5 +- django/contrib/gis/geos/tests/test_geos.py | 43 + django/contrib/gis/tests/__init__.py | 24 +- .../gis/tests/data/test_vrt/test_vrt.vrt | 2 +- django/contrib/gis/tests/geo3d/__init__.py | 0 django/contrib/gis/tests/geo3d/models.py | 69 ++ django/contrib/gis/tests/geo3d/tests.py | 234 +++++ django/contrib/gis/tests/geo3d/views.py | 1 + .../contrib/gis/tests/geoapp/test_regress.py | 4 +- django/contrib/gis/tests/geometries.py | 7 + django/contrib/gis/utils/__init__.py | 2 +- django/contrib/gis/utils/layermapping.py | 24 +- django/core/handlers/base.py | 103 ++- django/core/mail/__init__.py | 2 +- django/core/management/__init__.py | 4 +- django/core/management/commands/syncdb.py | 2 +- django/core/urlresolvers.py | 30 + django/db/models/base.py | 21 +- django/db/models/fields/related.py | 23 +- django/db/models/sql/subqueries.py | 2 +- docs/index.txt | 4 +- docs/internals/committers.txt | 13 + docs/ref/contrib/csrf.txt | 12 +- docs/ref/django-admin.txt | 174 ++-- docs/ref/models/options.txt | 7 +- docs/ref/templates/builtins.txt | 12 + docs/releases/1.1.2.txt | 36 + docs/releases/1.1.txt | 8 +- docs/releases/1.2.txt | 157 ++++ docs/releases/index.txt | 69 +- docs/topics/cache.txt | 20 +- docs/topics/email.txt | 64 +- docs/topics/http/urls.txt | 3 +- docs/topics/testing.txt | 7 +- tests/modeltests/model_package/tests.py | 27 +- .../admin_validation/models.py | 59 ++ .../bash_completion/__init__.py | 1 + .../bash_completion/management/__init__.py | 1 + .../management/commands/__init__.py | 1 + .../management/commands/test_command.py | 14 + .../regressiontests/bash_completion/models.py | 1 + .../regressiontests/bash_completion/tests.py | 87 ++ tests/regressiontests/defer_regress/models.py | 17 + tests/regressiontests/forms/fields.py | 25 + tests/regressiontests/forms/regressions.py | 30 + .../many_to_one_regress/models.py | 6 + tests/regressiontests/queries/models.py | 12 +- .../urlpatterns_reverse/middleware.py | 7 + .../urlpatterns_reverse/tests.py | 37 + .../urlpatterns_reverse/urlconf_inner.py | 12 + .../urlpatterns_reverse/urlconf_outer.py | 9 + 78 files changed, 2154 insertions(+), 1252 deletions(-) create mode 100644 django/contrib/gis/tests/geo3d/__init__.py create mode 100644 django/contrib/gis/tests/geo3d/models.py create mode 100644 django/contrib/gis/tests/geo3d/tests.py create mode 100644 django/contrib/gis/tests/geo3d/views.py create mode 100644 docs/releases/1.1.2.txt create mode 100644 docs/releases/1.2.txt create mode 100644 tests/regressiontests/bash_completion/__init__.py create mode 100644 tests/regressiontests/bash_completion/management/__init__.py create mode 100644 tests/regressiontests/bash_completion/management/commands/__init__.py create mode 100644 tests/regressiontests/bash_completion/management/commands/test_command.py create mode 100644 tests/regressiontests/bash_completion/models.py create mode 100644 tests/regressiontests/bash_completion/tests.py create mode 100644 tests/regressiontests/urlpatterns_reverse/middleware.py create mode 100644 tests/regressiontests/urlpatterns_reverse/urlconf_inner.py create mode 100644 tests/regressiontests/urlpatterns_reverse/urlconf_outer.py 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 3039bd8fad17608ccf9430ce3013cbefa2f19766..457a8280479ed991d06bbfc57ad0422e673f63ba 100644 GIT binary patch delta 21900 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 diff --git a/django/conf/locale/ca/LC_MESSAGES/django.po b/django/conf/locale/ca/LC_MESSAGES/django.po index 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 a8bd5f985de2bd15ff9f9a2a0eb931d5cc4686e2..cfb7952caec41f43ee44d7398fbf956075431faa 100644 GIT binary patch delta 21954 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 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 7c702db7c7f9853b79d61caf4bbf3257b1d6ae79..354a04d78aad326c7150c657652f914896aec922 100644 GIT binary patch delta 22078 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 delta 21892 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\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': '