summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHonza Král <honza.kral@gmail.com>2009-12-07 01:41:26 +0000
committerHonza Král <honza.kral@gmail.com>2009-12-07 01:41:26 +0000
commit3b895d4a9a68a841ec517b84451da496bb258b3f (patch)
tree1913ed9144ff29b5cb359822c7b6a4eecd3ba0fb
parent30ea350dabad28b0e524feabce434d446e013d6f (diff)
[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
-rw-r--r--AUTHORS1
-rw-r--r--django/conf/locale/ca/LC_MESSAGES/django.mobin67049 -> 67366 bytes
-rw-r--r--django/conf/locale/ca/LC_MESSAGES/django.po374
-rw-r--r--django/conf/locale/es/LC_MESSAGES/django.mobin67328 -> 67661 bytes
-rw-r--r--django/conf/locale/es/LC_MESSAGES/django.po369
-rw-r--r--django/conf/locale/hr/LC_MESSAGES/django.mobin64929 -> 65180 bytes
-rw-r--r--django/conf/locale/hr/LC_MESSAGES/django.po797
-rw-r--r--django/contrib/admin/sites.py2
-rw-r--r--django/contrib/admin/validation.py28
-rw-r--r--django/contrib/gis/db/backend/mysql/query.py6
-rw-r--r--django/contrib/gis/db/backend/oracle/query.py28
-rw-r--r--django/contrib/gis/db/backend/postgis/__init__.py3
-rw-r--r--django/contrib/gis/db/backend/postgis/adaptor.py6
-rw-r--r--django/contrib/gis/db/backend/postgis/query.py6
-rw-r--r--django/contrib/gis/db/models/aggregates.py3
-rw-r--r--django/contrib/gis/db/models/manager.py3
-rw-r--r--django/contrib/gis/db/models/query.py23
-rw-r--r--django/contrib/gis/db/models/sql/aggregates.py17
-rw-r--r--django/contrib/gis/db/models/sql/query.py5
-rw-r--r--django/contrib/gis/gdal/geometries.py15
-rw-r--r--django/contrib/gis/gdal/geomtype.py11
-rw-r--r--django/contrib/gis/gdal/layer.py30
-rw-r--r--django/contrib/gis/gdal/prototypes/ds.py5
-rw-r--r--django/contrib/gis/gdal/tests/test_ds.py40
-rw-r--r--django/contrib/gis/gdal/tests/test_geom.py18
-rw-r--r--django/contrib/gis/geos/geometry.py55
-rw-r--r--django/contrib/gis/geos/io.py27
-rw-r--r--django/contrib/gis/geos/prototypes/geom.py5
-rw-r--r--django/contrib/gis/geos/tests/test_geos.py43
-rw-r--r--django/contrib/gis/tests/__init__.py24
-rw-r--r--django/contrib/gis/tests/data/test_vrt/test_vrt.vrt2
-rw-r--r--django/contrib/gis/tests/geo3d/__init__.py0
-rw-r--r--django/contrib/gis/tests/geo3d/models.py69
-rw-r--r--django/contrib/gis/tests/geo3d/tests.py234
-rw-r--r--django/contrib/gis/tests/geo3d/views.py1
-rw-r--r--django/contrib/gis/tests/geoapp/test_regress.py4
-rw-r--r--django/contrib/gis/tests/geometries.py7
-rw-r--r--django/contrib/gis/utils/__init__.py2
-rw-r--r--django/contrib/gis/utils/layermapping.py24
-rw-r--r--django/core/handlers/base.py103
-rw-r--r--django/core/mail/__init__.py2
-rw-r--r--django/core/management/__init__.py4
-rw-r--r--django/core/management/commands/syncdb.py2
-rw-r--r--django/core/urlresolvers.py30
-rw-r--r--django/db/models/base.py21
-rw-r--r--django/db/models/fields/related.py23
-rw-r--r--django/db/models/sql/subqueries.py2
-rw-r--r--docs/index.txt4
-rw-r--r--docs/internals/committers.txt13
-rw-r--r--docs/ref/contrib/csrf.txt12
-rw-r--r--docs/ref/django-admin.txt174
-rw-r--r--docs/ref/models/options.txt7
-rw-r--r--docs/ref/templates/builtins.txt12
-rw-r--r--docs/releases/1.1.2.txt36
-rw-r--r--docs/releases/1.1.txt8
-rw-r--r--docs/releases/1.2.txt157
-rw-r--r--docs/releases/index.txt69
-rw-r--r--docs/topics/cache.txt20
-rw-r--r--docs/topics/email.txt64
-rw-r--r--docs/topics/http/urls.txt3
-rw-r--r--docs/topics/testing.txt7
-rw-r--r--tests/modeltests/model_package/tests.py27
-rw-r--r--tests/regressiontests/admin_validation/models.py59
-rw-r--r--tests/regressiontests/bash_completion/__init__.py1
-rw-r--r--tests/regressiontests/bash_completion/management/__init__.py1
-rw-r--r--tests/regressiontests/bash_completion/management/commands/__init__.py1
-rw-r--r--tests/regressiontests/bash_completion/management/commands/test_command.py14
-rw-r--r--tests/regressiontests/bash_completion/models.py1
-rw-r--r--tests/regressiontests/bash_completion/tests.py87
-rw-r--r--tests/regressiontests/defer_regress/models.py17
-rw-r--r--tests/regressiontests/forms/fields.py25
-rw-r--r--tests/regressiontests/forms/regressions.py30
-rw-r--r--tests/regressiontests/many_to_one_regress/models.py6
-rw-r--r--tests/regressiontests/queries/models.py12
-rw-r--r--tests/regressiontests/urlpatterns_reverse/middleware.py7
-rw-r--r--tests/regressiontests/urlpatterns_reverse/tests.py37
-rw-r--r--tests/regressiontests/urlpatterns_reverse/urlconf_inner.py12
-rw-r--r--tests/regressiontests/urlpatterns_reverse/urlconf_outer.py9
78 files changed, 2154 insertions, 1252 deletions
diff --git a/AUTHORS b/AUTHORS
index 50ca0af541..aab479b947 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -17,6 +17,7 @@ The PRIMARY AUTHORS are (and/or have been):
* Justin Bronn
* Karen Tracey
* Jannis Leidel
+ * James Tauber
More information on the main contributors to Django can be found in
docs/internals/committers.txt.
diff --git a/django/conf/locale/ca/LC_MESSAGES/django.mo b/django/conf/locale/ca/LC_MESSAGES/django.mo
index 3039bd8fad..457a828047 100644
--- a/django/conf/locale/ca/LC_MESSAGES/django.mo
+++ b/django/conf/locale/ca/LC_MESSAGES/django.mo
Binary files differ
diff --git a/django/conf/locale/ca/LC_MESSAGES/django.po b/django/conf/locale/ca/LC_MESSAGES/django.po
index 57f8764938..f8cd8aa609 100644
--- a/django/conf/locale/ca/LC_MESSAGES/django.po
+++ b/django/conf/locale/ca/LC_MESSAGES/django.po
@@ -5,7 +5,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Django\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2009-07-07 15:04+0200\n"
+"POT-Creation-Date: 2009-11-30 11:19+0100\n"
"PO-Revision-Date: 2009-03-24 13:28+0100\n"
"Last-Translator: Django Catalan Group <django-cat@googlegroups.com>\n"
"Language-Team: Catalan <ca@li.org>\n"
@@ -223,7 +223,7 @@ msgstr "xinès tradicional"
msgid "Successfully deleted %(count)d %(items)s."
msgstr "Eliminat/s %(count)d %(items)s satisfactòriament."
-#: contrib/admin/actions.py:67 contrib/admin/options.py:1025
+#: contrib/admin/actions.py:67 contrib/admin/options.py:1033
msgid "Are you sure?"
msgstr "Esteu segurs?"
@@ -266,15 +266,15 @@ msgstr "Aquest mes"
msgid "This year"
msgstr "Aquest any"
-#: contrib/admin/filterspecs.py:147 forms/widgets.py:434
+#: contrib/admin/filterspecs.py:147 forms/widgets.py:435
msgid "Yes"
msgstr "Si"
-#: contrib/admin/filterspecs.py:147 forms/widgets.py:434
+#: contrib/admin/filterspecs.py:147 forms/widgets.py:435
msgid "No"
msgstr "No"
-#: contrib/admin/filterspecs.py:154 forms/widgets.py:434
+#: contrib/admin/filterspecs.py:154 forms/widgets.py:435
msgid "Unknown"
msgstr "Desconegut"
@@ -310,61 +310,61 @@ msgstr "entrada del registre"
msgid "log entries"
msgstr "entrades del registre"
-#: contrib/admin/options.py:133 contrib/admin/options.py:147
+#: contrib/admin/options.py:134 contrib/admin/options.py:148
msgid "None"
msgstr "cap"
-#: contrib/admin/options.py:519
+#: contrib/admin/options.py:521
#, python-format
msgid "Changed %s."
msgstr "Modificat %s."
-#: contrib/admin/options.py:519 contrib/admin/options.py:529
-#: contrib/comments/templates/comments/preview.html:16 forms/models.py:388
-#: forms/models.py:600
+#: contrib/admin/options.py:521 contrib/admin/options.py:531
+#: contrib/comments/templates/comments/preview.html:16 forms/models.py:384
+#: forms/models.py:596
msgid "and"
msgstr "i"
-#: contrib/admin/options.py:524
+#: contrib/admin/options.py:526
#, python-format
msgid "Added %(name)s \"%(object)s\"."
msgstr "Afegit %(name)s \"%(object)s\""
-#: contrib/admin/options.py:528
+#: contrib/admin/options.py:530
#, python-format
msgid "Changed %(list)s for %(name)s \"%(object)s\"."
msgstr "Modificat %(list)s per a %(name)s \"%(object)s\"."
-#: contrib/admin/options.py:533
+#: contrib/admin/options.py:535
#, python-format
msgid "Deleted %(name)s \"%(object)s\"."
msgstr "Eliminat %(name)s \"%(object)s\"."
-#: contrib/admin/options.py:537
+#: contrib/admin/options.py:539
msgid "No fields changed."
msgstr "Cap camp canviat."
-#: contrib/admin/options.py:598 contrib/auth/admin.py:67
+#: contrib/admin/options.py:601 contrib/auth/admin.py:67
#, python-format
msgid "The %(name)s \"%(obj)s\" was added successfully."
msgstr "El/la %(name)s \"%(obj)s\".ha estat afegit/da amb èxit."
-#: contrib/admin/options.py:602 contrib/admin/options.py:635
+#: contrib/admin/options.py:605 contrib/admin/options.py:638
#: contrib/auth/admin.py:75
msgid "You may edit it again below."
msgstr "Podeu editar-lo de nou a baix."
-#: contrib/admin/options.py:612 contrib/admin/options.py:645
+#: contrib/admin/options.py:615 contrib/admin/options.py:648
#, python-format
msgid "You may add another %s below."
msgstr "Podeu afegir un altre %s a baix."
-#: contrib/admin/options.py:633
+#: contrib/admin/options.py:636
#, python-format
msgid "The %(name)s \"%(obj)s\" was changed successfully."
msgstr "S'ha modificat amb èxit el/la %(name)s \"%(obj)s."
-#: contrib/admin/options.py:641
+#: contrib/admin/options.py:644
#, python-format
msgid ""
"The %(name)s \"%(obj)s\" was added successfully. You may edit it again below."
@@ -372,43 +372,43 @@ msgstr ""
"S'ha afegit exitosament el/la %(name)s \"%(obj)s\". Pot editar-lo de nou "
"abaix."
-#: contrib/admin/options.py:772
+#: contrib/admin/options.py:777
#, python-format
msgid "Add %s"
msgstr "Afegir %s"
-#: contrib/admin/options.py:803 contrib/admin/options.py:1003
+#: contrib/admin/options.py:809 contrib/admin/options.py:1011
#, python-format
msgid "%(name)s object with primary key %(key)r does not exist."
msgstr "No existèix cap objecte %(name)s amb la clau primària %(key)r."
-#: contrib/admin/options.py:860
+#: contrib/admin/options.py:866
#, python-format
msgid "Change %s"
msgstr "Modificar %s"
-#: contrib/admin/options.py:904
+#: contrib/admin/options.py:910
msgid "Database error"
msgstr "Error de base de dades"
-#: contrib/admin/options.py:940
+#: contrib/admin/options.py:946
#, python-format
msgid "%(count)s %(name)s was changed successfully."
msgid_plural "%(count)s %(name)s were changed successfully."
msgstr[0] "%(count)s %(name)s s'ha modificat amb èxit."
msgstr[1] "%(count)s %(name)s s'han modificat amb èxit."
-#: contrib/admin/options.py:1018
+#: contrib/admin/options.py:1026
#, python-format
msgid "The %(name)s \"%(obj)s\" was deleted successfully."
msgstr "El/la %(name)s \"%(obj)s\" ha estat eliminat amb èxit."
-#: contrib/admin/options.py:1054
+#: contrib/admin/options.py:1063
#, python-format
msgid "Change history: %s"
msgstr "Modificar històric: %s"
-#: contrib/admin/sites.py:20 contrib/admin/views/decorators.py:14
+#: contrib/admin/sites.py:22 contrib/admin/views/decorators.py:14
#: contrib/auth/forms.py:80
msgid ""
"Please enter a correct username and password. Note that both fields are case-"
@@ -417,11 +417,11 @@ msgstr ""
"Si us plau, introduïu un nom d'usuari i contrasenya vàlids. Tingueu en "
"compte que tots dos camps son sensibles a majúscules i minúscules."
-#: contrib/admin/sites.py:278 contrib/admin/views/decorators.py:40
+#: contrib/admin/sites.py:292 contrib/admin/views/decorators.py:40
msgid "Please log in again, because your session has expired."
msgstr "Si us plau, identifiqueu-vos de nou doncs la vostra sessió ha expirat."
-#: contrib/admin/sites.py:285 contrib/admin/views/decorators.py:47
+#: contrib/admin/sites.py:299 contrib/admin/views/decorators.py:47
msgid ""
"Looks like your browser isn't configured to accept cookies. Please enable "
"cookies, reload this page, and try again."
@@ -430,29 +430,29 @@ msgstr ""
"'cookies' (galetes). Si us plau, habiliteu les 'cookies', recarregueu "
"aquesta pàgina i proveu-ho de nou. "
-#: contrib/admin/sites.py:301 contrib/admin/sites.py:307
+#: contrib/admin/sites.py:315 contrib/admin/sites.py:321
#: contrib/admin/views/decorators.py:66
msgid "Usernames cannot contain the '@' character."
msgstr "Els noms d'usuari no poden contenir el caracter '@'."
-#: contrib/admin/sites.py:304 contrib/admin/views/decorators.py:62
+#: contrib/admin/sites.py:318 contrib/admin/views/decorators.py:62
#, python-format
msgid "Your e-mail address is not your username. Try '%s' instead."
msgstr ""
"La vostra adreça de correu no és el vostre nom d'usuari. Provi '%s' en tot "
"cas."
-#: contrib/admin/sites.py:360
+#: contrib/admin/sites.py:374
msgid "Site administration"
msgstr "Lloc administratiu"
-#: contrib/admin/sites.py:373 contrib/admin/templates/admin/login.html:26
+#: contrib/admin/sites.py:388 contrib/admin/templates/admin/login.html:26
#: contrib/admin/templates/registration/password_reset_complete.html:14
#: contrib/admin/views/decorators.py:20
msgid "Log in"
msgstr "Iniciar sessió"
-#: contrib/admin/sites.py:417
+#: contrib/admin/sites.py:433
#, python-format
msgid "%s administration"
msgstr "Administració de %s"
@@ -467,27 +467,27 @@ msgstr "Un o més %(fieldname)s en %(name)s: %(obj)s"
msgid "One or more %(fieldname)s in %(name)s:"
msgstr "Un o més %(fieldname)s en %(name)s:"
-#: contrib/admin/widgets.py:71
+#: contrib/admin/widgets.py:72
msgid "Date:"
msgstr "Data:"
-#: contrib/admin/widgets.py:71
+#: contrib/admin/widgets.py:72
msgid "Time:"
msgstr "Hora:"
-#: contrib/admin/widgets.py:95
+#: contrib/admin/widgets.py:96
msgid "Currently:"
msgstr "Actualment:"
-#: contrib/admin/widgets.py:95
+#: contrib/admin/widgets.py:96
msgid "Change:"
msgstr "Modificar:"
-#: contrib/admin/widgets.py:124
+#: contrib/admin/widgets.py:125
msgid "Lookup"
msgstr "Cercar"
-#: contrib/admin/widgets.py:236
+#: contrib/admin/widgets.py:237
msgid "Add Another"
msgstr "Afegir un altre"
@@ -502,7 +502,7 @@ msgstr "Ho sentim, però no s'ha pogut trobar la pàgina sol·licitada"
#: contrib/admin/templates/admin/500.html:4
#: contrib/admin/templates/admin/app_index.html:8
-#: contrib/admin/templates/admin/base.html:31
+#: contrib/admin/templates/admin/base.html:54
#: contrib/admin/templates/admin/change_form.html:17
#: contrib/admin/templates/admin/change_list.html:25
#: contrib/admin/templates/admin/delete_confirmation.html:6
@@ -555,18 +555,18 @@ msgstr "Anar"
msgid "%(name)s"
msgstr "%(name)s"
-#: contrib/admin/templates/admin/base.html:26
+#: contrib/admin/templates/admin/base.html:27
msgid "Welcome,"
msgstr "Benvingut/da,"
-#: contrib/admin/templates/admin/base.html:26
+#: contrib/admin/templates/admin/base.html:32
#: contrib/admin/templates/registration/password_change_done.html:3
#: contrib/admin/templates/registration/password_change_form.html:3
#: contrib/admindocs/templates/admin_doc/bookmarklets.html:3
msgid "Documentation"
msgstr "Documentació"
-#: contrib/admin/templates/admin/base.html:26
+#: contrib/admin/templates/admin/base.html:40
#: contrib/admin/templates/admin/auth/user/change_password.html:14
#: contrib/admin/templates/admin/auth/user/change_password.html:47
#: contrib/admin/templates/registration/password_change_done.html:3
@@ -574,7 +574,7 @@ msgstr "Documentació"
msgid "Change password"
msgstr "Canviar contrasenya"
-#: contrib/admin/templates/admin/base.html:26
+#: contrib/admin/templates/admin/base.html:47
#: contrib/admin/templates/registration/password_change_done.html:3
#: contrib/admin/templates/registration/password_change_form.html:3
msgid "Log out"
@@ -600,7 +600,7 @@ msgstr "Històric"
#: contrib/admin/templates/admin/change_form.html:28
#: contrib/admin/templates/admin/edit_inline/stacked.html:13
-#: contrib/admin/templates/admin/edit_inline/tabular.html:27
+#: contrib/admin/templates/admin/edit_inline/tabular.html:28
msgid "View on site"
msgstr "Veure al lloc"
@@ -670,9 +670,9 @@ msgstr ""
#, python-format
msgid ""
"Are you sure you want to delete the selected %(object_name)s objects? All of "
-"the following objects and it's related items will be deleted:"
+"the following objects and their related items will be deleted:"
msgstr ""
-"Esteu segurs de voler esborrar els/les %(object_name)s seleccionats?Tots "
+"Esteu segurs de voler esborrar els/les %(object_name)s seleccionats? Tots "
"aquests objectes i els seus elements relacionats s'esborraran:"
#: contrib/admin/templates/admin/filter.html:2
@@ -736,7 +736,6 @@ msgid "User"
msgstr "Usuari"
#: contrib/admin/templates/admin/object_history.html:24
-#: contrib/comments/templates/comments/moderation_queue.html:33
msgid "Action"
msgstr "Acció"
@@ -985,7 +984,7 @@ msgstr "Adreça de correu electrònic:"
msgid "Reset my password"
msgstr "Restablir la meva contrasenya"
-#: contrib/admin/templatetags/admin_list.py:299
+#: contrib/admin/templatetags/admin_list.py:304
msgid "All dates"
msgstr "Totes les dates"
@@ -1007,145 +1006,144 @@ msgstr "lloc"
msgid "template"
msgstr "plantilla"
-#: contrib/admindocs/views.py:58 contrib/admindocs/views.py:60
-#: contrib/admindocs/views.py:62
+#: contrib/admindocs/views.py:61 contrib/admindocs/views.py:63
+#: contrib/admindocs/views.py:65
msgid "tag:"
msgstr "etiqueta:"
-#: contrib/admindocs/views.py:91 contrib/admindocs/views.py:93
-#: contrib/admindocs/views.py:95
+#: contrib/admindocs/views.py:94 contrib/admindocs/views.py:96
+#: contrib/admindocs/views.py:98
msgid "filter:"
msgstr "filtre:"
-#: contrib/admindocs/views.py:155 contrib/admindocs/views.py:157
-#: contrib/admindocs/views.py:159
+#: contrib/admindocs/views.py:158 contrib/admindocs/views.py:160
+#: contrib/admindocs/views.py:162
msgid "view:"
msgstr "vista:"
-#: contrib/admindocs/views.py:187
+#: contrib/admindocs/views.py:190
#, python-format
msgid "App %r not found"
msgstr "No s'ha pogut trobar l'aplicació %r"
-#: contrib/admindocs/views.py:194
+#: contrib/admindocs/views.py:197
#, python-format
msgid "Model %(model_name)r not found in app %(app_label)r"
msgstr "El model %(model_name)r no s'ha trobat en l'aplicació %(app_label)r"
-#: contrib/admindocs/views.py:206
+#: contrib/admindocs/views.py:209
#, python-format
msgid "the related `%(app_label)s.%(data_type)s` object"
msgstr "l'objecte relacionat `%(app_label)s.%(data_type)s`"
-#: contrib/admindocs/views.py:206 contrib/admindocs/views.py:225
-#: contrib/admindocs/views.py:230 contrib/admindocs/views.py:244
-#: contrib/admindocs/views.py:258 contrib/admindocs/views.py:263
+#: contrib/admindocs/views.py:209 contrib/admindocs/views.py:228
+#: contrib/admindocs/views.py:233 contrib/admindocs/views.py:247
+#: contrib/admindocs/views.py:261 contrib/admindocs/views.py:266
msgid "model:"
msgstr "model:"
-#: contrib/admindocs/views.py:221 contrib/admindocs/views.py:253
+#: contrib/admindocs/views.py:224 contrib/admindocs/views.py:256
#, python-format
msgid "related `%(app_label)s.%(object_name)s` objects"
msgstr "objectes relacionats `%(app_label)s.%(object_name)s`"
-#: contrib/admindocs/views.py:225 contrib/admindocs/views.py:258
+#: contrib/admindocs/views.py:228 contrib/admindocs/views.py:261
#, python-format
msgid "all %s"
msgstr "tots %s"
-#: contrib/admindocs/views.py:230 contrib/admindocs/views.py:263
+#: contrib/admindocs/views.py:233 contrib/admindocs/views.py:266
#, python-format
msgid "number of %s"
msgstr "nombre de %s"
-#: contrib/admindocs/views.py:268
+#: contrib/admindocs/views.py:271
#, python-format
msgid "Fields on %s objects"
msgstr "Camps en objectes %s"
-#: contrib/admindocs/views.py:331 contrib/admindocs/views.py:342
-#: contrib/admindocs/views.py:344 contrib/admindocs/views.py:350
-#: contrib/admindocs/views.py:351 contrib/admindocs/views.py:353
+#: contrib/admindocs/views.py:334 contrib/admindocs/views.py:345
+#: contrib/admindocs/views.py:347 contrib/admindocs/views.py:353
+#: contrib/admindocs/views.py:354 contrib/admindocs/views.py:356
msgid "Integer"
msgstr "Enter"
-#: contrib/admindocs/views.py:332
+#: contrib/admindocs/views.py:335
msgid "Boolean (Either True or False)"
msgstr "Booleà (Verdader o Fals)"
-#: contrib/admindocs/views.py:333 contrib/admindocs/views.py:352
+#: contrib/admindocs/views.py:336 contrib/admindocs/views.py:355
#, python-format
msgid "String (up to %(max_length)s)"
msgstr "Cadena (de fins a %(max_length)s)"
-#: contrib/admindocs/views.py:334
+#: contrib/admindocs/views.py:337
msgid "Comma-separated integers"
msgstr "Enters separats per comes"
-#: contrib/admindocs/views.py:335
+#: contrib/admindocs/views.py:338
msgid "Date (without time)"
msgstr "Data (sense hora)"
-#: contrib/admindocs/views.py:336
+#: contrib/admindocs/views.py:339
msgid "Date (with time)"
msgstr "Data (amb hora)"
-#: contrib/admindocs/views.py:337
+#: contrib/admindocs/views.py:340
msgid "Decimal number"
msgstr "Número decimal"
-#: contrib/admindocs/views.py:338
+#: contrib/admindocs/views.py:341
msgid "E-mail address"
msgstr "Adreça de correu electrònic"
-#: contrib/admindocs/views.py:339 contrib/admindocs/views.py:340
-#: contrib/admindocs/views.py:343
+#: contrib/admindocs/views.py:342 contrib/admindocs/views.py:343
+#: contrib/admindocs/views.py:346
msgid "File path"
msgstr "Ruta del fitxer"
-#: contrib/admindocs/views.py:341
+#: contrib/admindocs/views.py:344
msgid "Floating point number"
msgstr "Número amb punt de coma flotant"
-#: contrib/admindocs/views.py:345 contrib/comments/models.py:60
+#: contrib/admindocs/views.py:348 contrib/comments/models.py:60
msgid "IP address"
msgstr "Adreça IP"
-#: contrib/admindocs/views.py:347
+#: contrib/admindocs/views.py:350
msgid "Boolean (Either True, False or None)"
msgstr "Booleà (Verdader, Fals o 'None' (cap))"
-#: contrib/admindocs/views.py:348
+#: contrib/admindocs/views.py:351
msgid "Relation to parent model"
msgstr "Relació amb el model pare"
-#: contrib/admindocs/views.py:349
+#: contrib/admindocs/views.py:352
msgid "Phone number"
msgstr "Número de telèfon"
-#: contrib/admindocs/views.py:354
+#: contrib/admindocs/views.py:357
msgid "Text"
msgstr "Text"
-#: contrib/admindocs/views.py:355
+#: contrib/admindocs/views.py:358
msgid "Time"
msgstr "Hora"
-#: contrib/admindocs/views.py:356 contrib/comments/forms.py:95
-#: contrib/comments/templates/comments/moderation_queue.html:37
+#: contrib/admindocs/views.py:359 contrib/comments/forms.py:95
#: contrib/flatpages/admin.py:8 contrib/flatpages/models.py:7
msgid "URL"
msgstr "URL"
-#: contrib/admindocs/views.py:357
+#: contrib/admindocs/views.py:360
msgid "U.S. state (two uppercase letters)"
msgstr "Estat dels E.U.A. (dues lletres majúscules)"
-#: contrib/admindocs/views.py:358
+#: contrib/admindocs/views.py:361
msgid "XML text"
msgstr "Text XML"
-#: contrib/admindocs/views.py:384
+#: contrib/admindocs/views.py:387
#, python-format
msgid "%s does not appear to be a urlpattern object"
msgstr "%s no sembla ser un objecte 'urlpattern'"
@@ -1438,22 +1436,54 @@ msgstr "usuaris"
msgid "message"
msgstr "missatge"
-#: contrib/auth/views.py:56
+#: contrib/auth/views.py:60
msgid "Logged out"
msgstr "Sessió finalitzada"
-#: contrib/auth/management/commands/createsuperuser.py:23 forms/fields.py:429
+#: contrib/auth/management/commands/createsuperuser.py:23 forms/fields.py:428
msgid "Enter a valid e-mail address."
msgstr "Introduïu una adreça de correu vàlida."
-#: contrib/comments/admin.py:11
+#: contrib/comments/admin.py:12
msgid "Content"
msgstr "contingut"
-#: contrib/comments/admin.py:14
+#: contrib/comments/admin.py:15
msgid "Metadata"
msgstr "metadades"
+# Context problem... waitting for comments from django-i18n
+#: contrib/comments/admin.py:39
+msgid "flagged"
+msgstr "marcat"
+
+#: contrib/comments/admin.py:40
+msgid "Flag selected comments"
+msgstr "Marcar els comentaris seleccionats"
+
+#: contrib/comments/admin.py:43
+msgid "approved"
+msgstr "aprovat"
+
+#: contrib/comments/admin.py:44
+msgid "Approve selected comments"
+msgstr "Aprovar els comentaris seleccionats"
+
+#: contrib/comments/admin.py:47
+msgid "removed"
+msgstr "eliminat"
+
+#: contrib/comments/admin.py:48
+msgid "Remove selected comments"
+msgstr "Eliminar els comentaris seleccionats"
+
+#: contrib/comments/admin.py:60
+#, python-format
+msgid "1 comment was successfully %(action)s."
+msgid_plural "%(count)s comments were successfully %(action)s."
+msgstr[0] "1 comentari ha estat %(action)s satisfactòriament."
+msgstr[1] "%(count)s comentaris han estat %(action)s satisfactòriament."
+
#: contrib/comments/feeds.py:13
#, python-format
msgid "%(site_name)s comments"
@@ -1465,7 +1495,6 @@ msgid "Latest comments on %(site_name)s"
msgstr "Últims comentaris a %(site_name)s."
#: contrib/comments/forms.py:93
-#: contrib/comments/templates/comments/moderation_queue.html:34
msgid "Name"
msgstr "nom"
@@ -1474,7 +1503,6 @@ msgid "Email address"
msgstr "Adreça de correu electrònic"
#: contrib/comments/forms.py:96
-#: contrib/comments/templates/comments/moderation_queue.html:35
msgid "Comment"
msgstr "Comentari"
@@ -1606,7 +1634,6 @@ msgid "Really make this comment public?"
msgstr "Realment vol fer aquest comentari públic?"
#: contrib/comments/templates/comments/approve.html:12
-#: contrib/comments/templates/comments/moderation_queue.html:49
msgid "Approve"
msgstr "Aprovar"
@@ -1631,7 +1658,6 @@ msgid "Really remove this comment?"
msgstr "Realment vol eliminar aquest comentari?"
#: contrib/comments/templates/comments/delete.html:12
-#: contrib/comments/templates/comments/moderation_queue.html:53
msgid "Remove"
msgstr "Eliminar"
@@ -1666,39 +1692,6 @@ msgstr "Publicar"
msgid "Preview"
msgstr "Vista prèvia"
-#: contrib/comments/templates/comments/moderation_queue.html:4
-#: contrib/comments/templates/comments/moderation_queue.html:19
-msgid "Comment moderation queue"
-msgstr "Cua de moderació de comentaris"
-
-#: contrib/comments/templates/comments/moderation_queue.html:26
-msgid "No comments to moderate"
-msgstr "No hi ha comentaris per a moderar"
-
-#: contrib/comments/templates/comments/moderation_queue.html:36
-msgid "Email"
-msgstr "Correu electrònic"
-
-#: contrib/comments/templates/comments/moderation_queue.html:38
-msgid "Authenticated?"
-msgstr "Autentificat?"
-
-#: contrib/comments/templates/comments/moderation_queue.html:39
-msgid "IP Address"
-msgstr "Adreça IP"
-
-#: contrib/comments/templates/comments/moderation_queue.html:40
-msgid "Date posted"
-msgstr "Data d'enviament"
-
-#: contrib/comments/templates/comments/moderation_queue.html:63
-msgid "yes"
-msgstr "si"
-
-#: contrib/comments/templates/comments/moderation_queue.html:63
-msgid "no"
-msgstr "no"
-
#: contrib/comments/templates/comments/posted.html:4
msgid "Thanks for commenting"
msgstr "Gràcies per comentar"
@@ -1793,7 +1786,7 @@ msgstr "pàgina estàtica"
msgid "flat pages"
msgstr "pàgines estàtiques"
-#: contrib/formtools/wizard.py:130
+#: contrib/formtools/wizard.py:132
msgid ""
"We apologize, but your form has expired. Please continue filling out the "
"form from this page."
@@ -2615,6 +2608,10 @@ msgstr "Validació invàlida del número de compte bancari."
msgid "Enter a valid Finnish social security number."
msgstr "Introduïu un número vàlid de la seguretat social finlandesa."
+#: contrib/localflavor/fr/forms.py:30
+msgid "Phone numbers must be in 0X XX XX XX XX format."
+msgstr "Els números de telèfon han de estar en el format 0X XX XX XX XX."
+
#: contrib/localflavor/in_/forms.py:14
msgid "Enter a zip code in the format XXXXXXX."
msgstr "Introduïu un codi zip en el format XXXXXXX."
@@ -3053,7 +3050,8 @@ msgstr "Validació invàlida del número tributari (NIP)."
#: contrib/localflavor/pl/forms.py:109
msgid "National Business Register Number (REGON) consists of 9 or 14 digits."
msgstr ""
-"El número nacional de registre de negocis (REGON) consisteix en 9 o 14 dígits."
+"El número nacional de registre de negocis (REGON) consisteix en 9 o 14 "
+"dígits."
#: contrib/localflavor/pl/forms.py:110
msgid "Wrong checksum for the National Business Register Number (REGON)."
@@ -3943,14 +3941,14 @@ msgstr "Aquest valor ha de ser None (Cap), True (Veritat) o False (Fals)"
msgid "Enter a valid time in HH:MM[:ss[.uuuuuu]] format."
msgstr "Introduïu una hora vàlida en el format HH:MM[:ss[.uuuuuu]]."
-#: db/models/fields/related.py:816
+#: db/models/fields/related.py:869
msgid ""
"Hold down \"Control\", or \"Command\" on a Mac, to select more than one."
msgstr ""
"Premeu la tecla \"Control\" -o \"Command\" en un Mac- per seleccionar més "
"d'un valor."
-#: db/models/fields/related.py:894
+#: db/models/fields/related.py:930
#, python-format
msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid."
msgid_plural ""
@@ -3962,95 +3960,95 @@ msgstr[1] ""
"Si us plau, introduïu IDs de %(self)s vàlids. Els valors %(value)r són "
"invàlids."
-#: forms/fields.py:54
+#: forms/fields.py:53
msgid "This field is required."
msgstr "Aquest camp és obligatori."
-#: forms/fields.py:55
+#: forms/fields.py:54
msgid "Enter a valid value."
msgstr "Introduïu un valor vàlid."
-#: forms/fields.py:138
+#: forms/fields.py:137
#, python-format
msgid "Ensure this value has at most %(max)d characters (it has %(length)d)."
msgstr ""
"Assegureu-vos de que el valor té com a màxim %(max)d caràcters (en té %"
"(length)d)."
-#: forms/fields.py:139
+#: forms/fields.py:138
#, python-format
msgid "Ensure this value has at least %(min)d characters (it has %(length)d)."
msgstr ""
"Assegureu-vos de que el valor té com a mínim %(min)d caràcters (en té %"
"(length)d)."
-#: forms/fields.py:166
+#: forms/fields.py:165
msgid "Enter a whole number."
msgstr "Introduïu un número sencer."
-#: forms/fields.py:167 forms/fields.py:196 forms/fields.py:225
+#: forms/fields.py:166 forms/fields.py:195 forms/fields.py:224
#, python-format
msgid "Ensure this value is less than or equal to %s."
msgstr "Aquest valor ha de ser menor o igual a %s."
-#: forms/fields.py:168 forms/fields.py:197 forms/fields.py:226
+#: forms/fields.py:167 forms/fields.py:196 forms/fields.py:225
#, python-format
msgid "Ensure this value is greater than or equal to %s."
msgstr "Assegureu-vos de que aquest valor sigui superior o igual a %s."
-#: forms/fields.py:195 forms/fields.py:224
+#: forms/fields.py:194 forms/fields.py:223
msgid "Enter a number."
msgstr "Introduïu un número."
-#: forms/fields.py:227
+#: forms/fields.py:226
#, python-format
msgid "Ensure that there are no more than %s digits in total."
msgstr "Assegureu-vos de que no hi ha més de %s dígits en total."
-#: forms/fields.py:228
+#: forms/fields.py:227
#, python-format
msgid "Ensure that there are no more than %s decimal places."
msgstr "Assegureu-vos de que no hi ha més de %s decimals."
-#: forms/fields.py:229
+#: forms/fields.py:228
#, python-format
msgid "Ensure that there are no more than %s digits before the decimal point."
msgstr "Assegureu-vos de que no hi ha més de %s dígits decimals."
-#: forms/fields.py:288 forms/fields.py:863
+#: forms/fields.py:287 forms/fields.py:862
msgid "Enter a valid date."
msgstr "Introduïu una data vàlida."
-#: forms/fields.py:322 forms/fields.py:864
+#: forms/fields.py:321 forms/fields.py:863
msgid "Enter a valid time."
msgstr "Introduïu una hora vàlida."
-#: forms/fields.py:361
+#: forms/fields.py:360
msgid "Enter a valid date/time."
msgstr "Introduïu una data/hora vàlides."
-#: forms/fields.py:447
+#: forms/fields.py:446
msgid "No file was submitted. Check the encoding type on the form."
msgstr ""
"No s'ha enviat cap fitxer. Comprovi el tipus de codificació del formulari."
-#: forms/fields.py:448
+#: forms/fields.py:447
msgid "No file was submitted."
msgstr "No s'ha enviat cap fitxer."
-#: forms/fields.py:449
+#: forms/fields.py:448
msgid "The submitted file is empty."
msgstr "El fitxer enviat està buit."
-#: forms/fields.py:450
+#: forms/fields.py:449
#, python-format
msgid ""
"Ensure this filename has at most %(max)d characters (it has %(length)d)."
msgstr ""
-"Assegureu-vos de que el valor té com a màxim %(max)d caràcters "
-"(en té %(length)d)."
+"Assegureu-vos de que el valor té com a màxim %(max)d caràcters (en té %"
+"(length)d)."
-#: forms/fields.py:483
+#: forms/fields.py:482
msgid ""
"Upload a valid image. The file you uploaded was either not an image or a "
"corrupted image."
@@ -4058,28 +4056,28 @@ msgstr ""
"Envieu una imatge vàlida. El fitxer que heu enviat no era una imatge o "
"estava corrupte."
-#: forms/fields.py:544
+#: forms/fields.py:543
msgid "Enter a valid URL."
msgstr "Introduïu una URL vàlida."
-#: forms/fields.py:545
+#: forms/fields.py:544
msgid "This URL appears to be a broken link."
msgstr "Aquesta URL sembla ser un enllaç trencat."
-#: forms/fields.py:625 forms/fields.py:703
+#: forms/fields.py:624 forms/fields.py:702
#, python-format
msgid "Select a valid choice. %(value)s is not one of the available choices."
msgstr "Esculliu una opció vàlida. %(value)s no és una de les opcions vàlides."
-#: forms/fields.py:704 forms/fields.py:765 forms/models.py:1003
+#: forms/fields.py:703 forms/fields.py:764 forms/models.py:999
msgid "Enter a list of values."
msgstr "Introduïu una llista de valors."
-#: forms/fields.py:892
+#: forms/fields.py:891
msgid "Enter a valid IPv4 address."
msgstr "Introduïu una adreça IPv4 vàlida."
-#: forms/fields.py:902
+#: forms/fields.py:901
msgid ""
"Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."
msgstr ""
@@ -4090,56 +4088,58 @@ msgstr ""
msgid "Order"
msgstr "Ordre"
-#: forms/models.py:367
+#: forms/models.py:363
#, python-format
msgid "%(field_name)s must be unique for %(date_field)s %(lookup)s."
msgstr "El camp %(field_name)s ha de ser únic per a %(lookup)s %(date_field)s."
-#: forms/models.py:381 forms/models.py:389
+#: forms/models.py:377 forms/models.py:385
#, python-format
msgid "%(model_name)s with this %(field_label)s already exists."
msgstr "Ja existeix %(model_name)s amb aquest %(field_label)s."
-#: forms/models.py:594
+#: forms/models.py:590
#, python-format
msgid "Please correct the duplicate data for %(field)s."
msgstr "Si us plau, corregiu la dada duplicada per a %(field)s."
-#: forms/models.py:598
+#: forms/models.py:594
#, python-format
msgid "Please correct the duplicate data for %(field)s, which must be unique."
-msgstr "Si us plau, corregiu la dada duplicada per a %(field)s, la qual ha de ser única."
+msgstr ""
+"Si us plau, corregiu la dada duplicada per a %(field)s, la qual ha de ser "
+"única."
-#: forms/models.py:604
+#: forms/models.py:600
#, python-format
msgid ""
"Please correct the duplicate data for %(field_name)s which must be unique "
"for the %(lookup)s in %(date_field)s."
msgstr ""
-"Si us plau, corregiu la dada duplicada per a %(field_name)s, "
-"la qual ha de ser única per a la cerca %(lookup)s en %(date_field)s."
+"Si us plau, corregiu la dada duplicada per a %(field_name)s, la qual ha de "
+"ser única per a la cerca %(lookup)s en %(date_field)s."
-#: forms/models.py:612
+#: forms/models.py:608
msgid "Please correct the duplicate values below."
msgstr "Si us plau, corregiu els valors duplicats a baix."
-#: forms/models.py:867
+#: forms/models.py:863
msgid "The inline foreign key did not match the parent instance primary key."
msgstr ""
"La clau forànea en línea no coincideix amb la clau primària de la instància "
"del pare"
-#: forms/models.py:930
+#: forms/models.py:926
msgid "Select a valid choice. That choice is not one of the available choices."
msgstr ""
"Escolli una opció vàlida; Aquesta opció no és una de les opcions disponibles."
-#: forms/models.py:1004
+#: forms/models.py:1000
#, python-format
msgid "Select a valid choice. %s is not one of the available choices."
msgstr "Escolliu una opció vàlida; %s' no és una de les opcions vàlides."
-#: forms/models.py:1006
+#: forms/models.py:1002
#, python-format
msgid "\"%s\" is not a valid value for a primary key."
msgstr "\"%s\" no és un valor vàlid per a una clau primària."
@@ -4459,6 +4459,30 @@ msgstr "El/la %(verbose_name)s s'ha actualtzat amb èxit."
msgid "The %(verbose_name)s was deleted."
msgstr "El %(verbose_name)s s'ha eliminat."
+#~ msgid "Comment moderation queue"
+#~ msgstr "Cua de moderació de comentaris"
+
+#~ msgid "No comments to moderate"
+#~ msgstr "No hi ha comentaris per a moderar"
+
+#~ msgid "Email"
+#~ msgstr "Correu electrònic"
+
+#~ msgid "Authenticated?"
+#~ msgstr "Autentificat?"
+
+#~ msgid "IP Address"
+#~ msgstr "Adreça IP"
+
+#~ msgid "Date posted"
+#~ msgstr "Data d'enviament"
+
+#~ msgid "yes"
+#~ msgstr "si"
+
+#~ msgid "no"
+#~ msgstr "no"
+
#, fuzzy
#~ msgid "verbose_name"
#~ msgid_plural "verbose_name_plural"
diff --git a/django/conf/locale/es/LC_MESSAGES/django.mo b/django/conf/locale/es/LC_MESSAGES/django.mo
index a8bd5f985d..cfb7952cae 100644
--- a/django/conf/locale/es/LC_MESSAGES/django.mo
+++ b/django/conf/locale/es/LC_MESSAGES/django.mo
Binary files differ
diff --git a/django/conf/locale/es/LC_MESSAGES/django.po b/django/conf/locale/es/LC_MESSAGES/django.po
index fb5eec0c43..7a9e4eca73 100644
--- a/django/conf/locale/es/LC_MESSAGES/django.po
+++ b/django/conf/locale/es/LC_MESSAGES/django.po
@@ -5,8 +5,8 @@ msgid ""
msgstr ""
"Project-Id-Version: Django\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2009-07-07 15:15+0200\n"
-"PO-Revision-Date: 2009-07-07 15:22+0200\n"
+"POT-Creation-Date: 2009-11-30 11:27+0100\n"
+"PO-Revision-Date: 2009-11-30 11:31+0100\n"
"Last-Translator: Django Spanish Team <django-cat@googlegroups.com>Language-"
"Team: Django Spanish Team <django-cat@googlegroups.com>MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -222,7 +222,7 @@ msgstr "chino tradicional"
msgid "Successfully deleted %(count)d %(items)s."
msgstr "Eliminado/s %(count)d %(items)s satisfactoriamente."
-#: contrib/admin/actions.py:67 contrib/admin/options.py:1025
+#: contrib/admin/actions.py:67 contrib/admin/options.py:1033
msgid "Are you sure?"
msgstr "¿Está seguro?"
@@ -265,15 +265,15 @@ msgstr "Este mes"
msgid "This year"
msgstr "Este año"
-#: contrib/admin/filterspecs.py:147 forms/widgets.py:434
+#: contrib/admin/filterspecs.py:147 forms/widgets.py:435
msgid "Yes"
msgstr "Sí"
-#: contrib/admin/filterspecs.py:147 forms/widgets.py:434
+#: contrib/admin/filterspecs.py:147 forms/widgets.py:435
msgid "No"
msgstr "No"
-#: contrib/admin/filterspecs.py:154 forms/widgets.py:434
+#: contrib/admin/filterspecs.py:154 forms/widgets.py:435
msgid "Unknown"
msgstr "Desconocido"
@@ -309,104 +309,104 @@ msgstr "entrada de registro"
msgid "log entries"
msgstr "entradas de registro"
-#: contrib/admin/options.py:133 contrib/admin/options.py:147
+#: contrib/admin/options.py:134 contrib/admin/options.py:148
msgid "None"
msgstr "Ninguno"
-#: contrib/admin/options.py:519
+#: contrib/admin/options.py:521
#, python-format
msgid "Changed %s."
msgstr "Modificado/a %s."
-#: contrib/admin/options.py:519 contrib/admin/options.py:529
-#: contrib/comments/templates/comments/preview.html:16 forms/models.py:388
-#: forms/models.py:600
+#: contrib/admin/options.py:521 contrib/admin/options.py:531
+#: contrib/comments/templates/comments/preview.html:16 forms/models.py:384
+#: forms/models.py:596
msgid "and"
msgstr "y"
-#: contrib/admin/options.py:524
+#: contrib/admin/options.py:526
#, python-format
msgid "Added %(name)s \"%(object)s\"."
msgstr "Añadido/a \"%(object)s\" %(name)s."
-#: contrib/admin/options.py:528
+#: contrib/admin/options.py:530
#, python-format
msgid "Changed %(list)s for %(name)s \"%(object)s\"."
msgstr "Modificados %(list)s para \"%(object)s\" %(name)s."
-#: contrib/admin/options.py:533
+#: contrib/admin/options.py:535
#, python-format
msgid "Deleted %(name)s \"%(object)s\"."
msgstr "Eliminado/a \"%(object)s\" %(name)s."
-#: contrib/admin/options.py:537
+#: contrib/admin/options.py:539
msgid "No fields changed."
msgstr "No ha cambiado ningún campo."
-#: contrib/admin/options.py:598 contrib/auth/admin.py:67
+#: contrib/admin/options.py:601 contrib/auth/admin.py:67
#, python-format
msgid "The %(name)s \"%(obj)s\" was added successfully."
msgstr "Se añadió con éxito el %(name)s \"%(obj)s\"."
-#: contrib/admin/options.py:602 contrib/admin/options.py:635
+#: contrib/admin/options.py:605 contrib/admin/options.py:638
#: contrib/auth/admin.py:75
msgid "You may edit it again below."
msgstr "Puede editarlo de nuevo abajo."
-#: contrib/admin/options.py:612 contrib/admin/options.py:645
+#: contrib/admin/options.py:615 contrib/admin/options.py:648
#, python-format
msgid "You may add another %s below."
msgstr "Puede añadir otro %s abajo."
-#: contrib/admin/options.py:633
+#: contrib/admin/options.py:636
#, python-format
msgid "The %(name)s \"%(obj)s\" was changed successfully."
msgstr "Se modificó con éxito el %(name)s \"%(obj)s\"."
-#: contrib/admin/options.py:641
+#: contrib/admin/options.py:644
#, python-format
msgid ""
"The %(name)s \"%(obj)s\" was added successfully. You may edit it again below."
msgstr ""
"Se añadió con éxito el %(name)s \"%(obj)s. Puede editarlo de nuevo abajo."
-#: contrib/admin/options.py:772
+#: contrib/admin/options.py:777
#, python-format
msgid "Add %s"
msgstr "Añadir %s"
-#: contrib/admin/options.py:803 contrib/admin/options.py:1003
+#: contrib/admin/options.py:809 contrib/admin/options.py:1011
#, python-format
msgid "%(name)s object with primary key %(key)r does not exist."
msgstr "No existe ningún objeto %(name)s con la clave primaria %(key)r."
-#: contrib/admin/options.py:860
+#: contrib/admin/options.py:866
#, python-format
msgid "Change %s"
msgstr "Modificar %s"
-#: contrib/admin/options.py:904
+#: contrib/admin/options.py:910
msgid "Database error"
msgstr "Error en la base de datos"
-#: contrib/admin/options.py:940
+#: contrib/admin/options.py:946
#, python-format
msgid "%(count)s %(name)s was changed successfully."
msgid_plural "%(count)s %(name)s were changed successfully."
msgstr[0] "%(count)s %(name)s fué modificado con éxito."
msgstr[1] "%(count)s %(name)s fueron modificados con éxito."
-#: contrib/admin/options.py:1018
+#: contrib/admin/options.py:1026
#, python-format
msgid "The %(name)s \"%(obj)s\" was deleted successfully."
msgstr "Se eliminó con éxito el %(name)s \"%(obj)s\"."
-#: contrib/admin/options.py:1054
+#: contrib/admin/options.py:1063
#, python-format
msgid "Change history: %s"
msgstr "Histórico de modificaciones: %s"
-#: contrib/admin/sites.py:20 contrib/admin/views/decorators.py:14
+#: contrib/admin/sites.py:22 contrib/admin/views/decorators.py:14
#: contrib/auth/forms.py:80
msgid ""
"Please enter a correct username and password. Note that both fields are case-"
@@ -415,11 +415,11 @@ msgstr ""
"Por favor, introduzca un nombre de usuario y contraseña correctos. Note que "
"ambos campos son sensibles a mayúsculas/minúsculas."
-#: contrib/admin/sites.py:278 contrib/admin/views/decorators.py:40
+#: contrib/admin/sites.py:292 contrib/admin/views/decorators.py:40
msgid "Please log in again, because your session has expired."
msgstr "Por favor, inicie sesión de nuevo, ya que su sesión ha caducado."
-#: contrib/admin/sites.py:285 contrib/admin/views/decorators.py:47
+#: contrib/admin/sites.py:299 contrib/admin/views/decorators.py:47
msgid ""
"Looks like your browser isn't configured to accept cookies. Please enable "
"cookies, reload this page, and try again."
@@ -427,29 +427,29 @@ msgstr ""
"Parece que su navegador no está configurado para aceptar cookies. "
"Actívelas , recargue esta página, e inténtelo de nuevo."
-#: contrib/admin/sites.py:301 contrib/admin/sites.py:307
+#: contrib/admin/sites.py:315 contrib/admin/sites.py:321
#: contrib/admin/views/decorators.py:66
msgid "Usernames cannot contain the '@' character."
msgstr "Los nombres de usuario no pueden contener el carácter '@'."
-#: contrib/admin/sites.py:304 contrib/admin/views/decorators.py:62
+#: contrib/admin/sites.py:318 contrib/admin/views/decorators.py:62
#, python-format
msgid "Your e-mail address is not your username. Try '%s' instead."
msgstr ""
"Su dirección de correo no es su nombre de usuario. Pruebe con '%s' en su "
"lugar."
-#: contrib/admin/sites.py:360
+#: contrib/admin/sites.py:374
msgid "Site administration"
msgstr "Sitio administrativo"
-#: contrib/admin/sites.py:373 contrib/admin/templates/admin/login.html:26
+#: contrib/admin/sites.py:388 contrib/admin/templates/admin/login.html:26
#: contrib/admin/templates/registration/password_reset_complete.html:14
#: contrib/admin/views/decorators.py:20
msgid "Log in"
msgstr "Iniciar sesión"
-#: contrib/admin/sites.py:417
+#: contrib/admin/sites.py:433
#, python-format
msgid "%s administration"
msgstr "Administración de %s"
@@ -464,27 +464,27 @@ msgstr "Uno o más %(fieldname)s en %(name)s: %(obj)s"
msgid "One or more %(fieldname)s in %(name)s:"
msgstr "Uno o más %(fieldname)s en %(name)s:"
-#: contrib/admin/widgets.py:71
+#: contrib/admin/widgets.py:72
msgid "Date:"
msgstr "Fecha:"
-#: contrib/admin/widgets.py:71
+#: contrib/admin/widgets.py:72
msgid "Time:"
msgstr "Hora:"
-#: contrib/admin/widgets.py:95
+#: contrib/admin/widgets.py:96
msgid "Currently:"
msgstr "Actualmente:"
-#: contrib/admin/widgets.py:95
+#: contrib/admin/widgets.py:96
msgid "Change:"
msgstr "Modificar:"
-#: contrib/admin/widgets.py:124
+#: contrib/admin/widgets.py:125
msgid "Lookup"
msgstr "Buscar"
-#: contrib/admin/widgets.py:236
+#: contrib/admin/widgets.py:237
msgid "Add Another"
msgstr "Añadir otro"
@@ -499,7 +499,7 @@ msgstr "Lo sentimos, pero no se encuentra la página solicitada."
#: contrib/admin/templates/admin/500.html:4
#: contrib/admin/templates/admin/app_index.html:8
-#: contrib/admin/templates/admin/base.html:31
+#: contrib/admin/templates/admin/base.html:54
#: contrib/admin/templates/admin/change_form.html:17
#: contrib/admin/templates/admin/change_list.html:25
#: contrib/admin/templates/admin/delete_confirmation.html:6
@@ -553,18 +553,18 @@ msgstr "Ir"
msgid "%(name)s"
msgstr "%(name)s"
-#: contrib/admin/templates/admin/base.html:26
+#: contrib/admin/templates/admin/base.html:27
msgid "Welcome,"
msgstr "Bienvenido/a,"
-#: contrib/admin/templates/admin/base.html:26
+#: contrib/admin/templates/admin/base.html:32
#: contrib/admin/templates/registration/password_change_done.html:3
#: contrib/admin/templates/registration/password_change_form.html:3
#: contrib/admindocs/templates/admin_doc/bookmarklets.html:3
msgid "Documentation"
msgstr "Documentación"
-#: contrib/admin/templates/admin/base.html:26
+#: contrib/admin/templates/admin/base.html:40
#: contrib/admin/templates/admin/auth/user/change_password.html:14
#: contrib/admin/templates/admin/auth/user/change_password.html:47
#: contrib/admin/templates/registration/password_change_done.html:3
@@ -572,7 +572,7 @@ msgstr "Documentación"
msgid "Change password"
msgstr "Cambiar contraseña"
-#: contrib/admin/templates/admin/base.html:26
+#: contrib/admin/templates/admin/base.html:47
#: contrib/admin/templates/registration/password_change_done.html:3
#: contrib/admin/templates/registration/password_change_form.html:3
msgid "Log out"
@@ -598,7 +598,7 @@ msgstr "Histórico"
#: contrib/admin/templates/admin/change_form.html:28
#: contrib/admin/templates/admin/edit_inline/stacked.html:13
-#: contrib/admin/templates/admin/edit_inline/tabular.html:27
+#: contrib/admin/templates/admin/edit_inline/tabular.html:28
msgid "View on site"
msgstr "Ver en el sitio"
@@ -668,9 +668,9 @@ msgstr ""
#, python-format
msgid ""
"Are you sure you want to delete the selected %(object_name)s objects? All of "
-"the following objects and it's related items will be deleted:"
+"the following objects and their related items will be deleted:"
msgstr ""
-"¿Está seguro de que quiere borrar los %(object_name)s? Los siguientes "
+"¿Está seguro de que quiere eliminar los %(object_name)s seleccionados? Los siguientes "
"objetos y sus elementos relacionados serán eliminados:"
#: contrib/admin/templates/admin/filter.html:2
@@ -734,7 +734,6 @@ msgid "User"
msgstr "Usuario"
#: contrib/admin/templates/admin/object_history.html:24
-#: contrib/comments/templates/comments/moderation_queue.html:33
msgid "Action"
msgstr "Acción"
@@ -987,7 +986,7 @@ msgstr "Dirección de correo electrónico:"
msgid "Reset my password"
msgstr "Restablecer mi contraseña"
-#: contrib/admin/templatetags/admin_list.py:299
+#: contrib/admin/templatetags/admin_list.py:304
msgid "All dates"
msgstr "Todas las fechas"
@@ -1009,146 +1008,145 @@ msgstr "sitio"
msgid "template"
msgstr "plantilla"
-#: contrib/admindocs/views.py:58 contrib/admindocs/views.py:60
-#: contrib/admindocs/views.py:62
+#: contrib/admindocs/views.py:61 contrib/admindocs/views.py:63
+#: contrib/admindocs/views.py:65
msgid "tag:"
msgstr "etiqueta:"
-#: contrib/admindocs/views.py:91 contrib/admindocs/views.py:93
-#: contrib/admindocs/views.py:95
+#: contrib/admindocs/views.py:94 contrib/admindocs/views.py:96
+#: contrib/admindocs/views.py:98
msgid "filter:"
msgstr "filtro:"
-#: contrib/admindocs/views.py:155 contrib/admindocs/views.py:157
-#: contrib/admindocs/views.py:159
+#: contrib/admindocs/views.py:158 contrib/admindocs/views.py:160
+#: contrib/admindocs/views.py:162
msgid "view:"
msgstr "vista:"
-#: contrib/admindocs/views.py:187
+#: contrib/admindocs/views.py:190
#, python-format
msgid "App %r not found"
msgstr "Aplicación %r no encontrada"
-#: contrib/admindocs/views.py:194
+#: contrib/admindocs/views.py:197
#, python-format
msgid "Model %(model_name)r not found in app %(app_label)r"
msgstr ""
"El modelo %(model_name)r no se ha encontrado en la aplicación %(app_label)r"
-#: contrib/admindocs/views.py:206
+#: contrib/admindocs/views.py:209
#, python-format
msgid "the related `%(app_label)s.%(data_type)s` object"
msgstr "el objeto relacionado `%(app_label)s.%(data_type)s`"
-#: contrib/admindocs/views.py:206 contrib/admindocs/views.py:225
-#: contrib/admindocs/views.py:230 contrib/admindocs/views.py:244
-#: contrib/admindocs/views.py:258 contrib/admindocs/views.py:263
+#: contrib/admindocs/views.py:209 contrib/admindocs/views.py:228
+#: contrib/admindocs/views.py:233 contrib/admindocs/views.py:247
+#: contrib/admindocs/views.py:261 contrib/admindocs/views.py:266
msgid "model:"
msgstr "modelo:"
-#: contrib/admindocs/views.py:221 contrib/admindocs/views.py:253
+#: contrib/admindocs/views.py:224 contrib/admindocs/views.py:256
#, python-format
msgid "related `%(app_label)s.%(object_name)s` objects"
msgstr "los objetos relacionados `%(app_label)s.%(object_name)s`"
-#: contrib/admindocs/views.py:225 contrib/admindocs/views.py:258
+#: contrib/admindocs/views.py:228 contrib/admindocs/views.py:261
#, python-format
msgid "all %s"
msgstr "todo %s"
-#: contrib/admindocs/views.py:230 contrib/admindocs/views.py:263
+#: contrib/admindocs/views.py:233 contrib/admindocs/views.py:266
#, python-format
msgid "number of %s"
msgstr "número de %s"
-#: contrib/admindocs/views.py:268
+#: contrib/admindocs/views.py:271
#, python-format
msgid "Fields on %s objects"
msgstr "Campos en %s objetos"
-#: contrib/admindocs/views.py:331 contrib/admindocs/views.py:342
-#: contrib/admindocs/views.py:344 contrib/admindocs/views.py:350
-#: contrib/admindocs/views.py:351 contrib/admindocs/views.py:353
+#: contrib/admindocs/views.py:334 contrib/admindocs/views.py:345
+#: contrib/admindocs/views.py:347 contrib/admindocs/views.py:353
+#: contrib/admindocs/views.py:354 contrib/admindocs/views.py:356
msgid "Integer"
msgstr "Entero"
-#: contrib/admindocs/views.py:332
+#: contrib/admindocs/views.py:335
msgid "Boolean (Either True or False)"
msgstr "Booleano (Verdadero o Falso)"
-#: contrib/admindocs/views.py:333 contrib/admindocs/views.py:352
+#: contrib/admindocs/views.py:336 contrib/admindocs/views.py:355
#, python-format
msgid "String (up to %(max_length)s)"
msgstr "Cadena (máximo %(max_length)s)"
-#: contrib/admindocs/views.py:334
+#: contrib/admindocs/views.py:337
msgid "Comma-separated integers"
msgstr "Enteros separados por comas"
-#: contrib/admindocs/views.py:335
+#: contrib/admindocs/views.py:338
msgid "Date (without time)"
msgstr "Fecha (sin hora)"
-#: contrib/admindocs/views.py:336
+#: contrib/admindocs/views.py:339
msgid "Date (with time)"
msgstr "Fecha (con hora)"
-#: contrib/admindocs/views.py:337
+#: contrib/admindocs/views.py:340
msgid "Decimal number"
msgstr "Número decimal"
-#: contrib/admindocs/views.py:338
+#: contrib/admindocs/views.py:341
msgid "E-mail address"
msgstr "Dirección de correo electrónico"
-#: contrib/admindocs/views.py:339 contrib/admindocs/views.py:340
-#: contrib/admindocs/views.py:343
+#: contrib/admindocs/views.py:342 contrib/admindocs/views.py:343
+#: contrib/admindocs/views.py:346
msgid "File path"
msgstr "Ruta de fichero"
-#: contrib/admindocs/views.py:341
+#: contrib/admindocs/views.py:344
msgid "Floating point number"
msgstr "Número en coma flotante"
-#: contrib/admindocs/views.py:345 contrib/comments/models.py:60
+#: contrib/admindocs/views.py:348 contrib/comments/models.py:60
msgid "IP address"
msgstr "Dirección IP"
-#: contrib/admindocs/views.py:347
+#: contrib/admindocs/views.py:350
msgid "Boolean (Either True, False or None)"
msgstr "Booleano (Verdadero, Falso o Nulo)"
-#: contrib/admindocs/views.py:348
+#: contrib/admindocs/views.py:351
msgid "Relation to parent model"
msgstr "Relación con el modelo padre"
-#: contrib/admindocs/views.py:349
+#: contrib/admindocs/views.py:352
msgid "Phone number"
msgstr "Número de teléfono"
-#: contrib/admindocs/views.py:354
+#: contrib/admindocs/views.py:357
msgid "Text"
msgstr "Texto"
-#: contrib/admindocs/views.py:355
+#: contrib/admindocs/views.py:358
msgid "Time"
msgstr "Hora"
-#: contrib/admindocs/views.py:356 contrib/comments/forms.py:95
-#: contrib/comments/templates/comments/moderation_queue.html:37
+#: contrib/admindocs/views.py:359 contrib/comments/forms.py:95
#: contrib/flatpages/admin.py:8 contrib/flatpages/models.py:7
msgid "URL"
msgstr "URL"
-#: contrib/admindocs/views.py:357
+#: contrib/admindocs/views.py:360
msgid "U.S. state (two uppercase letters)"
msgstr "Estado de los EEUU (dos letras mayúsculas)"
-#: contrib/admindocs/views.py:358
+#: contrib/admindocs/views.py:361
msgid "XML text"
msgstr "Texto XML"
-#: contrib/admindocs/views.py:384
+#: contrib/admindocs/views.py:387
#, python-format
msgid "%s does not appear to be a urlpattern object"
msgstr "%s no parece ser un objeto urlpattern"
@@ -1441,22 +1439,53 @@ msgstr "usuarios"
msgid "message"
msgstr "mensaje"
-#: contrib/auth/views.py:56
+#: contrib/auth/views.py:60
msgid "Logged out"
msgstr "Sesión terminada"
-#: contrib/auth/management/commands/createsuperuser.py:23 forms/fields.py:429
+#: contrib/auth/management/commands/createsuperuser.py:23 forms/fields.py:428
msgid "Enter a valid e-mail address."
msgstr "Introduzca una dirección de correo electrónico válida."
-#: contrib/comments/admin.py:11
+#: contrib/comments/admin.py:12
msgid "Content"
msgstr "contenido"
-#: contrib/comments/admin.py:14
+#: contrib/comments/admin.py:15
msgid "Metadata"
msgstr "metadatos"
+#: contrib/comments/admin.py:39
+msgid "flagged"
+msgstr "marcado"
+
+#: contrib/comments/admin.py:40
+msgid "Flag selected comments"
+msgstr "Marcar los comentarios seleccionados"
+
+#: contrib/comments/admin.py:43
+msgid "approved"
+msgstr "aprobado"
+
+#: contrib/comments/admin.py:44
+msgid "Approve selected comments"
+msgstr "aprobar los comentarios seleccionados"
+
+#: contrib/comments/admin.py:47
+msgid "removed"
+msgstr "eliminado"
+
+#: contrib/comments/admin.py:48
+msgid "Remove selected comments"
+msgstr "Eliminar los comentarios seleccionados"
+
+#: contrib/comments/admin.py:60
+#, python-format
+msgid "1 comment was successfully %(action)s."
+msgid_plural "%(count)s comments were successfully %(action)s."
+msgstr[0] "1 comentarios ha sido %(action)s satisfactoriamente."
+msgstr[1] "%(count)s comentarios han sido %(action)s satisfactoriamente."
+
#: contrib/comments/feeds.py:13
#, python-format
msgid "%(site_name)s comments"
@@ -1468,7 +1497,6 @@ msgid "Latest comments on %(site_name)s"
msgstr "Últimos comentarios en %(site_name)s"
#: contrib/comments/forms.py:93
-#: contrib/comments/templates/comments/moderation_queue.html:34
msgid "Name"
msgstr "Nombre"
@@ -1477,7 +1505,6 @@ msgid "Email address"
msgstr "dirección de correo electrónico"
#: contrib/comments/forms.py:96
-#: contrib/comments/templates/comments/moderation_queue.html:35
msgid "Comment"
msgstr "Comentario"
@@ -1598,14 +1625,13 @@ msgstr "marcas de comentario"
#: contrib/comments/templates/comments/approve.html:4
msgid "Approve a comment"
-msgstr "Aprovar un comentario"
+msgstr "Aprobar un comentario"
#: contrib/comments/templates/comments/approve.html:7
msgid "Really make this comment public?"
msgstr "Realmente desea hacer este comentario público?"
#: contrib/comments/templates/comments/approve.html:12
-#: contrib/comments/templates/comments/moderation_queue.html:49
msgid "Approve"
msgstr "Aprobar"
@@ -1631,7 +1657,6 @@ msgid "Really remove this comment?"
msgstr "¿Realmente desea eliminar este comentario?"
#: contrib/comments/templates/comments/delete.html:12
-#: contrib/comments/templates/comments/moderation_queue.html:53
msgid "Remove"
msgstr "Eliminar"
@@ -1665,39 +1690,6 @@ msgstr "Enviar"
msgid "Preview"
msgstr "Previsualizar"
-#: contrib/comments/templates/comments/moderation_queue.html:4
-#: contrib/comments/templates/comments/moderation_queue.html:19
-msgid "Comment moderation queue"
-msgstr "Cola de moderación de comentarios"
-
-#: contrib/comments/templates/comments/moderation_queue.html:26
-msgid "No comments to moderate"
-msgstr "No hay comentarios por moderar"
-
-#: contrib/comments/templates/comments/moderation_queue.html:36
-msgid "Email"
-msgstr "Correo electrónico"
-
-#: contrib/comments/templates/comments/moderation_queue.html:38
-msgid "Authenticated?"
-msgstr "¿Autentificado?"
-
-#: contrib/comments/templates/comments/moderation_queue.html:39
-msgid "IP Address"
-msgstr "Dirección IP"
-
-#: contrib/comments/templates/comments/moderation_queue.html:40
-msgid "Date posted"
-msgstr "Fecha de envío"
-
-#: contrib/comments/templates/comments/moderation_queue.html:63
-msgid "yes"
-msgstr "sí"
-
-#: contrib/comments/templates/comments/moderation_queue.html:63
-msgid "no"
-msgstr "no"
-
#: contrib/comments/templates/comments/posted.html:4
msgid "Thanks for commenting"
msgstr "Gracias por comentar"
@@ -1790,7 +1782,7 @@ msgstr "página estática"
msgid "flat pages"
msgstr "páginas estáticas"
-#: contrib/formtools/wizard.py:130
+#: contrib/formtools/wizard.py:132
msgid ""
"We apologize, but your form has expired. Please continue filling out the "
"form from this page."
@@ -1815,8 +1807,8 @@ msgid ""
"An error occurred when transforming the geometry to the SRID of the geometry "
"form field."
msgstr ""
-"Ocurrió un error al transformar la geometria al SRID de la geometria "
-"del campo de formulario."
+"Ocurrió un error al transformar la geometria al SRID de la geometria del "
+"campo de formulario."
#: contrib/humanize/templatetags/humanize.py:19
msgid "th"
@@ -2611,6 +2603,10 @@ msgstr "El número de cuenta bancaria es incorrecto."
msgid "Enter a valid Finnish social security number."
msgstr "Introduzca un número de seguro social finlandés válido."
+#: contrib/localflavor/fr/forms.py:30
+msgid "Phone numbers must be in 0X XX XX XX XX format."
+msgstr "Los números de teléfono deben tener el formato 0X XX XX XX XX."
+
#: contrib/localflavor/in_/forms.py:14
msgid "Enter a zip code in the format XXXXXXX."
msgstr "Introduzca un código postal en el formato XXXXXXX."
@@ -3052,7 +3048,8 @@ msgstr "El Número de Identificación Tributaria (NIP) es incorrecto."
#: contrib/localflavor/pl/forms.py:109
msgid "National Business Register Number (REGON) consists of 9 or 14 digits."
msgstr ""
-"El Número Nacional de Registro de Negocios (REGON) consiste en 9 o 14 dígitos."
+"El Número Nacional de Registro de Negocios (REGON) consiste en 9 o 14 "
+"dígitos."
#: contrib/localflavor/pl/forms.py:110
msgid "Wrong checksum for the National Business Register Number (REGON)."
@@ -3941,14 +3938,14 @@ msgstr "Este valor debe ser Verdadero, Falso o Ninguno."
msgid "Enter a valid time in HH:MM[:ss[.uuuuuu]] format."
msgstr "Introduzca una hora válida en formato HH:MM[:ss[.uuuuuu]]."
-#: db/models/fields/related.py:816
+#: db/models/fields/related.py:869
msgid ""
"Hold down \"Control\", or \"Command\" on a Mac, to select more than one."
msgstr ""
"Mantenga presionado \"Control\", o \"Command\" en un Mac, para seleccionar "
"más de una opción."
-#: db/models/fields/related.py:894
+#: db/models/fields/related.py:930
#, python-format
msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid."
msgid_plural ""
@@ -3960,88 +3957,88 @@ msgstr[1] ""
"Por favor, introduzca IDs de %(self)s válidos. Los valores %(value)r no son "
"válidos."
-#: forms/fields.py:54
+#: forms/fields.py:53
msgid "This field is required."
msgstr "Este campo es obligatorio."
-#: forms/fields.py:55
+#: forms/fields.py:54
msgid "Enter a valid value."
msgstr "Introduzca un valor correcto."
-#: forms/fields.py:138
+#: forms/fields.py:137
#, python-format
msgid "Ensure this value has at most %(max)d characters (it has %(length)d)."
msgstr ""
"Asegúrese de que su texto tiene a lo más %(max)d caracteres (actualmente "
"tiene %(length)d)."
-#: forms/fields.py:139
+#: forms/fields.py:138
#, python-format
msgid "Ensure this value has at least %(min)d characters (it has %(length)d)."
msgstr ""
"Asegúrese de que su texto tiene al menos %(min)d caracteres (actualmente "
"tiene %(length)d)."
-#: forms/fields.py:166
+#: forms/fields.py:165
msgid "Enter a whole number."
msgstr "Introduzca un número entero."
-#: forms/fields.py:167 forms/fields.py:196 forms/fields.py:225
+#: forms/fields.py:166 forms/fields.py:195 forms/fields.py:224
#, python-format
msgid "Ensure this value is less than or equal to %s."
msgstr "Asegúrese de que este valor es menor o igual a %s."
-#: forms/fields.py:168 forms/fields.py:197 forms/fields.py:226
+#: forms/fields.py:167 forms/fields.py:196 forms/fields.py:225
#, python-format
msgid "Ensure this value is greater than or equal to %s."
msgstr "Asegúrese de que este valor es mayor o igual a %s."
-#: forms/fields.py:195 forms/fields.py:224
+#: forms/fields.py:194 forms/fields.py:223
msgid "Enter a number."
msgstr "Introduzca un número."
-#: forms/fields.py:227
+#: forms/fields.py:226
#, python-format
msgid "Ensure that there are no more than %s digits in total."
msgstr "Asegúrese de que no hay más de %s dígitos en total."
-#: forms/fields.py:228
+#: forms/fields.py:227
#, python-format
msgid "Ensure that there are no more than %s decimal places."
msgstr "Asegúrese de que no hay más de %s decimales."
-#: forms/fields.py:229
+#: forms/fields.py:228
#, python-format
msgid "Ensure that there are no more than %s digits before the decimal point."
msgstr "Asegúrese de que no hay más de %s dígitos antes de la coma decimal."
-#: forms/fields.py:288 forms/fields.py:863
+#: forms/fields.py:287 forms/fields.py:862
msgid "Enter a valid date."
msgstr "Introduzca una fecha válida."
-#: forms/fields.py:322 forms/fields.py:864
+#: forms/fields.py:321 forms/fields.py:863
msgid "Enter a valid time."
msgstr "Introduzca una hora válida."
-#: forms/fields.py:361
+#: forms/fields.py:360
msgid "Enter a valid date/time."
msgstr "Introduzca una fecha/hora válida."
-#: forms/fields.py:447
+#: forms/fields.py:446
msgid "No file was submitted. Check the encoding type on the form."
msgstr ""
"No se ha enviado ningún fichero. Compruebe el tipo de codificación en el "
"formulario."
-#: forms/fields.py:448
+#: forms/fields.py:447
msgid "No file was submitted."
msgstr "No se ha enviado ningún fichero"
-#: forms/fields.py:449
+#: forms/fields.py:448
msgid "The submitted file is empty."
msgstr "El fichero enviado está vacío."
-#: forms/fields.py:450
+#: forms/fields.py:449
#, python-format
msgid ""
"Ensure this filename has at most %(max)d characters (it has %(length)d)."
@@ -4049,7 +4046,7 @@ msgstr ""
"Asegúrese de que su texto tiene no más de %(max)d caracteres (actualmente "
"tiene %(length)d)."
-#: forms/fields.py:483
+#: forms/fields.py:482
msgid ""
"Upload a valid image. The file you uploaded was either not an image or a "
"corrupted image."
@@ -4057,29 +4054,29 @@ msgstr ""
"Envíe una imagen válida. El fichero que ha enviado no era una imagen o se "
"trataba de una imagen corrupta."
-#: forms/fields.py:544
+#: forms/fields.py:543
msgid "Enter a valid URL."
msgstr "Introduzca una URL válida."
-#: forms/fields.py:545
+#: forms/fields.py:544
msgid "This URL appears to be a broken link."
msgstr "La URL parece ser un enlace roto."
-#: forms/fields.py:625 forms/fields.py:703
+#: forms/fields.py:624 forms/fields.py:702
#, python-format
msgid "Select a valid choice. %(value)s is not one of the available choices."
msgstr ""
"Escoja una opción válida. %(value)s no es una de las opciones disponibles."
-#: forms/fields.py:704 forms/fields.py:765 forms/models.py:1003
+#: forms/fields.py:703 forms/fields.py:764 forms/models.py:999
msgid "Enter a list of values."
msgstr "Introduzca una lista de valores."
-#: forms/fields.py:892
+#: forms/fields.py:891
msgid "Enter a valid IPv4 address."
msgstr "Introduzca una dirección IPv4 válida."
-#: forms/fields.py:902
+#: forms/fields.py:901
msgid ""
"Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."
msgstr ""
@@ -4090,28 +4087,28 @@ msgstr ""
msgid "Order"
msgstr "Orden"
-#: forms/models.py:367
+#: forms/models.py:363
#, python-format
msgid "%(field_name)s must be unique for %(date_field)s %(lookup)s."
msgstr "El campo %(field_name)s debe ser único para %(lookup)s %(date_field)s"
-#: forms/models.py:381 forms/models.py:389
+#: forms/models.py:377 forms/models.py:385
#, python-format
msgid "%(model_name)s with this %(field_label)s already exists."
msgstr "Ya existe %(model_name)s con este %(field_label)s."
-#: forms/models.py:594
+#: forms/models.py:590
#, python-format
msgid "Please correct the duplicate data for %(field)s."
msgstr "Por favor, corrija el dato duplicado para %(field)s."
-#: forms/models.py:598
+#: forms/models.py:594
#, python-format
msgid "Please correct the duplicate data for %(field)s, which must be unique."
msgstr ""
"Por favor corriga el dato duplicado para %(field)s, el cual debe ser único."
-#: forms/models.py:604
+#: forms/models.py:600
#, python-format
msgid ""
"Please correct the duplicate data for %(field_name)s which must be unique "
@@ -4120,26 +4117,26 @@ msgstr ""
"Por favor corriga los datos duplicados para %(field_name)s el cual debe ser "
"único para %(lookup)s en %(date_field)s."
-#: forms/models.py:612
+#: forms/models.py:608
msgid "Please correct the duplicate values below."
msgstr "Por favor, corrija los valores duplicados abajo."
-#: forms/models.py:867
+#: forms/models.py:863
msgid "The inline foreign key did not match the parent instance primary key."
msgstr ""
"La clave foránea en linea no coincide con la clave primaria de la instancia "
"padre."
-#: forms/models.py:930
+#: forms/models.py:926
msgid "Select a valid choice. That choice is not one of the available choices."
msgstr "Escoja una opción válida. Esa opción no está entre las disponibles."
-#: forms/models.py:1004
+#: forms/models.py:1000
#, python-format
msgid "Select a valid choice. %s is not one of the available choices."
msgstr "Escoja una opción válida; '%s' no es una de las opciones disponibles."
-#: forms/models.py:1006
+#: forms/models.py:1002
#, python-format
msgid "\"%s\" is not a valid value for a primary key."
msgstr "\"%s\" no es un valor válido para una clave primaria."
@@ -4459,6 +4456,30 @@ msgstr "Se actualizó con éxito el %(verbose_name)s."
msgid "The %(verbose_name)s was deleted."
msgstr "El/La %(verbose_name)s ha sido borrado."
+#~ msgid "Comment moderation queue"
+#~ msgstr "Cola de moderación de comentarios"
+
+#~ msgid "No comments to moderate"
+#~ msgstr "No hay comentarios por moderar"
+
+#~ msgid "Email"
+#~ msgstr "Correo electrónico"
+
+#~ msgid "Authenticated?"
+#~ msgstr "¿Autentificado?"
+
+#~ msgid "IP Address"
+#~ msgstr "Dirección IP"
+
+#~ msgid "Date posted"
+#~ msgstr "Fecha de envío"
+
+#~ msgid "yes"
+#~ msgstr "sí"
+
+#~ msgid "no"
+#~ msgstr "no"
+
#~ msgid "verbose_name"
#~ msgid_plural "verbose_name_plural"
#~ msgstr[0] "verbose_name"
diff --git a/django/conf/locale/hr/LC_MESSAGES/django.mo b/django/conf/locale/hr/LC_MESSAGES/django.mo
index 7c702db7c7..354a04d78a 100644
--- a/django/conf/locale/hr/LC_MESSAGES/django.mo
+++ b/django/conf/locale/hr/LC_MESSAGES/django.mo
Binary files differ
diff --git a/django/conf/locale/hr/LC_MESSAGES/django.po b/django/conf/locale/hr/LC_MESSAGES/django.po
index 36e50cb36d..c4137e161e 100644
--- a/django/conf/locale/hr/LC_MESSAGES/django.po
+++ b/django/conf/locale/hr/LC_MESSAGES/django.po
@@ -1,20 +1,17 @@
-# translation of django.po to Hrvatski jezik
-# This file is distributed under the same license as the Django package.
-#
msgid ""
msgstr ""
"Project-Id-Version: Django\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2009-05-08 18:09+0200\n"
-"PO-Revision-Date: 2009-05-08 18:19+0200\n"
-"Last-Translator: Aljosa Mohorovic <aljosa.mohorovic@gmail.com>\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ć <r.dav.lc@gmail.com>\n"
"Language-Team: Hrvatski Jezik <Django-i18n <django-i18n@googlegroups.com>>\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:<br> %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,10 +411,29 @@ 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."
gtype = self.geom_type
@@ -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 @@
<OGRVRTDataSource>
<OGRVRTLayer name="test_vrt">
<SrcDataSource relativeToVRT="1">test_vrt.csv</SrcDataSource>
-<GeometryType>wkbPoint</GeometryType>
+<GeometryType>wkbPoint25D</GeometryType>
<GeometryField encoding="PointFromColumns" x="POINT_X" y="POINT_Y" z="NUM"/>
</OGRVRTLayer>
</OGRVRTDataSource> \ 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
--- /dev/null
+++ b/django/contrib/gis/tests/geo3d/__init__.py
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'^<Point><coordinates>-95.363\d+,29.763\d+,18</coordinates></Point>$')
+ self.failUnless(ref_kml_regex.match(h.kml))
+
+ def test02b_geojson(self):
+ "Test GeoQuerySet.geojson() with Z values."
+ h = City3D.objects.geojson(precision=6).get(name='Houston')
+ # GeoJSON should be 3D
+ # `SELECT ST_AsGeoJSON(point, 6) FROM geo3d_city3d WHERE name='Houston';`
+ ref_json_regex = re.compile(r'^{"type":"Point","coordinates":\[-95.363151,29.763374,18(\.0+)?\]}$')
+ self.failUnless(ref_json_regex.match(h.geojson))
+
+ def test03a_union(self):
+ "Testing the Union aggregate of 3D models."
+ # PostGIS query that returned the reference EWKT for this test:
+ # `SELECT ST_AsText(ST_Union(point)) FROM geo3d_city3d;`
+ ref_ewkt = 'SRID=4326;MULTIPOINT(-123.305196 48.462611 15,-104.609252 38.255001 1433,-97.521157 34.464642 380,-96.801611 32.782057 147,-95.363151 29.763374 18,-95.23506 38.971823 251,-87.650175 41.850385 181,174.783117 -41.315268 14)'
+ ref_union = GEOSGeometry(ref_ewkt)
+ union = City3D.objects.aggregate(Union('point'))['point__union']
+ self.failUnless(union.hasz)
+ self.assertEqual(ref_union, union)
+
+ def test03b_extent(self):
+ "Testing the Extent3D aggregate for 3D models."
+ # `SELECT ST_Extent3D(point) FROM geo3d_city3d;`
+ ref_extent3d = (-123.305196, -41.315268, 14,174.783117, 48.462611, 1433)
+ extent1 = City3D.objects.aggregate(Extent3D('point'))['point__extent3d']
+ extent2 = City3D.objects.extent3d()
+
+ def check_extent3d(extent3d, tol=6):
+ for ref_val, ext_val in zip(ref_extent3d, extent3d):
+ self.assertAlmostEqual(ref_val, ext_val, tol)
+
+ for e3d in [extent1, extent2]:
+ check_extent3d(e3d)
+
+ def test04_perimeter(self):
+ "Testing GeoQuerySet.perimeter() on 3D fields."
+ # Reference query for values below:
+ # `SELECT ST_Perimeter3D(poly), ST_Perimeter2D(poly) FROM geo3d_polygon3d;`
+ ref_perim_3d = 76859.2620451
+ ref_perim_2d = 76859.2577803
+ tol = 6
+ self.assertAlmostEqual(ref_perim_2d,
+ Polygon2D.objects.perimeter().get(name='2D BBox').perimeter.m,
+ tol)
+ self.assertAlmostEqual(ref_perim_3d,
+ Polygon3D.objects.perimeter().get(name='3D BBox').perimeter.m,
+ tol)
+
+ def test05_length(self):
+ "Testing GeoQuerySet.length() on 3D fields."
+ # ST_Length_Spheroid Z-aware, and thus does not need to use
+ # a separate function internally.
+ # `SELECT ST_Length_Spheroid(line, 'SPHEROID["GRS 1980",6378137,298.257222101]')
+ # FROM geo3d_interstate[2d|3d];`
+ tol = 3
+ ref_length_2d = 4368.1721949481
+ ref_length_3d = 4368.62547052088
+ self.assertAlmostEqual(ref_length_2d,
+ Interstate2D.objects.length().get(name='I-45').length.m,
+ tol)
+ self.assertAlmostEqual(ref_length_3d,
+ Interstate3D.objects.length().get(name='I-45').length.m,
+ tol)
+
+ # Making sure `ST_Length3D` is used on for a projected
+ # and 3D model rather than `ST_Length`.
+ # `SELECT ST_Length(line) FROM geo3d_interstateproj2d;`
+ ref_length_2d = 4367.71564892392
+ # `SELECT ST_Length3D(line) FROM geo3d_interstateproj3d;`
+ ref_length_3d = 4368.16897234101
+ self.assertAlmostEqual(ref_length_2d,
+ InterstateProj2D.objects.length().get(name='I-45').length.m,
+ tol)
+ self.assertAlmostEqual(ref_length_3d,
+ InterstateProj3D.objects.length().get(name='I-45').length.m,
+ tol)
+
+ def test06_scale(self):
+ "Testing GeoQuerySet.scale() on Z values."
+ # Mapping of City name to reference Z values.
+ zscales = (-3, 4, 23)
+ for zscale in zscales:
+ for city in City3D.objects.scale(1.0, 1.0, zscale):
+ self.assertEqual(city_dict[city.name][2] * zscale, city.scale.z)
+
+ def test07_translate(self):
+ "Testing GeoQuerySet.translate() on Z values."
+ ztranslations = (5.23, 23, -17)
+ for ztrans in ztranslations:
+ for city in City3D.objects.translate(0, 0, ztrans):
+ self.assertEqual(city_dict[city.name][2] + ztrans, city.translate.z)
+
+def suite():
+ s = unittest.TestSuite()
+ s.addTest(unittest.makeSuite(Geo3DTest))
+ return s
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('<h1>Permission denied</h1>')
- except SystemExit:
- # Allow sys.exit() to actually exit. See tickets #1023 and #4701
- raise
- except: # Handle everything else, including SuspiciousOperation, etc.
- # Get the exception info now, in case another exception is thrown later.
- exc_info = sys.exc_info()
- receivers = signals.got_request_exception.send(sender=self.__class__, request=request)
- return self.handle_uncaught_exception(request, resolver, exc_info)
+ view_name = callback.func_name # If it's a function
+ except AttributeError:
+ view_name = callback.__class__.__name__ + '.__call__' # If it's a class
+ raise ValueError, "The view %s.%s didn't return an HttpResponse object." % (callback.__module__, view_name)
+
+ return response
+ except http.Http404, e:
+ if settings.DEBUG:
+ from django.views import debug
+ return debug.technical_404_response(request, e)
+ else:
+ try:
+ callback, param_dict = resolver.resolve404()
+ return callback(request, **param_dict)
+ except:
+ try:
+ return self.handle_uncaught_exception(request, resolver, sys.exc_info())
+ finally:
+ receivers = signals.got_request_exception.send(sender=self.__class__, request=request)
+ except exceptions.PermissionDenied:
+ return http.HttpResponseForbidden('<h1>Permission denied</h1>')
+ except SystemExit:
+ # Allow sys.exit() to actually exit. See tickets #1023 and #4701
+ raise
+ except: # Handle everything else, including SuspiciousOperation, etc.
+ # Get the exception info now, in case another exception is thrown later.
+ exc_info = sys.exc_info()
+ receivers = signals.got_request_exception.send(sender=self.__class__, request=request)
+ return self.handle_uncaught_exception(request, resolver, exc_info)
+ finally:
+ # Reset URLconf for this thread on the way out for complete
+ # isolation of request.urlconf
+ urlresolvers.set_urlconf(None)
def handle_uncaught_exception(self, request, resolver, exc_info):
"""
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 <misc-api-stability>` |
- :ref:`Archive of release notes <releases-index>` | `Backwards-incompatible changes`_ |
+ :ref:`Release notes and upgrading instructions <releases-index>` |
:ref:`Deprecation Timeline <internals-deprecation>`
-
-.. _Backwards-incompatible changes: http://code.djangoproject.com/wiki/BackwardsIncompatibleChanges
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 ``<form>`` element if the form is for an internal URL, e.g.::
<form action="" method="POST">{% csrf_token %}
@@ -123,14 +123,14 @@ as ``CsrfResponseMiddleware``, and it can be used by following these steps:
``CsrfResponseMiddleware`` needs to process the response before things
like compression or setting ofETags happen to the response, so it must
- come after ``GZipMiddleware``, ``CommonMiddleware`` and
+ come after ``GZipMiddleware``, ``CommonMiddleware`` and
``ConditionalGetMiddleware`` in the list. It also must come after
``CsrfViewMiddleware``.
Use of the ``CsrfResponseMiddleware`` is not recommended because of the
performance hit it imposes, and because of a potential security problem (see
below). It can be used as an interim measure until applications have been
-updated to use the ``{% csrf_token %}`` tag. It is deprecated and will be
+updated to use the :ttag:`csrf_token` tag. It is deprecated and will be
removed in Django 1.4.
Django 1.1 and earlier provided a single ``CsrfMiddleware`` class. This is also
@@ -153,6 +153,8 @@ launch a CSRF attack on your site against that user. The
``@csrf_response_exempt`` decorator can be used to fix this, but only if the
page doesn't also contain internal forms that require the token.
+.. _ref-csrf-upgrading-notes:
+
Upgrading notes
---------------
@@ -199,7 +201,7 @@ Note that contrib apps, such as the admin, have been updated to use the
``CsrfViewMiddleware`` to your settings. However, if you have supplied
customised templates to any of the view functions of contrib apps (whether
explicitly via a keyword argument, or by overriding built-in templates), **you
-MUST update them** to include the ``csrf_token`` template tag as described
+MUST update them** to include the :ttag:`csrf_token` template tag as described
above, or they will stop working. (If you cannot update these templates for
some reason, you will be forced to use ``CsrfResponseMiddleware`` for these
views to continue working).
@@ -364,7 +366,7 @@ exactly that.
Caching
=======
-If the ``csrf_token`` template tag is used by a template (or the ``get_token``
+If the :ttag:`csrf_token` template tag is used by a template (or the ``get_token``
function is called some other way), ``CsrfViewMiddleware`` will add a cookie and
a ``Vary: Cookie`` header to the response. Similarly,
``CsrfResponseMiddleware`` will send the ``Vary: Cookie`` header if it inserted
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 <amount>
-
-Use ``--verbosity`` to specify the amount of notification and debug information
+Use :djadminopt:`--verbosity` to specify the amount of notification and debug information
that ``django-admin.py`` should print to the console. For more details, see the
-documentation for the :ref:`default options for django-admin.py <django-admin-verbosity>`.
+documentation for the :djadminopt:`--verbosity` option.
Available subcommands
=====================
@@ -90,6 +88,8 @@ Available subcommands
cleanup
-------
+.. django-admin:: cleanup
+
.. versionadded:: 1.0
Can be run as a cronjob or directly to clean out old data from the database
@@ -98,17 +98,16 @@ Can be run as a cronjob or directly to clean out old data from the database
compilemessages
---------------
+.. django-admin:: compilemessages
+
.. versionchanged:: 1.0
Before 1.0 this was the "bin/compile-messages.py" command.
Compiles .po files created with ``makemessages`` to .mo files for use with
the builtin gettext support. See :ref:`topics-i18n`.
---locale
-~~~~~~~~
-
-Use the ``--locale`` or ``-l`` option to specify the locale to process.
-If not provided all locales are processed.
+Use the :djadminopt:`--locale`` option to specify the locale to process.
+If not provided, all locales are processed.
Example usage::
@@ -117,7 +116,7 @@ Example usage::
createcachetable
----------------
-.. django-admin:: createcachetable <tablename>
+.. django-admin:: createcachetable
Creates a cache table named ``tablename`` for use with the database cache
backend. See :ref:`topics-cache` for more information.
@@ -183,10 +182,10 @@ example, the default settings don't define ``ROOT_URLCONF``, so
Note that Django's default settings live in ``django/conf/global_settings.py``,
if you're ever curious to see the full list of defaults.
-dumpdata
---------
+dumpdata <appname appname appname.Model ...>
+--------------------------------------------
-.. django-admin:: dumpdata <appname appname appname.Model ...>
+.. django-admin:: dumpdata
Outputs to standard output all data in the database associated with the named
application(s).
@@ -215,18 +214,17 @@ directives::
django-admin.py dumpdata --exclude=auth --exclude=contenttypes
-
.. django-admin-option:: --format <fmt>
- By default, ``dumpdata`` will format its output in JSON, but you can use the
- ``--format`` option to specify another format. Currently supported formats
- are listed in :ref:`serialization-formats`.
+By default, ``dumpdata`` will format its output in JSON, but you can use the
+``--format`` option to specify another format. Currently supported formats
+are listed in :ref:`serialization-formats`.
.. django-admin-option:: --indent <num>
- By default, ``dumpdata`` will output all data on a single line. This isn't
- easy for humans to read, so you can use the ``--indent`` option to
- pretty-print the output with a number of indentation spaces.
+By default, ``dumpdata`` will output all data on a single line. This isn't
+easy for humans to read, so you can use the ``--indent`` option to
+pretty-print the output with a number of indentation spaces.
.. versionadded:: 1.1
@@ -239,22 +237,21 @@ model names.
flush
-----
-.. django-admin: flush
+.. django-admin:: flush
Returns the database to the state it was in immediately after syncdb was
executed. This means that all data will be removed from the database, any
post-synchronization handlers will be re-executed, and the ``initial_data``
fixture will be re-installed.
-.. django-admin-option:: --noinput
-
- Use the ``--noinput`` option to suppress all user prompting, such as "Are
- you sure?" confirmation messages. This is useful if ``django-admin.py`` is
- being executed as an unattended, automated script.
+The :djadminopt:`--noinput` option may be provided to suppress all user
+prompts.
inspectdb
---------
+.. django-admin:: inspectdb
+
Introspects the database tables in the database pointed-to by the
``DATABASE_NAME`` setting and outputs a Django model module (a ``models.py``
file) to standard output.
@@ -296,6 +293,8 @@ only works in PostgreSQL and with certain types of MySQL tables.
loaddata <fixture fixture ...>
------------------------------
+.. django-admin:: loaddata
+
Searches for and loads the contents of the named fixture into the database.
What's a "fixture"?
@@ -382,6 +381,8 @@ installation will be aborted, and any data installed in the call to
makemessages
------------
+.. django-admin:: makemessages
+
.. versionchanged:: 1.0
Before 1.0 this was the ``bin/make-messages.py`` command.
@@ -392,8 +393,7 @@ directory. After making changes to the messages files you need to compile them
with ``compilemessages`` for use with the builtin gettext support. See the
:ref:`i18n documentation <how-to-create-language-files>` for details.
---all
-~~~~~
+.. django-admin-option:: --all
Use the ``--all`` or ``-a`` option to update the message files for all
available languages.
@@ -402,8 +402,7 @@ Example usage::
django-admin.py makemessages --all
---extension
-~~~~~~~~~~~
+.. django-admin-option:: --extension
Use the ``--extension`` or ``-e`` option to specify a list of file extensions
to examine (default: ".html").
@@ -416,17 +415,13 @@ Separate multiple extensions with commas or use -e or --extension multiple times
django-admin.py makemessages --locale=de --extension=html,txt --extension xml
---locale
-~~~~~~~~
-
-Use the ``--locale`` or ``-l`` option to specify the locale to process.
+Use the :djadminopt:`--locale` option to specify the locale to process.
Example usage::
django-admin.py makemessages --locale=br_PT
---domain
-~~~~~~~~
+.. django-admin-option:: --domain
Use the ``--domain`` or ``-d`` option to change the domain of the messages files.
Currently supported:
@@ -434,23 +429,21 @@ Currently supported:
* ``django`` for all ``*.py`` and ``*.html`` files (default)
* ``djangojs`` for ``*.js`` files
-.. _django-admin-reset:
-
reset <appname appname ...>
---------------------------
-Executes the equivalent of ``sqlreset`` for the given app name(s).
+.. django-admin:: reset
---noinput
-~~~~~~~~~
+Executes the equivalent of ``sqlreset`` for the given app name(s).
-Use the ``--noinput`` option to suppress all user prompting, such as
-"Are you sure?" confirmation messages. This is useful if ``django-admin.py``
-is being executed as an unattended, automated script.
+The :djadminopt:`--noinput` option may be provided to suppress all user
+prompts.
runfcgi [options]
-----------------
+.. django-admin:: runfcgi
+
Starts a set of FastCGI processes suitable for use with any Web server that
supports the FastCGI protocol. See the :ref:`FastCGI deployment documentation
<howto-deployment-fastcgi>` for details. Requires the Python FastCGI module from
@@ -458,10 +451,10 @@ supports the FastCGI protocol. See the :ref:`FastCGI deployment documentation
.. _flup: http://www.saddi.com/software/flup/
-runserver
----------
+runserver [port or ipaddr:port]
+-------------------------------
-.. django-admin:: runserver [port or ipaddr:port]
+.. django-admin:: runserver
Starts a lightweight development Web server on the local machine. By default,
the server runs on port 8000 on the IP address 127.0.0.1. You can pass in an
@@ -544,6 +537,8 @@ you want to configure Django to serve static media, read :ref:`howto-static-file
shell
-----
+.. django-admin:: shell
+
Starts the Python interactive interpreter.
Django will use IPython_, if it's installed. If you have IPython installed and
@@ -557,11 +552,15 @@ option, like so::
sql <appname appname ...>
-------------------------
+.. django-admin:: sql
+
Prints the CREATE TABLE SQL statements for the given app name(s).
sqlall <appname appname ...>
----------------------------
+.. django-admin:: sqlall
+
Prints the CREATE TABLE and initial-data SQL statements for the given app name(s).
Refer to the description of ``sqlcustom`` for an explanation of how to
@@ -570,11 +569,15 @@ specify initial data.
sqlclear <appname appname ...>
------------------------------
+.. django-admin:: sqlclear
+
Prints the DROP TABLE SQL statements for the given app name(s).
sqlcustom <appname appname ...>
-------------------------------
+.. django-admin:: sqlcustom
+
Prints the custom SQL statements for the given app name(s).
For each model in each specified app, this command looks for the file
@@ -594,21 +597,30 @@ Note that the order in which the SQL files are processed is undefined.
sqlflush
--------
-Prints the SQL statements that would be executed for the `flush`_ command.
+.. django-admin:: sqlflush
+
+Prints the SQL statements that would be executed for the :djadmin:`flush`
+command.
sqlindexes <appname appname ...>
--------------------------------
+.. django-admin:: sqlindexes
+
Prints the CREATE INDEX SQL statements for the given app name(s).
sqlreset <appname appname ...>
------------------------------
+.. django-admin:: sqlreset
+
Prints the DROP TABLE SQL, then the CREATE TABLE SQL, for the given app name(s).
sqlsequencereset <appname appname ...>
--------------------------------------
+.. django-admin:: sqlsequencereset
+
Prints the SQL statements for resetting sequences for the given app name(s).
Sequences are indexes used by some database engines to track the next available
@@ -620,12 +632,16 @@ of sync with its automatically incremented field data.
startapp <appname>
------------------
+.. django-admin:: startapp
+
Creates a Django app directory structure for the given app name in the current
directory.
startproject <projectname>
--------------------------
+.. django-admin:: startproject
+
Creates a Django project directory structure for the given project name in the
current directory.
@@ -635,11 +651,11 @@ This command is disabled when the ``--settings`` option to
situations, either omit the ``--settings`` option or unset
``DJANGO_SETTINGS_MODULE``.
-.. _django-admin-syncdb:
-
syncdb
------
+.. django-admin:: syncdb
+
Creates the database tables for all apps in ``INSTALLED_APPS`` whose tables
have not already been created.
@@ -669,29 +685,22 @@ with an appropriate extension (e.g. ``json`` or ``xml``). See the
documentation for ``loaddata`` for details on the specification of fixture
data files.
---noinput
-~~~~~~~~~
+The :djadminopt:`--noinput` option may be provided to suppress all user
+prompts.
-Use the ``--noinput`` option to suppress all user prompting, such as
-"Are you sure?" confirmation messages. This is useful if ``django-admin.py``
-is being executed as an unattended, automated script.
+test <app or test identifier>
+-----------------------------
-test
-----
+.. django-admin:: test
Runs tests for all installed models. See :ref:`topics-testing` for more
information.
---noinput
-~~~~~~~~~
-
-Use the ``--noinput`` option to suppress all user prompting, such as
-"Are you sure?" confirmation messages. This is useful if ``django-admin.py``
-is being executed as an unattended, automated script.
-
testserver <fixture fixture ...>
--------------------------------
+.. django-admin:: testserver
+
.. versionadded:: 1.0
Runs a Django development server (as in ``runserver``) using data from the
@@ -727,8 +736,7 @@ Note that this server does *not* automatically detect changes to your Python
source code (as ``runserver`` does). It does, however, detect changes to
templates.
---addrport [port number or ipaddr:port]
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+.. django-admin-option:: --addrport [port number or ipaddr:port]
Use ``--addrport`` to specify a different port, or IP address and port, from
the default of 127.0.0.1:8000. This value follows exactly the same format and
@@ -752,6 +760,8 @@ To run on 1.2.3.4:7000 with a ``test`` fixture::
validate
--------
+.. django-admin:: validate
+
Validates all installed models (according to the ``INSTALLED_APPS`` setting)
and prints validation errors to standard output.
@@ -761,8 +771,7 @@ Default options
Although some subcommands may allow their own custom options, every subcommand
allows for the following options:
---pythonpath
-------------
+.. django-admin-option:: --pythonpath
Example usage::
@@ -777,8 +786,7 @@ setting the Python path for you.
.. _import search path: http://diveintopython.org/getting_to_know_python/everything_is_an_object.html
---settings
-----------
+.. django-admin-option:: --settings
Example usage::
@@ -792,8 +800,7 @@ variable.
Note that this option is unnecessary in ``manage.py``, because it uses
``settings.py`` from the current project by default.
---traceback
------------
+.. django-admin-option:: --traceback
Example usage::
@@ -803,10 +810,7 @@ By default, ``django-admin.py`` will show a simple error message whenever an
error occurs. If you specify ``--traceback``, ``django-admin.py`` will
output a full stack trace whenever an exception is raised.
-.. _django-admin-verbosity:
-
---verbosity
------------
+.. django-admin-option:: --verbosity
Example usage::
@@ -819,6 +823,23 @@ that ``django-admin.py`` should print to the console.
* ``1`` means normal output (default).
* ``2`` means verbose output.
+Common options
+==============
+
+The following options are not available on every commands, but they are
+common to a number of commands.
+
+.. django-admin-option:: --locale
+
+Use the ``--locale`` or ``-l`` option to specify the locale to process.
+If not provided all locales are processed.
+
+.. django-admin-option:: --noinput
+
+Use the ``--noinput`` option to suppress all user prompting, such as "Are
+you sure?" confirmation messages. This is useful if ``django-admin.py`` is
+being executed as an unattended, automated script.
+
Extra niceties
==============
@@ -844,5 +865,4 @@ distribution. It enables tab-completion of ``django-admin.py`` and
with ``sql``.
-
See :ref:`howto-custom-management-commands` for how to add customized actions.
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
-<django-admin-reset>` management command. That is, Django *manages* the
-database tables' lifecycles.
+tables in :djadmin:`syncdb` and remove them as part of a :djadmin:`reset`
+management command. That is, Django *manages* the database tables' lifecycles.
If ``False``, no database table creation or deletion operations will be
performed for this model. This is useful if the model represents an existing
@@ -114,7 +113,7 @@ model handling are exactly the same as normal. This includes
unmanaged model, then the intermediate table for the many-to-many join
will also not be created. However, a the intermediary table between one
managed and one unmanaged model *will* be created.
-
+
If you need to change this default behavior, create the intermediary
table as an explicit model (with ``managed`` set as needed) and use the
:attr:`ManyToManyField.through` attribute to make the relation use your
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 <ref-contrib-csrf>`.
+
.. 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 <misc-api-stability>`. This means
that, in general, code you develop against Django 1.0 should continue to work
@@ -150,6 +152,8 @@ Django 1.1 adds a ``permanent`` argument to the
backwards-incompatible if you were using the ``redirect_to`` view with a
format-string key called 'permanent', which is highly unlikely.
+.. _deprecated-features-1.1:
+
Features deprecated in 1.1
==========================
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 <ref-contrib-csrf>`. The following are the major
+changes that developers must be aware of:
+
+ * ``CsrfResponseMiddleware`` and ``CsrfMiddleware`` have been deprecated, and
+ will be removed completely in Django 1.4, in favor of a template tag that
+ should be inserted into forms.
+
+ * All contrib apps use a ``csrf_protect`` decorator to protect the view. This
+ requires the use of the csrf_token template tag in the template, so if you
+ have used custom templates for contrib views, you MUST READ THE :ref:`UPGRADE
+ INSTRUCTIONS <ref-csrf-upgrading-notes>` to fix those templates.
+
+ * ``CsrfViewMiddleware`` is included in :setting:`MIDDLEWARE_CLASSES` by
+ default. This turns on CSRF protection by default, so that views that accept
+ POST requests need to be written to work with the middleware. Instructions
+ on how to do this are found in the CSRF docs.
+
+ * All of the CSRF has moved from contrib to core (with backwards compatible
+ imports in the old locations, which are deprecated).
+
+``LazyObject``
+--------------
+
+``LazyObject`` is an undocumented utility class used for lazily wrapping other
+objects of unknown type. In Django 1.1 and earlier, it handled introspection in
+a non-standard way, depending on wrapped objects implementing a public method
+``get_all_members()``. Since this could easily lead to name clashes, it has been
+changed to use the standard method, involving ``__members__`` and ``__dir__()``.
+If you used ``LazyObject`` in your own code, and implemented the
+``get_all_members()`` method for wrapped objects, you need to make the following
+changes:
+
+ * If your class does not have special requirements for introspection (i.e. you
+ have not implemented ``__getattr__()`` or other methods that allow for
+ attributes not discoverable by normal mechanisms), you can simply remove the
+ ``get_all_members()`` method. The default implementation on ``LazyObject``
+ will do the right thing.
+
+ * If you have more complex requirements for introspection, first rename the
+ ``get_all_members()`` method to ``__dir__()``. This is the standard method,
+ from Python 2.6 onwards, for supporting introspection. If you are require
+ support for Python < 2.6, add the following code to the class::
+
+ __members__ = property(lambda self: self.__dir__())
+
+
+.. _deprecated-features-1.2:
+
+Features deprecated in 1.2
+==========================
+
+CSRF response rewriting middleware
+----------------------------------
+
+``CsrfResponseMiddleware``, the middleware that automatically inserted CSRF
+tokens into POST forms in outgoing pages, has been deprecated in favor of a
+template tag method (see above), and will be removed completely in Django
+1.4. ``CsrfMiddleware``, which includes the functionality of
+``CsrfResponseMiddleware`` and ``CsrfViewMiddleware`` has likewise been
+deprecated.
+
+Also, the CSRF module has moved from contrib to core, and the old imports are
+deprecated, as described in the :ref:`upgrading notes <ref-csrf-upgrading-notes>`.
+
+``SMTPConnection``
+------------------
+
+The ``SMTPConnection`` class has been deprecated in favor of a generic
+E-mail backend API. Old code that explicitly instantiated an instance
+of an SMTPConnection::
+
+ from django.core.mail import SMTPConnection
+ connection = SMTPConnection()
+ messages = get_notification_email()
+ connection.send_messages(messages)
+
+should now call :meth:`~django.core.mail.get_connection()` to
+instantiate a generic e-mail connection::
+
+ from django.core.mail import get_connection
+ connection = get_connection()
+ messages = get_notification_email()
+ connection.send_messages(messages)
+
+Depending on the value of the :setting:`EMAIL_BACKEND` setting, this
+may not return an SMTP connection. If you explicitly require an SMTP
+connection with which to send e-mail, you can explicitly request an
+SMTP connection::
+
+ from django.core.mail import get_connection
+ connection = get_connection('django.core.mail.backends.smtp')
+ messages = get_notification_email()
+ connection.send_messages(messages)
+
+If your call to construct an instance of ``SMTPConnection`` required
+additional arguments, those arguments can be passed to the
+:meth:`~django.core.mail.get_connection()` call::
+
+ connection = get_connection('django.core.mail.backends.smtp', hostname='localhost', port=1234)
+
+What's new in Django 1.2
+========================
+
+CSRF support
+------------
+
+Django now has much improved protection against :ref:`Cross-Site
+Request Forgery (CSRF) attacks<ref-contrib-csrf>`. This type of attack
+occurs when a malicious Web site contains a link, a form button or
+some javascript that is intended to perform some action on your Web
+site, using the credentials of a logged-in user who visits the
+malicious site in their browser. A related type of attack, 'login
+CSRF', where an attacking site tricks a user's browser into logging
+into a site with someone else's credentials, is also covered.
+
+E-mail Backends
+---------------
+
+You can now :ref:`configure the way that Django sends e-mail
+<topic-email-backends>`. Instead of using SMTP to send all e-mail, you
+can now choose a configurable e-mail backend to send messages. If your
+hosting provider uses a sandbox or some other non-SMTP technique for
+sending mail, you can now construct an e-mail backend that will allow
+Django's standard :ref:`mail sending methods<topics-email>` to use
+those facilities.
+
+This also makes it easier to debug mail sending - Django ships with
+backend implementations that allow you to send e-mail to a
+:ref:`file<topic-email-file-backend>`, to the
+:ref:`console<topic-email-console-backend>`, or to
+:ref:`memory<topic-email-memory-backend>` - you can even configure all
+e-mail to be :ref:`thrown away<topic-email-dummy-backend>`.
+
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
+
+ 1.0.2
+ 1.0.1
+ 1.0
+
+Pre-1.0 releases
+----------------
+.. toctree::
+ :maxdepth: 1
+
+ 0.96
+ 0.95
+
+Development releases
+====================
- 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.
+These notes are retained for historical purposes. If you are upgrading
+between formal Django releases, you don't need to worry about these
+notes.
-.. _backwards-incompatible changes: http://code.djangoproject.com/wiki/BackwardsIncompatibleChanges
+.. 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 <topic-email-backends>`
for more details.
@@ -215,8 +215,8 @@ message itself. The :ref:`e-mail backend <topic-email-backends>` is then
responsible for sending the e-mail.
For convenience, :class:`~django.core.mail.EmailMessage` provides a simple
-``send()`` method for sending a single email. If you need to send multiple
-messages, the email backend API :ref:`provides an alternative
+``send()`` method for sending a single e-mail. If you need to send multiple
+messages, the e-mail backend API :ref:`provides an alternative
<topics-sending-multiple-emails>`.
EmailMessage Objects
@@ -264,7 +264,7 @@ For example::
The class has the following methods:
* ``send(fail_silently=False)`` sends the message. If a connection was
- specified when the email was constructed, that connection will be used.
+ specified when the e-mail was constructed, that connection will be used.
Otherwise, an instance of the default backend will be instantiated and
used. If the keyword argument ``fail_silently`` is ``True``, exceptions
raised while sending the message will be quashed.
@@ -358,9 +358,9 @@ The actual sending of an e-mail is handled by the e-mail backend.
The e-mail backend class has the following methods:
- * ``open()`` instantiates an long-lived email-sending connection.
+ * ``open()`` instantiates an long-lived e-mail-sending connection.
- * ``close()`` closes the current email-sending connection.
+ * ``close()`` closes the current e-mail-sending connection.
* ``send_messages(email_messages)`` sends a list of
:class:`~django.core.mail.EmailMessage` objects. If the connection is
@@ -379,11 +379,11 @@ instance of the e-mail backend that you can use.
.. function:: get_connection(backend=None, fail_silently=False, *args, **kwargs)
By default, a call to ``get_connection()`` will return an instance of the
-email backend specified in :setting:`EMAIL_BACKEND`. If you specify the
+e-mail backend specified in :setting:`EMAIL_BACKEND`. If you specify the
``backend`` argument, an instance of that backend will be instantiated.
The ``fail_silently`` argument controls how the backend should handle errors.
-If ``fail_silently`` is True, exceptions during the email sending process
+If ``fail_silently`` is True, exceptions during the e-mail sending process
will be silently ignored.
All other arguments are passed directly to the constructor of the
@@ -391,8 +391,8 @@ e-mail backend.
Django ships with several e-mail sending backends. With the exception of the
SMTP backend (which is the default), these backends are only useful during
-testing and development. If you have special email sending requirements, you
-can :ref:`write your own email backend <topic-custom-email-backend>`.
+testing and development. If you have special e-mail sending requirements, you
+can :ref:`write your own e-mail backend <topic-custom-email-backend>`.
.. _topic-email-smtp-backend:
@@ -401,7 +401,7 @@ SMTP backend
This is the default backend. E-mail will be sent through a SMTP server.
The server address and authentication credentials are set in the
-:setting:`EMAIL_HOST`, :setting:`EMAIL_POST`, :setting:`EMAIL_HOST_USER`,
+:setting:`EMAIL_HOST`, :setting:`EMAIL_PORT`, :setting:`EMAIL_HOST_USER`,
:setting:`EMAIL_HOST_PASSWORD` and :setting:`EMAIL_USE_TLS` settings in your
settings file.
@@ -414,13 +414,15 @@ want to specify it explicitly, put the following in your settings::
Prior to version 1.2, Django provided a
:class:`~django.core.mail.SMTPConnection` class. This class provided a way
- to directly control the use of SMTP to send email. This class has been
- deprecated in favor of the generic email backend API.
+ to directly control the use of SMTP to send e-mail. This class has been
+ deprecated in favor of the generic e-mail backend API.
For backwards compatibility :class:`~django.core.mail.SMTPConnection` is
still available in ``django.core.mail`` as an alias for the SMTP backend.
New code should use :meth:`~django.core.mail.get_connection` instead.
+.. _topic-email-console-backend:
+
Console backend
~~~~~~~~~~~~~~~
@@ -436,6 +438,8 @@ To specify this backend, put the following in your settings::
This backend is not intended for use in production -- it is provided as a
convenience that can be used during development.
+.. _topic-email-file-backend:
+
File backend
~~~~~~~~~~~~
@@ -453,6 +457,8 @@ To specify this backend, put the following in your settings::
This backend is not intended for use in production -- it is provided as a
convenience that can be used during development.
+.. _topic-email-memory-backend:
+
In-memory backend
~~~~~~~~~~~~~~~~~
@@ -469,6 +475,8 @@ To specify this backend, put the following in your settings::
This backend is not intended for use in production -- it is provided as a
convenience that can be used during development and testing.
+.. _topic-email-dummy-backend:
+
Dummy backend
~~~~~~~~~~~~~
@@ -500,15 +508,15 @@ implementation.
.. _topics-sending-multiple-emails:
-Sending multiple emails
------------------------
+Sending multiple e-mails
+------------------------
Establishing and closing an SMTP connection (or any other network connection,
-for that matter) is an expensive process. If you have a lot of emails to send,
+for that matter) is an expensive process. If you have a lot of e-mails to send,
it makes sense to reuse an SMTP connection, rather than creating and
-destroying a connection every time you want to send an email.
+destroying a connection every time you want to send an e-mail.
-There are two ways you tell an email backend to reuse a connection.
+There are two ways you tell an e-mail backend to reuse a connection.
Firstly, you can use the ``send_messages()`` method. ``send_messages()`` takes
a list of :class:`~django.core.mail.EmailMessage` instances (or subclasses),
@@ -516,11 +524,11 @@ and sends them all using a single connection.
For example, if you have a function called ``get_notification_email()`` that
returns a list of :class:`~django.core.mail.EmailMessage` objects representing
-some periodic e-mail you wish to send out, you could send these emails using
+some periodic e-mail you wish to send out, you could send these e-mails using
a single call to send_messages::
from django.core import mail
- connection = mail.get_connection() # Use default email connection
+ connection = mail.get_connection() # Use default e-mail connection
messages = get_notification_email()
connection.send_messages(messages)
@@ -528,7 +536,7 @@ In this example, the call to ``send_messages()`` opens a connection on the
backend, sends the list of messages, and then closes the connection again.
The second approach is to use the ``open()`` and ``close()`` methods on the
-email backend to manually control the connection. ``send_messages()`` will not
+e-mail backend to manually control the connection. ``send_messages()`` will not
manually open or close the connection if it is already open, so if you
manually open the connection, you can control when it is closed. For example::
@@ -538,10 +546,10 @@ manually open the connection, you can control when it is closed. For example::
# Manually open the connection
connection.open()
- # Construct an email message that uses the connection
+ # Construct an e-mail message that uses the connection
email1 = mail.EmailMessage('Hello', 'Body goes here', 'from@example.com',
['to1@example.com'], connection=connection)
- email1.send() # Send the email
+ email1.send() # Send the e-mail
# Construct two more messages
email2 = mail.EmailMessage('Hello', 'Body goes here', 'from@example.com',
@@ -549,7 +557,7 @@ manually open the connection, you can control when it is closed. For example::
email3 = mail.EmailMessage('Hello', 'Body goes here', 'from@example.com',
['to3@example.com'])
- # Send the two emails in a single call -
+ # Send the two e-mails in a single call -
connection.send_messages([email2, email3])
# The connection was already open so send_messages() doesn't close it.
# We need to manually close the connection.
@@ -566,10 +574,10 @@ people under the right conditions, and that those e-mails will contain the
correct content.
The easiest way to test your project's use of e-mail is to use the ``console``
-email backend. This backend redirects all email to stdout, allowing you to
+e-mail backend. This backend redirects all e-mail to stdout, allowing you to
inspect the content of mail.
-The ``file`` email backend can also be useful during development -- this backend
+The ``file`` e-mail backend can also be useful during development -- this backend
dumps the contents of every SMTP connection to a file that can be inspected
at your leisure.
@@ -596,7 +604,7 @@ SMTPConnection
.. deprecated:: 1.2
-The ``SMTPConnection`` class has been deprecated in favor of the generic email
+The ``SMTPConnection`` class has been deprecated in favor of the generic e-mail
backend API.
For backwards compatibility ``SMTPConnection`` is still available in
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 <request-middleware>`), its value
will be used in place of the ``ROOT_URLCONF`` setting.
2. Django loads that Python module and looks for the variable
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<loaddata>` for more
+ ``birds``. See the :djadmin:`loaddata` documentation for more
details on defining and installing fixtures.
This flush/load procedure is repeated for each test in the test case, so you
@@ -1028,6 +1030,7 @@ For example::
def testIndexPageView(self):
# Here you'd test your view using ``Client``.
+ call_some_test_code()
This test case will use the contents of ``myapp.test_urls`` as the
URLconf for the duration of the test case.
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
+<Advertisment: Advertisment object>
+
+>>> 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: <class 'regressiontests.admin_validation.models.TwoAlbumFKAndAnE'> ha
>>> validate_inline(TwoAlbumFKAndAnEInline, None, Album)
+# Regression test for #12203/#12237 - Fail more gracefully when a M2M field that
+# specifies the 'through' option is included in the 'fields' or the 'fieldsets'
+# ModelAdmin options.
+
+>>> class BookAdmin(admin.ModelAdmin):
+... fields = ['authors']
+
+>>> validate(BookAdmin, Book)
+Traceback (most recent call last):
+ ...
+ImproperlyConfigured: 'BookAdmin.fields' can't include the ManyToManyField field 'authors' because 'authors' manually specifies a 'through' model.
+
+>>> class FieldsetBookAdmin(admin.ModelAdmin):
+... fieldsets = (
+... ('Header 1', {'fields': ('name',)}),
+... ('Header 2', {'fields': ('authors',)}),
+... )
+
+>>> validate(FieldsetBookAdmin, Book)
+Traceback (most recent call last):
+ ...
+ImproperlyConfigured: 'FieldsetBookAdmin.fieldsets[1][1]['fields']' can't include the ManyToManyField field 'authors' because 'authors' manually specifies a 'through' model.
+
+>>> class NestedFieldsetAdmin(admin.ModelAdmin):
+... fieldsets = (
+... ('Main', {'fields': ('price', ('name', 'subtitle'))}),
+... )
+
+>>> validate(NestedFieldsetAdmin, Book)
+
+# Regression test for #12209 -- If the explicitly provided through model
+# is specified as a string, the admin should still be able use
+# Model.m2m_field.through
+
+>>> class AuthorsInline(admin.TabularInline):
+... model = Book.authors.through
+
+>>> class BookAdmin(admin.ModelAdmin):
+... inlines = [AuthorsInline]
+
+# If the through model is still a string (and hasn't been resolved to a model)
+# the validation will fail.
+>>> validate(BookAdmin, Book)
+
"""}
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'<ul class="errorlist"><li>(Hidden field data) This field is required.</li></ul
>>> f.as_table()
u'<tr><td colspan="2"><ul class="errorlist"><li>(Hidden field data) This field is required.</li></ul><input type="hidden" name="data" id="id_data" /></td></tr>'
+###################################################
+# Tests for XSS vulnerabilities in error messages #
+###################################################
+
+# The forms layer doesn't escape input values directly because error messages
+# might be presented in non-HTML contexts. Instead, the message is just marked
+# for escaping by the template engine. So we'll need to construct a little
+# silly template to trigger the escaping.
+
+>>> from django.template import Template, Context
+>>> t = Template('{{ form.errors }}')
+
+>>> class SomeForm(Form):
+... field = ChoiceField(choices=[('one', 'One')])
+>>> f = SomeForm({'field': '<script>'})
+>>> t.render(Context({'form': f}))
+u'<ul class="errorlist"><li>field<ul class="errorlist"><li>Select a valid choice. &lt;script&gt; is not one of the available choices.</li></ul></li></ul>'
+
+>>> class SomeForm(Form):
+... field = MultipleChoiceField(choices=[('one', 'One')])
+>>> f = SomeForm({'field': ['<script>']})
+>>> t.render(Context({'form': f}))
+u'<ul class="errorlist"><li>field<ul class="errorlist"><li>Select a valid choice. &lt;script&gt; is not one of the available choices.</li></ul></li></ul>'
+
+>>> from regressiontests.forms.models import ChoiceModel
+>>> class SomeForm(Form):
+... field = ModelMultipleChoiceField(ChoiceModel.objects.all())
+>>> f = SomeForm({'field': ['<script>']})
+>>> t.render(Context({'form': f}))
+u'<ul class="errorlist"><li>field<ul class="errorlist"><li>&quot;&lt;script&gt;&quot; is not a valid value for a primary key.</li></ul></li></ul>'
"""
diff --git a/tests/regressiontests/many_to_one_regress/models.py b/tests/regressiontests/many_to_one_regress/models.py
index 3f35bef62c..d491f002c2 100644
--- a/tests/regressiontests/many_to_one_regress/models.py
+++ b/tests/regressiontests/many_to_one_regress/models.py
@@ -167,4 +167,10 @@ Traceback (most recent call last):
...
ValueError: Cannot assign "<Child: Child object>": "Child.parent" must be a "Parent" instance.
+# Regression for #12190 -- Should be able to instantiate a FK
+# outside of a model, and interrogate its related field.
+>>> cat = models.ForeignKey(Category)
+>>> cat.rel.get_related_field().name
+'id'
+
"""}
diff --git a/tests/regressiontests/queries/models.py b/tests/regressiontests/queries/models.py
index cf21d5210b..ddac509ede 100644
--- a/tests/regressiontests/queries/models.py
+++ b/tests/regressiontests/queries/models.py
@@ -755,10 +755,20 @@ Bug #6180, #6203 -- dates with limits and/or counts
>>> Item.objects.dates('created', 'day')[0]
datetime.datetime(2007, 12, 19, 0, 0)
-Bug #7087 -- dates with extra select columns
+Bug #7087/#12242 -- dates with extra select columns
>>> Item.objects.dates('created', 'day').extra(select={'a': 1})
[datetime.datetime(2007, 12, 19, 0, 0), datetime.datetime(2007, 12, 20, 0, 0)]
+>>> Item.objects.extra(select={'a': 1}).dates('created', 'day')
+[datetime.datetime(2007, 12, 19, 0, 0), datetime.datetime(2007, 12, 20, 0, 0)]
+
+>>> name="one"
+>>> Item.objects.dates('created', 'day').extra(where=['name=%s'], params=[name])
+[datetime.datetime(2007, 12, 19, 0, 0)]
+
+>>> Item.objects.extra(where=['name=%s'], params=[name]).dates('created', 'day')
+[datetime.datetime(2007, 12, 19, 0, 0)]
+
Bug #7155 -- nullable dates
>>> Item.objects.dates('modified', 'day')
[datetime.datetime(2007, 12, 19, 0, 0)]
diff --git a/tests/regressiontests/urlpatterns_reverse/middleware.py b/tests/regressiontests/urlpatterns_reverse/middleware.py
new file mode 100644
index 0000000000..7bb9aa924f
--- /dev/null
+++ b/tests/regressiontests/urlpatterns_reverse/middleware.py
@@ -0,0 +1,7 @@
+from django.core.urlresolvers import set_urlconf
+
+import urlconf_inner
+
+class ChangeURLconfMiddleware(object):
+ def process_request(self, request):
+ request.urlconf = urlconf_inner.__name__
diff --git a/tests/regressiontests/urlpatterns_reverse/tests.py b/tests/regressiontests/urlpatterns_reverse/tests.py
index d4f281ba81..34c9db25d3 100644
--- a/tests/regressiontests/urlpatterns_reverse/tests.py
+++ b/tests/regressiontests/urlpatterns_reverse/tests.py
@@ -16,11 +16,16 @@ ImproperlyConfigured: The included urlconf regressiontests.urlpatterns_reverse.n
import unittest
+from django.conf import settings
from django.core.urlresolvers import reverse, resolve, NoReverseMatch, Resolver404
from django.http import HttpResponseRedirect, HttpResponsePermanentRedirect
from django.shortcuts import redirect
from django.test import TestCase
+import urlconf_outer
+import urlconf_inner
+import middleware
+
test_data = (
('places', '/places/3/', [3], {}),
('places', '/places/3/', ['3'], {}),
@@ -239,3 +244,35 @@ class NamespaceTests(TestCase):
self.assertEquals('/other1/inner/37/42/', reverse('nodefault:urlobject-view', args=[37,42], current_app='other-ns1'))
self.assertEquals('/other1/inner/42/37/', reverse('nodefault:urlobject-view', kwargs={'arg1':42, 'arg2':37}, current_app='other-ns1'))
+
+class RequestURLconfTests(TestCase):
+ def setUp(self):
+ self.root_urlconf = settings.ROOT_URLCONF
+ self.middleware_classes = settings.MIDDLEWARE_CLASSES
+ settings.ROOT_URLCONF = urlconf_outer.__name__
+
+ def tearDown(self):
+ settings.ROOT_URLCONF = self.root_urlconf
+ settings.MIDDLEWARE_CLASSES = self.middleware_classes
+
+ def test_urlconf(self):
+ response = self.client.get('/test/me/')
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.content, 'outer:/test/me/,'
+ 'inner:/inner_urlconf/second_test/')
+ response = self.client.get('/inner_urlconf/second_test/')
+ self.assertEqual(response.status_code, 200)
+ response = self.client.get('/second_test/')
+ self.assertEqual(response.status_code, 404)
+
+ def test_urlconf_overridden(self):
+ settings.MIDDLEWARE_CLASSES += (
+ '%s.ChangeURLconfMiddleware' % middleware.__name__,
+ )
+ response = self.client.get('/test/me/')
+ self.assertEqual(response.status_code, 404)
+ response = self.client.get('/inner_urlconf/second_test/')
+ self.assertEqual(response.status_code, 404)
+ response = self.client.get('/second_test/')
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(response.content, 'outer:,inner:/second_test/')
diff --git a/tests/regressiontests/urlpatterns_reverse/urlconf_inner.py b/tests/regressiontests/urlpatterns_reverse/urlconf_inner.py
new file mode 100644
index 0000000000..d188e06421
--- /dev/null
+++ b/tests/regressiontests/urlpatterns_reverse/urlconf_inner.py
@@ -0,0 +1,12 @@
+from django.conf.urls.defaults import *
+from django.template import Template, Context
+from django.http import HttpResponse
+
+def inner_view(request):
+ content = Template('{% url outer as outer_url %}outer:{{ outer_url }},'
+ '{% url inner as inner_url %}inner:{{ inner_url }}').render(Context())
+ return HttpResponse(content)
+
+urlpatterns = patterns('',
+ url(r'^second_test/$', inner_view, name='inner'),
+) \ No newline at end of file
diff --git a/tests/regressiontests/urlpatterns_reverse/urlconf_outer.py b/tests/regressiontests/urlpatterns_reverse/urlconf_outer.py
new file mode 100644
index 0000000000..506e03666e
--- /dev/null
+++ b/tests/regressiontests/urlpatterns_reverse/urlconf_outer.py
@@ -0,0 +1,9 @@
+from django.conf.urls.defaults import *
+
+import urlconf_inner
+
+
+urlpatterns = patterns('',
+ url(r'^test/me/$', urlconf_inner.inner_view, name='outer'),
+ url(r'^inner_urlconf/', include(urlconf_inner.__name__))
+) \ No newline at end of file