diff --git a/AUTHORS b/AUTHORS index 5f36d83770..c965ddee4e 100644 --- a/AUTHORS +++ b/AUTHORS @@ -131,6 +131,7 @@ answer newbie questions, and generally made Django that much better: Andrew Durdin dusk@woofle.net Andy Dustman + J. Clifford Dyer Clint Ecker Nick Efford eibaan@gmail.com diff --git a/django/conf/locale/ca/LC_MESSAGES/django.mo b/django/conf/locale/ca/LC_MESSAGES/django.mo index 38aecaae32..3039bd8fad 100644 Binary files a/django/conf/locale/ca/LC_MESSAGES/django.mo and b/django/conf/locale/ca/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/ca/LC_MESSAGES/django.po b/django/conf/locale/ca/LC_MESSAGES/django.po index 9cfafe3e2a..57f8764938 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-03-24 13:00+0100\n" +"POT-Creation-Date: 2009-07-07 15:04+0200\n" "PO-Revision-Date: 2009-03-24 13:28+0100\n" "Last-Translator: Django Catalan Group \n" "Language-Team: Catalan \n" @@ -218,6 +218,20 @@ msgstr "xinès simplificat" msgid "Traditional Chinese" msgstr "xinès tradicional" +#: contrib/admin/actions.py:60 +#, python-format +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 +msgid "Are you sure?" +msgstr "Esteu segurs?" + +#: contrib/admin/actions.py:85 +#, python-format +msgid "Delete selected %(verbose_name_plural)s" +msgstr "Eliminar els %(verbose_name_plural)s seleccionats" + #: contrib/admin/filterspecs.py:44 #, python-format msgid "" @@ -252,15 +266,15 @@ msgstr "Aquest mes" msgid "This year" msgstr "Aquest any" -#: contrib/admin/filterspecs.py:147 forms/widgets.py:413 +#: contrib/admin/filterspecs.py:147 forms/widgets.py:434 msgid "Yes" msgstr "Si" -#: contrib/admin/filterspecs.py:147 forms/widgets.py:413 +#: contrib/admin/filterspecs.py:147 forms/widgets.py:434 msgid "No" msgstr "No" -#: contrib/admin/filterspecs.py:154 forms/widgets.py:413 +#: contrib/admin/filterspecs.py:154 forms/widgets.py:434 msgid "Unknown" msgstr "Desconegut" @@ -296,74 +310,61 @@ msgstr "entrada del registre" msgid "log entries" msgstr "entrades del registre" -#: contrib/admin/options.py:131 contrib/admin/options.py:145 +#: contrib/admin/options.py:133 contrib/admin/options.py:147 msgid "None" msgstr "cap" -#: contrib/admin/options.py:498 -#, python-format -msgid "Successfully deleted %(count)d %(items)s." -msgstr "Eliminat/s %(count)d %(items)s satisfactòriament." - -#: contrib/admin/options.py:505 contrib/admin/options.py:1012 -msgid "Are you sure?" -msgstr "Esteu segurs?" - -#: contrib/admin/options.py:523 -#, python-format -msgid "Delete selected %(verbose_name_plural)s" -msgstr "Eliminar els %(verbose_name_plural)s seleccionats" - -#: contrib/admin/options.py:531 +#: contrib/admin/options.py:519 #, python-format msgid "Changed %s." msgstr "Modificat %s." -#: contrib/admin/options.py:531 contrib/admin/options.py:541 -#: contrib/comments/templates/comments/preview.html:15 forms/models.py:296 +#: 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 msgid "and" msgstr "i" -#: contrib/admin/options.py:536 +#: contrib/admin/options.py:524 #, python-format msgid "Added %(name)s \"%(object)s\"." msgstr "Afegit %(name)s \"%(object)s\"" -#: contrib/admin/options.py:540 +#: contrib/admin/options.py:528 #, 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:545 +#: contrib/admin/options.py:533 #, python-format msgid "Deleted %(name)s \"%(object)s\"." msgstr "Eliminat %(name)s \"%(object)s\"." -#: contrib/admin/options.py:549 +#: contrib/admin/options.py:537 msgid "No fields changed." msgstr "Cap camp canviat." -#: contrib/admin/options.py:610 contrib/auth/admin.py:67 +#: contrib/admin/options.py:598 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:614 contrib/admin/options.py:647 +#: contrib/admin/options.py:602 contrib/admin/options.py:635 #: contrib/auth/admin.py:75 msgid "You may edit it again below." msgstr "Podeu editar-lo de nou a baix." -#: contrib/admin/options.py:624 contrib/admin/options.py:657 +#: contrib/admin/options.py:612 contrib/admin/options.py:645 #, python-format msgid "You may add another %s below." msgstr "Podeu afegir un altre %s a baix." -#: contrib/admin/options.py:645 +#: contrib/admin/options.py:633 #, 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:653 +#: contrib/admin/options.py:641 #, python-format msgid "" "The %(name)s \"%(obj)s\" was added successfully. You may edit it again below." @@ -371,43 +372,43 @@ msgstr "" "S'ha afegit exitosament el/la %(name)s \"%(obj)s\". Pot editar-lo de nou " "abaix." -#: contrib/admin/options.py:774 +#: contrib/admin/options.py:772 #, python-format msgid "Add %s" msgstr "Afegir %s" -#: contrib/admin/options.py:805 contrib/admin/options.py:990 +#: contrib/admin/options.py:803 contrib/admin/options.py:1003 #, 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:862 +#: contrib/admin/options.py:860 #, python-format msgid "Change %s" msgstr "Modificar %s" -#: contrib/admin/options.py:894 +#: contrib/admin/options.py:904 msgid "Database error" msgstr "Error de base de dades" -#: contrib/admin/options.py:930 +#: contrib/admin/options.py:940 #, 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:1005 +#: contrib/admin/options.py:1018 #, 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:1041 +#: contrib/admin/options.py:1054 #, python-format msgid "Change history: %s" msgstr "Modificar històric: %s" -#: contrib/admin/sites.py:15 contrib/admin/views/decorators.py:14 +#: contrib/admin/sites.py:20 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-" @@ -416,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:250 contrib/admin/views/decorators.py:40 +#: contrib/admin/sites.py:278 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:257 contrib/admin/views/decorators.py:47 +#: contrib/admin/sites.py:285 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." @@ -429,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:273 contrib/admin/sites.py:279 +#: contrib/admin/sites.py:301 contrib/admin/sites.py:307 #: 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:276 contrib/admin/views/decorators.py:62 +#: contrib/admin/sites.py:304 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:336 +#: contrib/admin/sites.py:360 msgid "Site administration" msgstr "Lloc administratiu" -#: contrib/admin/sites.py:349 contrib/admin/templates/admin/login.html:26 +#: contrib/admin/sites.py:373 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:396 +#: contrib/admin/sites.py:417 #, python-format msgid "%s administration" msgstr "Administració de %s" @@ -466,34 +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/util.py:222 -#, fuzzy -msgid "verbose_name" -msgid_plural "verbose_name_plural" -msgstr[0] "verbose_name" -msgstr[1] "verbose_name_plural" - -#: contrib/admin/widgets.py:70 +#: contrib/admin/widgets.py:71 msgid "Date:" msgstr "Data:" -#: contrib/admin/widgets.py:70 +#: contrib/admin/widgets.py:71 msgid "Time:" msgstr "Hora:" -#: contrib/admin/widgets.py:94 +#: contrib/admin/widgets.py:95 msgid "Currently:" msgstr "Actualment:" -#: contrib/admin/widgets.py:94 +#: contrib/admin/widgets.py:95 msgid "Change:" msgstr "Modificar:" -#: contrib/admin/widgets.py:123 +#: contrib/admin/widgets.py:124 msgid "Lookup" msgstr "Cercar" -#: contrib/admin/widgets.py:230 +#: contrib/admin/widgets.py:236 msgid "Add Another" msgstr "Afegir un altre" @@ -508,9 +502,9 @@ 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:33 +#: contrib/admin/templates/admin/base.html:31 #: contrib/admin/templates/admin/change_form.html:17 -#: contrib/admin/templates/admin/change_list.html:20 +#: contrib/admin/templates/admin/change_list.html:25 #: contrib/admin/templates/admin/delete_confirmation.html:6 #: contrib/admin/templates/admin/delete_selected_confirmation.html:6 #: contrib/admin/templates/admin/invalid_setup.html:4 @@ -552,7 +546,6 @@ msgid "Run the selected action" msgstr "Executar la acció seleccionada" #: contrib/admin/templates/admin/actions.html:4 -#: contrib/admin/templates/admin/search_form.html:8 msgid "Go" msgstr "Anar" @@ -562,26 +555,26 @@ msgstr "Anar" msgid "%(name)s" msgstr "%(name)s" -#: contrib/admin/templates/admin/base.html:28 +#: contrib/admin/templates/admin/base.html:26 msgid "Welcome," msgstr "Benvingut/da," -#: contrib/admin/templates/admin/base.html:28 +#: contrib/admin/templates/admin/base.html:26 #: 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:28 -#: contrib/admin/templates/admin/auth/user/change_password.html:13 -#: contrib/admin/templates/admin/auth/user/change_password.html:46 +#: contrib/admin/templates/admin/base.html:26 +#: 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 #: contrib/admin/templates/registration/password_change_form.html:3 msgid "Change password" msgstr "Canviar contrasenya" -#: contrib/admin/templates/admin/base.html:28 +#: contrib/admin/templates/admin/base.html:26 #: contrib/admin/templates/registration/password_change_done.html:3 #: contrib/admin/templates/registration/password_change_form.html:3 msgid "Log out" @@ -612,24 +605,24 @@ msgid "View on site" msgstr "Veure al lloc" #: contrib/admin/templates/admin/change_form.html:38 -#: contrib/admin/templates/admin/change_list.html:49 -#: contrib/admin/templates/admin/auth/user/change_password.html:22 +#: contrib/admin/templates/admin/change_list.html:54 +#: contrib/admin/templates/admin/auth/user/change_password.html:23 msgid "Please correct the error below." msgid_plural "Please correct the errors below." msgstr[0] "Si us plau, corregiu l'error mostrat a baix." msgstr[1] "Si us plau, corregiu els errors mostrats a baix." -#: contrib/admin/templates/admin/change_list.html:41 +#: contrib/admin/templates/admin/change_list.html:46 #, python-format msgid "Add %(name)s" msgstr "Afegir %(name)s" -#: contrib/admin/templates/admin/change_list.html:60 +#: contrib/admin/templates/admin/change_list.html:65 msgid "Filter" msgstr "Filtre" #: contrib/admin/templates/admin/delete_confirmation.html:10 -#: contrib/admin/templates/admin/submit_line.html:4 forms/formsets.py:251 +#: contrib/admin/templates/admin/submit_line.html:4 forms/formsets.py:275 msgid "Delete" msgstr "Eliminar" @@ -669,9 +662,9 @@ msgid "" "your account doesn't have permission to delete the following types of " "objects:" msgstr "" -"Eliminar el/la %(object_name)s provocaria l'eliminació " -"d'objectes relacionats, però el vostre compte no te permissos per a esborrar " -"els tipus d'objecte següents:" +"Eliminar el/la %(object_name)s provocaria l'eliminació d'objectes " +"relacionats, però el vostre compte no te permissos per a esborrar els tipus " +"d'objecte següents:" #: contrib/admin/templates/admin/delete_selected_confirmation.html:22 #, python-format @@ -679,8 +672,8 @@ 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:" msgstr "" -"Esteu segurs de voler esborrar els/les %(object_name)s seleccionats?" -"Tots aquests objectes i els seus elements relacionats s'esborraran:" +"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 #, python-format @@ -712,6 +705,10 @@ msgstr "Les meves accions" msgid "None available" msgstr "Cap disponible" +#: contrib/admin/templates/admin/index.html:72 +msgid "Unknown content" +msgstr "Contingut desconegut" + #: contrib/admin/templates/admin/invalid_setup.html:7 msgid "" "Something's wrong with your database installation. Make sure the appropriate " @@ -760,6 +757,15 @@ msgstr "" msgid "Show all" msgstr "Mostrar tots" +#: contrib/admin/templates/admin/pagination.html:11 +#: contrib/admin/templates/admin/submit_line.html:3 +msgid "Save" +msgstr "Desar" + +#: contrib/admin/templates/admin/search_form.html:8 +msgid "Search" +msgstr "Cerca" + #: contrib/admin/templates/admin/search_form.html:10 #, python-format msgid "1 result" @@ -772,10 +778,6 @@ msgstr[1] "%(counter)s resultats" msgid "%(full_result_count)s total" msgstr "%(full_result_count)s en total" -#: contrib/admin/templates/admin/submit_line.html:3 -msgid "Save" -msgstr "Desar" - #: contrib/admin/templates/admin/submit_line.html:5 msgid "Save as new" msgstr "Desar com a nou" @@ -802,23 +804,23 @@ msgid "Username" msgstr "Usuari" #: contrib/admin/templates/admin/auth/user/add_form.html:20 -#: contrib/admin/templates/admin/auth/user/change_password.html:33 +#: contrib/admin/templates/admin/auth/user/change_password.html:34 #: contrib/auth/forms.py:17 contrib/auth/forms.py:60 contrib/auth/forms.py:185 msgid "Password" msgstr "Contrasenya" #: contrib/admin/templates/admin/auth/user/add_form.html:26 -#: contrib/admin/templates/admin/auth/user/change_password.html:39 +#: contrib/admin/templates/admin/auth/user/change_password.html:40 #: contrib/auth/forms.py:186 msgid "Password (again)" msgstr "Contrasenya (de nou)" #: contrib/admin/templates/admin/auth/user/add_form.html:27 -#: contrib/admin/templates/admin/auth/user/change_password.html:40 +#: contrib/admin/templates/admin/auth/user/change_password.html:41 msgid "Enter the same password as above, for verification." msgstr "Introduïu la mateixa contrasenya de dalt, per fer-ne la verificació." -#: contrib/admin/templates/admin/auth/user/change_password.html:26 +#: contrib/admin/templates/admin/auth/user/change_password.html:27 #, python-format msgid "Enter a new password for the user %(username)s." msgstr "Introduïu una contrasenya per l'usuari %(username)s" @@ -1035,114 +1037,115 @@ msgstr "El model %(model_name)r no s'ha trobat en l'aplicació %(app_label)r" 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:228 -#: contrib/admindocs/views.py:242 contrib/admindocs/views.py:247 +#: 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 msgid "model:" msgstr "model:" -#: contrib/admindocs/views.py:237 +#: contrib/admindocs/views.py:221 contrib/admindocs/views.py:253 #, python-format msgid "related `%(app_label)s.%(object_name)s` objects" msgstr "objectes relacionats `%(app_label)s.%(object_name)s`" -#: contrib/admindocs/views.py:242 +#: contrib/admindocs/views.py:225 contrib/admindocs/views.py:258 #, python-format msgid "all %s" msgstr "tots %s" -#: contrib/admindocs/views.py:247 +#: contrib/admindocs/views.py:230 contrib/admindocs/views.py:263 #, python-format msgid "number of %s" msgstr "nombre de %s" -#: contrib/admindocs/views.py:252 +#: contrib/admindocs/views.py:268 #, python-format msgid "Fields on %s objects" msgstr "Camps en objectes %s" -#: 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: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 msgid "Integer" msgstr "Enter" -#: contrib/admindocs/views.py:316 +#: contrib/admindocs/views.py:332 msgid "Boolean (Either True or False)" msgstr "Booleà (Verdader o Fals)" -#: contrib/admindocs/views.py:317 contrib/admindocs/views.py:336 +#: contrib/admindocs/views.py:333 contrib/admindocs/views.py:352 #, python-format msgid "String (up to %(max_length)s)" msgstr "Cadena (de fins a %(max_length)s)" -#: contrib/admindocs/views.py:318 +#: contrib/admindocs/views.py:334 msgid "Comma-separated integers" msgstr "Enters separats per comes" -#: contrib/admindocs/views.py:319 +#: contrib/admindocs/views.py:335 msgid "Date (without time)" msgstr "Data (sense hora)" -#: contrib/admindocs/views.py:320 +#: contrib/admindocs/views.py:336 msgid "Date (with time)" msgstr "Data (amb hora)" -#: contrib/admindocs/views.py:321 +#: contrib/admindocs/views.py:337 msgid "Decimal number" msgstr "Número decimal" -#: contrib/admindocs/views.py:322 +#: contrib/admindocs/views.py:338 msgid "E-mail address" msgstr "Adreça de correu electrònic" -#: contrib/admindocs/views.py:323 contrib/admindocs/views.py:324 -#: contrib/admindocs/views.py:327 +#: contrib/admindocs/views.py:339 contrib/admindocs/views.py:340 +#: contrib/admindocs/views.py:343 msgid "File path" msgstr "Ruta del fitxer" -#: contrib/admindocs/views.py:325 +#: contrib/admindocs/views.py:341 msgid "Floating point number" msgstr "Número amb punt de coma flotant" -#: contrib/admindocs/views.py:329 contrib/comments/models.py:58 +#: contrib/admindocs/views.py:345 contrib/comments/models.py:60 msgid "IP address" msgstr "Adreça IP" -#: contrib/admindocs/views.py:331 +#: contrib/admindocs/views.py:347 msgid "Boolean (Either True, False or None)" msgstr "Booleà (Verdader, Fals o 'None' (cap))" -#: contrib/admindocs/views.py:332 +#: contrib/admindocs/views.py:348 msgid "Relation to parent model" msgstr "Relació amb el model pare" -#: contrib/admindocs/views.py:333 +#: contrib/admindocs/views.py:349 msgid "Phone number" msgstr "Número de telèfon" -#: contrib/admindocs/views.py:338 +#: contrib/admindocs/views.py:354 msgid "Text" msgstr "Text" -#: contrib/admindocs/views.py:339 +#: contrib/admindocs/views.py:355 msgid "Time" msgstr "Hora" -#: contrib/admindocs/views.py:340 contrib/comments/forms.py:95 +#: contrib/admindocs/views.py:356 contrib/comments/forms.py:95 #: contrib/comments/templates/comments/moderation_queue.html:37 #: contrib/flatpages/admin.py:8 contrib/flatpages/models.py:7 msgid "URL" msgstr "URL" -#: contrib/admindocs/views.py:341 +#: contrib/admindocs/views.py:357 msgid "U.S. state (two uppercase letters)" msgstr "Estat dels E.U.A. (dues lletres majúscules)" -#: contrib/admindocs/views.py:342 +#: contrib/admindocs/views.py:358 msgid "XML text" msgstr "Text XML" -#: contrib/admindocs/views.py:368 +#: contrib/admindocs/views.py:384 #, python-format msgid "%s does not appear to be a urlpattern object" msgstr "%s no sembla ser un objecte 'urlpattern'" @@ -1247,7 +1250,7 @@ msgid "Change password: %s" msgstr "Canviar contrasenya: %s" #: contrib/auth/forms.py:15 contrib/auth/forms.py:48 -#: contrib/auth/models.py:127 +#: contrib/auth/models.py:128 msgid "" "Required. 30 characters or fewer. Alphanumeric characters only (letters, " "digits and underscores)." @@ -1338,31 +1341,31 @@ msgstr "permisos" msgid "group" msgstr "grup" -#: contrib/auth/models.py:91 contrib/auth/models.py:137 +#: contrib/auth/models.py:91 contrib/auth/models.py:138 msgid "groups" msgstr "grups" -#: contrib/auth/models.py:127 +#: contrib/auth/models.py:128 msgid "username" msgstr "nom d'usuari" -#: contrib/auth/models.py:128 +#: contrib/auth/models.py:129 msgid "first name" msgstr "nom propi" -#: contrib/auth/models.py:129 +#: contrib/auth/models.py:130 msgid "last name" msgstr "cognoms" -#: contrib/auth/models.py:130 +#: contrib/auth/models.py:131 msgid "e-mail address" msgstr "adreça de correu electrònic" -#: contrib/auth/models.py:131 +#: contrib/auth/models.py:132 msgid "password" msgstr "contrasenya" -#: contrib/auth/models.py:131 +#: contrib/auth/models.py:132 msgid "" "Use '[algo]$[salt]$[hexdigest]' or use the change " "password form." @@ -1370,19 +1373,19 @@ msgstr "" "Utilitzeu '[algo]$[salt]$[hexdigest]' o el formulari " "de canvi de contrasenya." -#: contrib/auth/models.py:132 +#: contrib/auth/models.py:133 msgid "staff status" msgstr "membre del personal" -#: contrib/auth/models.py:132 +#: contrib/auth/models.py:133 msgid "Designates whether the user can log into this admin site." msgstr "Indica si l'usuari pot entrar en el lloc administratiu." -#: contrib/auth/models.py:133 +#: contrib/auth/models.py:134 msgid "active" msgstr "actiu" -#: contrib/auth/models.py:133 +#: contrib/auth/models.py:134 msgid "" "Designates whether this user should be treated as active. Unselect this " "instead of deleting accounts." @@ -1390,11 +1393,11 @@ msgstr "" "Designa si aquest usuari ha de ser tractat com a actiu. Deseleccioneu-ho " "enlloc d'esborrar comptes d'usuari." -#: contrib/auth/models.py:134 +#: contrib/auth/models.py:135 msgid "superuser status" msgstr "estat de superusuari" -#: contrib/auth/models.py:134 +#: contrib/auth/models.py:135 msgid "" "Designates that this user has all permissions without explicitly assigning " "them." @@ -1402,15 +1405,15 @@ msgstr "" "Designa que aquest usuari té tots els permissos sense assignar-los " "explícitament." -#: contrib/auth/models.py:135 +#: contrib/auth/models.py:136 msgid "last login" msgstr "últim inici de sessió" -#: contrib/auth/models.py:136 +#: contrib/auth/models.py:137 msgid "date joined" msgstr "data de creació" -#: contrib/auth/models.py:138 +#: contrib/auth/models.py:139 msgid "" "In addition to the permissions manually assigned, this user will also get " "all permissions granted to each group he/she is in." @@ -1418,23 +1421,24 @@ msgstr "" "Junt amb els permissos asignats manualment, aquest usuari tindrà, també, els " "permissos dels grups dels que sigui membre." -#: contrib/auth/models.py:139 +#: contrib/auth/models.py:140 msgid "user permissions" msgstr "permissos de l'usuari" -#: contrib/auth/models.py:143 +#: contrib/auth/models.py:144 contrib/comments/models.py:50 +#: contrib/comments/models.py:168 msgid "user" msgstr "usuari" -#: contrib/auth/models.py:144 +#: contrib/auth/models.py:145 msgid "users" msgstr "usuaris" -#: contrib/auth/models.py:300 +#: contrib/auth/models.py:301 msgid "message" msgstr "missatge" -#: contrib/auth/views.py:50 +#: contrib/auth/views.py:56 msgid "Logged out" msgstr "Sessió finalitzada" @@ -1450,6 +1454,16 @@ msgstr "contingut" msgid "Metadata" msgstr "metadades" +#: contrib/comments/feeds.py:13 +#, python-format +msgid "%(site_name)s comments" +msgstr "comentaris de %(site_name)s" + +#: contrib/comments/feeds.py:23 +#, python-format +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" @@ -1479,46 +1493,51 @@ msgstr "" "Si entreu cualsevol cosa en aquest camp el vostre comentari es tractarà com " "a spam" -#: contrib/comments/models.py:23 +#: contrib/comments/models.py:22 contrib/contenttypes/models.py:74 +msgid "content type" +msgstr "tipus de contingut" + +#: contrib/comments/models.py:24 msgid "object ID" msgstr "ID de l'objecte" -#: contrib/comments/models.py:50 +#: contrib/comments/models.py:52 msgid "user's name" msgstr "nom d'usuari" -#: contrib/comments/models.py:51 +#: contrib/comments/models.py:53 msgid "user's email address" msgstr "adreça de correu electrònic del usuari" -#: contrib/comments/models.py:52 +#: contrib/comments/models.py:54 msgid "user's URL" msgstr "URL del usuari" -#: contrib/comments/models.py:54 +#: contrib/comments/models.py:56 contrib/comments/models.py:76 +#: contrib/comments/models.py:169 msgid "comment" msgstr "comentari" -#: contrib/comments/models.py:57 +#: contrib/comments/models.py:59 msgid "date/time submitted" msgstr "data/hora d'enviament" -#: contrib/comments/models.py:59 +#: contrib/comments/models.py:61 msgid "is public" msgstr "és públic" -#: contrib/comments/models.py:60 +#: contrib/comments/models.py:62 msgid "" "Uncheck this box to make the comment effectively disappear from the site." msgstr "" "Desmarqui aquesta casella per fer desaparèixar aquest comentari del lloc web " "de forma efectiva." -#: contrib/comments/models.py:62 +#: contrib/comments/models.py:64 msgid "is removed" msgstr "està eliminat" -#: contrib/comments/models.py:63 +#: contrib/comments/models.py:65 msgid "" "Check this box if the comment is inappropriate. A \"This comment has been " "removed\" message will be displayed instead." @@ -1526,7 +1545,11 @@ msgstr "" "Marqueu aquesta caixa si el comentari no és apropiat. En lloc seu es " "mostrarà \"Aquest comentari ha estat eliminat\" " -#: contrib/comments/models.py:115 +#: contrib/comments/models.py:77 +msgid "comments" +msgstr "comentaris" + +#: contrib/comments/models.py:119 msgid "" "This comment was posted by an authenticated user and thus the name is read-" "only." @@ -1534,7 +1557,7 @@ msgstr "" "Aquest comentari va ser publicat per un usuari autentificat, per això el seu " "nom no es modificable." -#: contrib/comments/models.py:124 +#: contrib/comments/models.py:128 msgid "" "This comment was posted by an authenticated user and thus the email is read-" "only." @@ -1542,7 +1565,7 @@ msgstr "" "Aquest comentari va ser publicat per un usuari autentificat, per això la " "seva adreça de correu electrònic no es pot modificar." -#: contrib/comments/models.py:149 +#: contrib/comments/models.py:153 #, python-format msgid "" "Posted by %(user)s at %(date)s\n" @@ -1557,9 +1580,26 @@ msgstr "" "\n" "http://%(domain)s%(url)s" +# Context problem... waitting for comments from django-i18n +#: contrib/comments/models.py:170 +msgid "flag" +msgstr "marcar" + +#: contrib/comments/models.py:171 +msgid "date" +msgstr "data" + +#: contrib/comments/models.py:181 +msgid "comment flag" +msgstr "marca del comentari" + +#: contrib/comments/models.py:182 +msgid "comment flags" +msgstr "marques de comentari" + #: contrib/comments/templates/comments/approve.html:4 msgid "Approve a comment" -msgstr "aprovar un comentari" +msgstr "Aprovar un comentari" #: contrib/comments/templates/comments/approve.html:7 msgid "Really make this comment public?" @@ -1616,13 +1656,13 @@ msgstr "Marcar" msgid "Thanks for flagging" msgstr "Gràcies per marcar" -#: contrib/comments/templates/comments/form.html:16 -#: contrib/comments/templates/comments/preview.html:31 +#: contrib/comments/templates/comments/form.html:17 +#: contrib/comments/templates/comments/preview.html:32 msgid "Post" msgstr "Publicar" -#: contrib/comments/templates/comments/form.html:17 -#: contrib/comments/templates/comments/preview.html:32 +#: contrib/comments/templates/comments/form.html:18 +#: contrib/comments/templates/comments/preview.html:33 msgid "Preview" msgstr "Vista prèvia" @@ -1668,33 +1708,29 @@ msgid "Thank you for your comment" msgstr "Gràcies pel seu comentari" #: contrib/comments/templates/comments/preview.html:4 -#: contrib/comments/templates/comments/preview.html:12 +#: contrib/comments/templates/comments/preview.html:13 msgid "Preview your comment" msgstr "Previsualitzar el seu comentari" -#: contrib/comments/templates/comments/preview.html:10 +#: contrib/comments/templates/comments/preview.html:11 msgid "Please correct the error below" msgid_plural "Please correct the errors below" msgstr[0] "Si us plau, corregiu l'error mostrat a baix." msgstr[1] "Si us plau, corregiu els errors mostrats a baix." -#: contrib/comments/templates/comments/preview.html:15 +#: contrib/comments/templates/comments/preview.html:16 msgid "Post your comment" msgstr "Enviar el seu comentari" -#: contrib/comments/templates/comments/preview.html:15 +#: contrib/comments/templates/comments/preview.html:16 msgid "or make changes" msgstr "o faci canvis." -#: contrib/contenttypes/models.py:67 +#: contrib/contenttypes/models.py:70 msgid "python model class name" msgstr "nom de la classe del model en python" -#: contrib/contenttypes/models.py:71 -msgid "content type" -msgstr "tipus de contingut" - -#: contrib/contenttypes/models.py:72 +#: contrib/contenttypes/models.py:75 msgid "content types" msgstr "tipus de continguts" @@ -1765,18 +1801,26 @@ msgstr "" "Ho sentim, pero el seu formulari ha expirat. Per favor, continui omplint el " "formulari en aquesta pàgina." -#: contrib/gis/forms/fields.py:14 +#: contrib/gis/forms/fields.py:17 msgid "No geometry value provided." msgstr "No s'ha indicat cap valor de geometria." -#: contrib/gis/forms/fields.py:15 +#: contrib/gis/forms/fields.py:18 msgid "Invalid geometry value." msgstr "Valor de geometria invàlid." -#: contrib/gis/forms/fields.py:16 +#: contrib/gis/forms/fields.py:19 msgid "Invalid geometry type." msgstr "Tipus de geometria invàlid." +#: contrib/gis/forms/fields.py:20 +msgid "" +"An error occurred when transforming the geometry to the SRID of the geometry " +"form field." +msgstr "" +"Ha ocurregut un error al transformar la geometria al SRID de la geometria " +"del camp de formulari." + #: contrib/humanize/templatetags/humanize.py:19 msgid "th" msgstr "rt" @@ -2112,51 +2156,51 @@ msgstr "Regió Bohèmia Central" #: contrib/localflavor/cz/cz_regions.py:10 msgid "South Bohemian Region" -msgstr "Regió Bohemia Sur" +msgstr "Regió Bohèmia Sur" #: contrib/localflavor/cz/cz_regions.py:11 msgid "Pilsen Region" -msgstr "" +msgstr "Regió Pilsen" #: contrib/localflavor/cz/cz_regions.py:12 msgid "Carlsbad Region" -msgstr "" +msgstr "Regió Carlsbad" #: contrib/localflavor/cz/cz_regions.py:13 msgid "Usti Region" -msgstr "" +msgstr "Regió Usti" #: contrib/localflavor/cz/cz_regions.py:14 msgid "Liberec Region" -msgstr "" +msgstr "Regió Liberec" #: contrib/localflavor/cz/cz_regions.py:15 msgid "Hradec Region" -msgstr "" +msgstr "Regió Hradec" #: contrib/localflavor/cz/cz_regions.py:16 msgid "Pardubice Region" -msgstr "" +msgstr "Regió Pardubice" #: contrib/localflavor/cz/cz_regions.py:17 msgid "Vysocina Region" -msgstr "" +msgstr "Regió Vysocina" #: contrib/localflavor/cz/cz_regions.py:18 msgid "South Moravian Region" -msgstr "" +msgstr "Regió Moràvia Sur" #: contrib/localflavor/cz/cz_regions.py:19 msgid "Olomouc Region" -msgstr "" +msgstr "Regió Olomouc" #: contrib/localflavor/cz/cz_regions.py:20 msgid "Zlin Region" -msgstr "" +msgstr "Regió Zlin" #: contrib/localflavor/cz/cz_regions.py:21 msgid "Moravian-Silesian Region" -msgstr "" +msgstr "Regió Moràvia-Silesiana" #: contrib/localflavor/cz/forms.py:27 contrib/localflavor/sk/forms.py:30 msgid "Enter a postal code in the format XXXXX or XXX XX." @@ -2164,11 +2208,13 @@ msgstr "Introduïu un codi postal en el format XXXXX or XXX XX." #: contrib/localflavor/cz/forms.py:47 msgid "Enter a birth number in the format XXXXXX/XXXX or XXXXXXXXXX." -msgstr "Introduïu un número de naixement en el format XXXXXX/XXXX o XXXXXXXXXX." +msgstr "" +"Introduïu un número de naixement en el format XXXXXX/XXXX o XXXXXXXXXX." #: contrib/localflavor/cz/forms.py:48 msgid "Invalid optional parameter Gender, valid values are 'f' and 'm'" -msgstr "El paràmetre opcional 'Gènere' és invàlid, els valors vàlids son 'f' i 'm'." +msgstr "" +"El paràmetre opcional 'Gènere' és invàlid, els valors vàlids son 'f' i 'm'." #: contrib/localflavor/cz/forms.py:49 msgid "Enter a valid birth number." @@ -3004,16 +3050,16 @@ msgstr "Introduïu un número NIP en el format XXX-XXX-XX-XX o XX-XX-XXX-XXX." msgid "Wrong checksum for the Tax Number (NIP)." msgstr "Validació invàlida del número tributari (NIP)." -#: contrib/localflavor/pl/forms.py:111 -msgid "National Business Register Number (REGON) consists of 7 or 9 digits." +#: 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 7 o 9 dígits." +"El número nacional de registre de negocis (REGON) consisteix en 9 o 14 dígits." -#: contrib/localflavor/pl/forms.py:112 +#: contrib/localflavor/pl/forms.py:110 msgid "Wrong checksum for the National Business Register Number (REGON)." msgstr "Validació invàlida del número nacional de registre de negocis." -#: contrib/localflavor/pl/forms.py:155 +#: contrib/localflavor/pl/forms.py:148 msgid "Enter a postal code in the format XX-XXX." msgstr "Introduïu un codi postal en el format XX-XXX." @@ -3851,60 +3897,60 @@ msgstr "nom per mostrar" msgid "sites" msgstr "llocs" -#: db/models/fields/__init__.py:356 db/models/fields/__init__.py:700 +#: db/models/fields/__init__.py:356 db/models/fields/__init__.py:710 msgid "This value must be an integer." msgstr "Aquest valor ha de ser un enter." -#: db/models/fields/__init__.py:387 +#: db/models/fields/__init__.py:388 msgid "This value must be either True or False." msgstr "Aquest valor ha de ser True (Veritat) o False (Fals)" -#: db/models/fields/__init__.py:420 +#: db/models/fields/__init__.py:427 msgid "This field cannot be null." msgstr "Aquest camp no pot ser nul." -#: db/models/fields/__init__.py:436 +#: db/models/fields/__init__.py:443 msgid "Enter only digits separated by commas." msgstr "Introduïu només dígits separats per comes." -#: db/models/fields/__init__.py:467 +#: db/models/fields/__init__.py:474 msgid "Enter a valid date in YYYY-MM-DD format." msgstr "Introduïu una data vàlida en el forma AAAA-MM-DD." -#: db/models/fields/__init__.py:476 +#: db/models/fields/__init__.py:483 #, python-format msgid "Invalid date: %s" msgstr "Data invàlida: %s" -#: db/models/fields/__init__.py:540 db/models/fields/__init__.py:558 +#: db/models/fields/__init__.py:547 db/models/fields/__init__.py:565 msgid "Enter a valid date/time in YYYY-MM-DD HH:MM[:ss[.uuuuuu]] format." msgstr "" "Introduïu un data/hora vàlida en format YYYY-MM-DD HH:MM[:ss[.uuuuuu]]." -#: db/models/fields/__init__.py:594 +#: db/models/fields/__init__.py:601 msgid "This value must be a decimal number." msgstr "Aquest valor ha de ser un número decimal." -#: db/models/fields/__init__.py:676 +#: db/models/fields/__init__.py:686 msgid "This value must be a float." msgstr "Aquest valor ha de ser un número amb punt de coma flotant." -#: db/models/fields/__init__.py:736 +#: db/models/fields/__init__.py:746 msgid "This value must be either None, True or False." msgstr "Aquest valor ha de ser None (Cap), True (Veritat) o False (Fals)" -#: db/models/fields/__init__.py:839 db/models/fields/__init__.py:853 +#: db/models/fields/__init__.py:849 db/models/fields/__init__.py:863 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:779 +#: db/models/fields/related.py:816 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:857 +#: db/models/fields/related.py:894 #, python-format msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid." msgid_plural "" @@ -3971,11 +4017,11 @@ msgstr "Assegureu-vos de que no hi ha més de %s decimals." 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:850 +#: forms/fields.py:288 forms/fields.py:863 msgid "Enter a valid date." msgstr "Introduïu una data vàlida." -#: forms/fields.py:322 forms/fields.py:851 +#: forms/fields.py:322 forms/fields.py:864 msgid "Enter a valid time." msgstr "Introduïu una hora vàlida." @@ -3996,7 +4042,15 @@ msgstr "No s'ha enviat cap fitxer." msgid "The submitted file is empty." msgstr "El fitxer enviat està buit." -#: forms/fields.py:478 +#: forms/fields.py:450 +#, 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)." + +#: forms/fields.py:483 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." @@ -4004,106 +4058,139 @@ msgstr "" "Envieu una imatge vàlida. El fitxer que heu enviat no era una imatge o " "estava corrupte." -#: forms/fields.py:539 +#: forms/fields.py:544 msgid "Enter a valid URL." msgstr "Introduïu una URL vàlida." -#: forms/fields.py:540 +#: forms/fields.py:545 msgid "This URL appears to be a broken link." msgstr "Aquesta URL sembla ser un enllaç trencat." -#: forms/fields.py:619 forms/fields.py:697 +#: forms/fields.py:625 forms/fields.py:703 #, 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:698 forms/fields.py:759 forms/models.py:729 +#: forms/fields.py:704 forms/fields.py:765 forms/models.py:1003 msgid "Enter a list of values." msgstr "Introduïu una llista de valors." -#: forms/fields.py:879 +#: forms/fields.py:892 msgid "Enter a valid IPv4 address." msgstr "Introduïu una adreça IPv4 vàlida." -#: forms/fields.py:889 +#: forms/fields.py:902 msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" "Introduïu un 'slug' vàlid, consistent en lletres, números, guions o guions " "baixos." -#: forms/formsets.py:247 forms/formsets.py:249 +#: forms/formsets.py:271 forms/formsets.py:273 msgid "Order" msgstr "Ordre" -#: forms/models.py:289 forms/models.py:298 +#: forms/models.py:367 +#, 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 #, 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:602 +#: forms/models.py:594 +#, 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 +#, 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." + +#: forms/models.py:604 +#, 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." + +#: forms/models.py:612 +msgid "Please correct the duplicate values below." +msgstr "Si us plau, corregiu els valors duplicats a baix." + +#: forms/models.py:867 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:659 +#: forms/models.py:930 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:730 +#: forms/models.py:1004 #, 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." -#: template/defaultfilters.py:751 +#: forms/models.py:1006 +#, 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." + +#: template/defaultfilters.py:767 msgid "yes,no,maybe" msgstr "si,no,potser" -#: template/defaultfilters.py:782 +#: template/defaultfilters.py:798 #, python-format msgid "%(size)d byte" msgid_plural "%(size)d bytes" msgstr[0] "%(size)d byte" msgstr[1] "%(size)d bytes" -#: template/defaultfilters.py:784 +#: template/defaultfilters.py:800 #, python-format msgid "%.1f KB" msgstr "%.1f KB" -#: template/defaultfilters.py:786 +#: template/defaultfilters.py:802 #, python-format msgid "%.1f MB" msgstr "%.1f MB" -#: template/defaultfilters.py:787 +#: template/defaultfilters.py:803 #, python-format msgid "%.1f GB" msgstr "%.1f GB" -#: utils/dateformat.py:41 +#: utils/dateformat.py:42 msgid "p.m." msgstr "p.m." -#: utils/dateformat.py:42 +#: utils/dateformat.py:43 msgid "a.m." msgstr "a.m." -#: utils/dateformat.py:47 +#: utils/dateformat.py:48 msgid "PM" msgstr "PM" -#: utils/dateformat.py:48 +#: utils/dateformat.py:49 msgid "AM" msgstr "AM" -#: utils/dateformat.py:97 +#: utils/dateformat.py:98 msgid "midnight" msgstr "mitja nit" -#: utils/dateformat.py:99 +#: utils/dateformat.py:100 msgid "noon" msgstr "mig dia" @@ -4327,16 +4414,16 @@ msgid_plural "minutes" msgstr[0] "minut" msgstr[1] "minuts" -#: utils/timesince.py:43 +#: utils/timesince.py:45 msgid "minutes" msgstr "minuts" -#: utils/timesince.py:48 +#: utils/timesince.py:50 #, python-format msgid "%(number)d %(type)s" msgstr "%(number)d %(type)s" -#: utils/timesince.py:54 +#: utils/timesince.py:56 #, python-format msgid ", %(number)d %(type)s" msgstr ", %(number)d %(type)s" @@ -4371,3 +4458,9 @@ msgstr "El/la %(verbose_name)s s'ha actualtzat amb èxit." #, python-format msgid "The %(verbose_name)s was deleted." msgstr "El %(verbose_name)s s'ha eliminat." + +#, fuzzy +#~ msgid "verbose_name" +#~ msgid_plural "verbose_name_plural" +#~ msgstr[0] "verbose_name" +#~ msgstr[1] "verbose_name_plural" diff --git a/django/conf/locale/es/LC_MESSAGES/django.mo b/django/conf/locale/es/LC_MESSAGES/django.mo index 831964a5ae..a8bd5f985d 100644 Binary files a/django/conf/locale/es/LC_MESSAGES/django.mo and b/django/conf/locale/es/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/es/LC_MESSAGES/django.po b/django/conf/locale/es/LC_MESSAGES/django.po index b02ab9b7ff..fb5eec0c43 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-03-24 13:00+0100\n" -"PO-Revision-Date: 2009-03-24 13:26+0100\n" +"POT-Creation-Date: 2009-07-07 15:15+0200\n" +"PO-Revision-Date: 2009-07-07 15:22+0200\n" "Last-Translator: Django Spanish Team Language-" "Team: Django Spanish Team MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -217,6 +217,20 @@ msgstr "chino simplificado" msgid "Traditional Chinese" msgstr "chino tradicional" +#: contrib/admin/actions.py:60 +#, python-format +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 +msgid "Are you sure?" +msgstr "¿Está seguro?" + +#: contrib/admin/actions.py:85 +#, python-format +msgid "Delete selected %(verbose_name_plural)s" +msgstr "Eliminar %(verbose_name_plural)s seleccionado/s" + #: contrib/admin/filterspecs.py:44 #, python-format msgid "" @@ -251,15 +265,15 @@ msgstr "Este mes" msgid "This year" msgstr "Este año" -#: contrib/admin/filterspecs.py:147 forms/widgets.py:413 +#: contrib/admin/filterspecs.py:147 forms/widgets.py:434 msgid "Yes" msgstr "Sí" -#: contrib/admin/filterspecs.py:147 forms/widgets.py:413 +#: contrib/admin/filterspecs.py:147 forms/widgets.py:434 msgid "No" msgstr "No" -#: contrib/admin/filterspecs.py:154 forms/widgets.py:413 +#: contrib/admin/filterspecs.py:154 forms/widgets.py:434 msgid "Unknown" msgstr "Desconocido" @@ -295,117 +309,104 @@ msgstr "entrada de registro" msgid "log entries" msgstr "entradas de registro" -#: contrib/admin/options.py:131 contrib/admin/options.py:145 +#: contrib/admin/options.py:133 contrib/admin/options.py:147 msgid "None" msgstr "Ninguno" -#: contrib/admin/options.py:498 -#, python-format -msgid "Successfully deleted %(count)d %(items)s." -msgstr "Eliminado/s %(count)d %(items)s satisfactoriamente." - -#: contrib/admin/options.py:505 contrib/admin/options.py:1012 -msgid "Are you sure?" -msgstr "¿Está seguro?" - -#: contrib/admin/options.py:523 -#, python-format -msgid "Delete selected %(verbose_name_plural)s" -msgstr "Eliminar %(verbose_name_plural)s seleccionado/s" - -#: contrib/admin/options.py:531 +#: contrib/admin/options.py:519 #, python-format msgid "Changed %s." msgstr "Modificado/a %s." -#: contrib/admin/options.py:531 contrib/admin/options.py:541 -#: contrib/comments/templates/comments/preview.html:15 forms/models.py:296 +#: 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 msgid "and" msgstr "y" -#: contrib/admin/options.py:536 +#: contrib/admin/options.py:524 #, python-format msgid "Added %(name)s \"%(object)s\"." msgstr "Añadido/a \"%(object)s\" %(name)s." -#: contrib/admin/options.py:540 +#: contrib/admin/options.py:528 #, python-format msgid "Changed %(list)s for %(name)s \"%(object)s\"." msgstr "Modificados %(list)s para \"%(object)s\" %(name)s." -#: contrib/admin/options.py:545 +#: contrib/admin/options.py:533 #, python-format msgid "Deleted %(name)s \"%(object)s\"." msgstr "Eliminado/a \"%(object)s\" %(name)s." -#: contrib/admin/options.py:549 +#: contrib/admin/options.py:537 msgid "No fields changed." msgstr "No ha cambiado ningún campo." -#: contrib/admin/options.py:610 contrib/auth/admin.py:67 +#: contrib/admin/options.py:598 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:614 contrib/admin/options.py:647 +#: contrib/admin/options.py:602 contrib/admin/options.py:635 #: contrib/auth/admin.py:75 msgid "You may edit it again below." msgstr "Puede editarlo de nuevo abajo." -#: contrib/admin/options.py:624 contrib/admin/options.py:657 +#: contrib/admin/options.py:612 contrib/admin/options.py:645 #, python-format msgid "You may add another %s below." msgstr "Puede añadir otro %s abajo." -#: contrib/admin/options.py:645 +#: contrib/admin/options.py:633 #, 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:653 +#: contrib/admin/options.py:641 #, 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:774 +#: contrib/admin/options.py:772 #, python-format msgid "Add %s" msgstr "Añadir %s" -#: contrib/admin/options.py:805 contrib/admin/options.py:990 +#: contrib/admin/options.py:803 contrib/admin/options.py:1003 #, 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:862 +#: contrib/admin/options.py:860 #, python-format msgid "Change %s" msgstr "Modificar %s" -#: contrib/admin/options.py:894 +#: contrib/admin/options.py:904 msgid "Database error" msgstr "Error en la base de datos" -#: contrib/admin/options.py:930 +#: contrib/admin/options.py:940 #, 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:1005 +#: contrib/admin/options.py:1018 #, 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:1041 +#: contrib/admin/options.py:1054 #, python-format msgid "Change history: %s" msgstr "Histórico de modificaciones: %s" -#: contrib/admin/sites.py:15 contrib/admin/views/decorators.py:14 +#: contrib/admin/sites.py:20 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-" @@ -414,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:250 contrib/admin/views/decorators.py:40 +#: contrib/admin/sites.py:278 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:257 contrib/admin/views/decorators.py:47 +#: contrib/admin/sites.py:285 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." @@ -426,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:273 contrib/admin/sites.py:279 +#: contrib/admin/sites.py:301 contrib/admin/sites.py:307 #: 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:276 contrib/admin/views/decorators.py:62 +#: contrib/admin/sites.py:304 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:336 +#: contrib/admin/sites.py:360 msgid "Site administration" msgstr "Sitio administrativo" -#: contrib/admin/sites.py:349 contrib/admin/templates/admin/login.html:26 +#: contrib/admin/sites.py:373 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:396 +#: contrib/admin/sites.py:417 #, python-format msgid "%s administration" msgstr "Administración de %s" @@ -463,33 +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/util.py:222 -msgid "verbose_name" -msgid_plural "verbose_name_plural" -msgstr[0] "verbose_name" -msgstr[1] "verbose_name_plural" - -#: contrib/admin/widgets.py:70 +#: contrib/admin/widgets.py:71 msgid "Date:" msgstr "Fecha:" -#: contrib/admin/widgets.py:70 +#: contrib/admin/widgets.py:71 msgid "Time:" msgstr "Hora:" -#: contrib/admin/widgets.py:94 +#: contrib/admin/widgets.py:95 msgid "Currently:" msgstr "Actualmente:" -#: contrib/admin/widgets.py:94 +#: contrib/admin/widgets.py:95 msgid "Change:" msgstr "Modificar:" -#: contrib/admin/widgets.py:123 +#: contrib/admin/widgets.py:124 msgid "Lookup" msgstr "Buscar" -#: contrib/admin/widgets.py:230 +#: contrib/admin/widgets.py:236 msgid "Add Another" msgstr "Añadir otro" @@ -504,9 +499,9 @@ 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:33 +#: contrib/admin/templates/admin/base.html:31 #: contrib/admin/templates/admin/change_form.html:17 -#: contrib/admin/templates/admin/change_list.html:20 +#: contrib/admin/templates/admin/change_list.html:25 #: contrib/admin/templates/admin/delete_confirmation.html:6 #: contrib/admin/templates/admin/delete_selected_confirmation.html:6 #: contrib/admin/templates/admin/invalid_setup.html:4 @@ -549,7 +544,6 @@ msgid "Run the selected action" msgstr "Ejecutar la acción seleccionada" #: contrib/admin/templates/admin/actions.html:4 -#: contrib/admin/templates/admin/search_form.html:8 msgid "Go" msgstr "Ir" @@ -559,26 +553,26 @@ msgstr "Ir" msgid "%(name)s" msgstr "%(name)s" -#: contrib/admin/templates/admin/base.html:28 +#: contrib/admin/templates/admin/base.html:26 msgid "Welcome," msgstr "Bienvenido/a," -#: contrib/admin/templates/admin/base.html:28 +#: contrib/admin/templates/admin/base.html:26 #: 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:28 -#: contrib/admin/templates/admin/auth/user/change_password.html:13 -#: contrib/admin/templates/admin/auth/user/change_password.html:46 +#: contrib/admin/templates/admin/base.html:26 +#: 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 #: contrib/admin/templates/registration/password_change_form.html:3 msgid "Change password" msgstr "Cambiar contraseña" -#: contrib/admin/templates/admin/base.html:28 +#: contrib/admin/templates/admin/base.html:26 #: contrib/admin/templates/registration/password_change_done.html:3 #: contrib/admin/templates/registration/password_change_form.html:3 msgid "Log out" @@ -609,24 +603,24 @@ msgid "View on site" msgstr "Ver en el sitio" #: contrib/admin/templates/admin/change_form.html:38 -#: contrib/admin/templates/admin/change_list.html:49 -#: contrib/admin/templates/admin/auth/user/change_password.html:22 +#: contrib/admin/templates/admin/change_list.html:54 +#: contrib/admin/templates/admin/auth/user/change_password.html:23 msgid "Please correct the error below." msgid_plural "Please correct the errors below." msgstr[0] "Por favor, corrija el siguiente error." msgstr[1] "Por favor, corrija los siguientes errores." -#: contrib/admin/templates/admin/change_list.html:41 +#: contrib/admin/templates/admin/change_list.html:46 #, python-format msgid "Add %(name)s" msgstr "Añadir %(name)s" -#: contrib/admin/templates/admin/change_list.html:60 +#: contrib/admin/templates/admin/change_list.html:65 msgid "Filter" msgstr "Filtro" #: contrib/admin/templates/admin/delete_confirmation.html:10 -#: contrib/admin/templates/admin/submit_line.html:4 forms/formsets.py:251 +#: contrib/admin/templates/admin/submit_line.html:4 forms/formsets.py:275 msgid "Delete" msgstr "Eliminar" @@ -666,9 +660,9 @@ msgid "" "your account doesn't have permission to delete the following types of " "objects:" msgstr "" -"Eliminar el %(object_name)s provocaría la eliminación " -"de objetos relacionados, pero su cuenta no tiene permiso para borrar los " -"siguientes tipos de objetos:" +"Eliminar el %(object_name)s provocaría la eliminación de objetos " +"relacionados, pero su cuenta no tiene permiso para borrar los siguientes " +"tipos de objetos:" #: contrib/admin/templates/admin/delete_selected_confirmation.html:22 #, python-format @@ -676,8 +670,8 @@ 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:" msgstr "" -"¿Está seguro de que quiere borrar los %(object_name)s? " -"Los siguientes objetos y sus elementos relacionados serán eliminados:" +"¿Está seguro de que quiere borrar los %(object_name)s? Los siguientes " +"objetos y sus elementos relacionados serán eliminados:" #: contrib/admin/templates/admin/filter.html:2 #, python-format @@ -709,6 +703,10 @@ msgstr "Mis acciones" msgid "None available" msgstr "Ninguno disponible" +#: contrib/admin/templates/admin/index.html:72 +msgid "Unknown content" +msgstr "Contenido desconocido" + #: contrib/admin/templates/admin/invalid_setup.html:7 msgid "" "Something's wrong with your database installation. Make sure the appropriate " @@ -757,6 +755,15 @@ msgstr "" msgid "Show all" msgstr "Mostrar todo" +#: contrib/admin/templates/admin/pagination.html:11 +#: contrib/admin/templates/admin/submit_line.html:3 +msgid "Save" +msgstr "Grabar" + +#: contrib/admin/templates/admin/search_form.html:8 +msgid "Search" +msgstr "Buscar" + #: contrib/admin/templates/admin/search_form.html:10 #, python-format msgid "1 result" @@ -769,10 +776,6 @@ msgstr[1] "%(counter)s resultados" msgid "%(full_result_count)s total" msgstr "%(full_result_count)s total" -#: contrib/admin/templates/admin/submit_line.html:3 -msgid "Save" -msgstr "Grabar" - #: contrib/admin/templates/admin/submit_line.html:5 msgid "Save as new" msgstr "Grabar como nuevo" @@ -799,23 +802,23 @@ msgid "Username" msgstr "Nombre de usuario" #: contrib/admin/templates/admin/auth/user/add_form.html:20 -#: contrib/admin/templates/admin/auth/user/change_password.html:33 +#: contrib/admin/templates/admin/auth/user/change_password.html:34 #: contrib/auth/forms.py:17 contrib/auth/forms.py:60 contrib/auth/forms.py:185 msgid "Password" msgstr "Contraseña" #: contrib/admin/templates/admin/auth/user/add_form.html:26 -#: contrib/admin/templates/admin/auth/user/change_password.html:39 +#: contrib/admin/templates/admin/auth/user/change_password.html:40 #: contrib/auth/forms.py:186 msgid "Password (again)" msgstr "Contraseña (de nuevo)" #: contrib/admin/templates/admin/auth/user/add_form.html:27 -#: contrib/admin/templates/admin/auth/user/change_password.html:40 +#: contrib/admin/templates/admin/auth/user/change_password.html:41 msgid "Enter the same password as above, for verification." msgstr "Introduzca la misma contraseña que arriba, para verificación." -#: contrib/admin/templates/admin/auth/user/change_password.html:26 +#: contrib/admin/templates/admin/auth/user/change_password.html:27 #, python-format msgid "Enter a new password for the user %(username)s." msgstr "" @@ -1037,114 +1040,115 @@ msgstr "" 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:228 -#: contrib/admindocs/views.py:242 contrib/admindocs/views.py:247 +#: 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 msgid "model:" msgstr "modelo:" -#: contrib/admindocs/views.py:237 +#: contrib/admindocs/views.py:221 contrib/admindocs/views.py:253 #, 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:242 +#: contrib/admindocs/views.py:225 contrib/admindocs/views.py:258 #, python-format msgid "all %s" msgstr "todo %s" -#: contrib/admindocs/views.py:247 +#: contrib/admindocs/views.py:230 contrib/admindocs/views.py:263 #, python-format msgid "number of %s" msgstr "número de %s" -#: contrib/admindocs/views.py:252 +#: contrib/admindocs/views.py:268 #, python-format msgid "Fields on %s objects" msgstr "Campos en %s objetos" -#: 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: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 msgid "Integer" msgstr "Entero" -#: contrib/admindocs/views.py:316 +#: contrib/admindocs/views.py:332 msgid "Boolean (Either True or False)" msgstr "Booleano (Verdadero o Falso)" -#: contrib/admindocs/views.py:317 contrib/admindocs/views.py:336 +#: contrib/admindocs/views.py:333 contrib/admindocs/views.py:352 #, python-format msgid "String (up to %(max_length)s)" msgstr "Cadena (máximo %(max_length)s)" -#: contrib/admindocs/views.py:318 +#: contrib/admindocs/views.py:334 msgid "Comma-separated integers" msgstr "Enteros separados por comas" -#: contrib/admindocs/views.py:319 +#: contrib/admindocs/views.py:335 msgid "Date (without time)" msgstr "Fecha (sin hora)" -#: contrib/admindocs/views.py:320 +#: contrib/admindocs/views.py:336 msgid "Date (with time)" msgstr "Fecha (con hora)" -#: contrib/admindocs/views.py:321 +#: contrib/admindocs/views.py:337 msgid "Decimal number" msgstr "Número decimal" -#: contrib/admindocs/views.py:322 +#: contrib/admindocs/views.py:338 msgid "E-mail address" msgstr "Dirección de correo electrónico" -#: contrib/admindocs/views.py:323 contrib/admindocs/views.py:324 -#: contrib/admindocs/views.py:327 +#: contrib/admindocs/views.py:339 contrib/admindocs/views.py:340 +#: contrib/admindocs/views.py:343 msgid "File path" msgstr "Ruta de fichero" -#: contrib/admindocs/views.py:325 +#: contrib/admindocs/views.py:341 msgid "Floating point number" msgstr "Número en coma flotante" -#: contrib/admindocs/views.py:329 contrib/comments/models.py:58 +#: contrib/admindocs/views.py:345 contrib/comments/models.py:60 msgid "IP address" msgstr "Dirección IP" -#: contrib/admindocs/views.py:331 +#: contrib/admindocs/views.py:347 msgid "Boolean (Either True, False or None)" msgstr "Booleano (Verdadero, Falso o Nulo)" -#: contrib/admindocs/views.py:332 +#: contrib/admindocs/views.py:348 msgid "Relation to parent model" msgstr "Relación con el modelo padre" -#: contrib/admindocs/views.py:333 +#: contrib/admindocs/views.py:349 msgid "Phone number" msgstr "Número de teléfono" -#: contrib/admindocs/views.py:338 +#: contrib/admindocs/views.py:354 msgid "Text" msgstr "Texto" -#: contrib/admindocs/views.py:339 +#: contrib/admindocs/views.py:355 msgid "Time" msgstr "Hora" -#: contrib/admindocs/views.py:340 contrib/comments/forms.py:95 +#: contrib/admindocs/views.py:356 contrib/comments/forms.py:95 #: contrib/comments/templates/comments/moderation_queue.html:37 #: contrib/flatpages/admin.py:8 contrib/flatpages/models.py:7 msgid "URL" msgstr "URL" -#: contrib/admindocs/views.py:341 +#: contrib/admindocs/views.py:357 msgid "U.S. state (two uppercase letters)" msgstr "Estado de los EEUU (dos letras mayúsculas)" -#: contrib/admindocs/views.py:342 +#: contrib/admindocs/views.py:358 msgid "XML text" msgstr "Texto XML" -#: contrib/admindocs/views.py:368 +#: contrib/admindocs/views.py:384 #, python-format msgid "%s does not appear to be a urlpattern object" msgstr "%s no parece ser un objeto urlpattern" @@ -1248,7 +1252,7 @@ msgid "Change password: %s" msgstr "Cambiar contraseña: %s" #: contrib/auth/forms.py:15 contrib/auth/forms.py:48 -#: contrib/auth/models.py:127 +#: contrib/auth/models.py:128 msgid "" "Required. 30 characters or fewer. Alphanumeric characters only (letters, " "digits and underscores)." @@ -1340,31 +1344,31 @@ msgstr "permisos" msgid "group" msgstr "grupo" -#: contrib/auth/models.py:91 contrib/auth/models.py:137 +#: contrib/auth/models.py:91 contrib/auth/models.py:138 msgid "groups" msgstr "grupos" -#: contrib/auth/models.py:127 +#: contrib/auth/models.py:128 msgid "username" msgstr "nombre de usuario" -#: contrib/auth/models.py:128 +#: contrib/auth/models.py:129 msgid "first name" msgstr "nombre propio" -#: contrib/auth/models.py:129 +#: contrib/auth/models.py:130 msgid "last name" msgstr "apellidos" -#: contrib/auth/models.py:130 +#: contrib/auth/models.py:131 msgid "e-mail address" msgstr "dirección de correo electrónico" -#: contrib/auth/models.py:131 +#: contrib/auth/models.py:132 msgid "password" msgstr "contraseña" -#: contrib/auth/models.py:131 +#: contrib/auth/models.py:132 msgid "" "Use '[algo]$[salt]$[hexdigest]' or use the change " "password form." @@ -1372,19 +1376,19 @@ msgstr "" "Use'[algo]$[sal]$[hash hexadecimal]' o use el " "formulario para cambiar la contraseña." -#: contrib/auth/models.py:132 +#: contrib/auth/models.py:133 msgid "staff status" msgstr "es staff" -#: contrib/auth/models.py:132 +#: contrib/auth/models.py:133 msgid "Designates whether the user can log into this admin site." msgstr "Indica si el usuario puede entrar en este sitio de administración." -#: contrib/auth/models.py:133 +#: contrib/auth/models.py:134 msgid "active" msgstr "activo" -#: contrib/auth/models.py:133 +#: contrib/auth/models.py:134 msgid "" "Designates whether this user should be treated as active. Unselect this " "instead of deleting accounts." @@ -1392,11 +1396,11 @@ msgstr "" "Indica si el usuario puede ser tratado como activo. Desmarque esta opción en " "lugar de borrar la cuenta." -#: contrib/auth/models.py:134 +#: contrib/auth/models.py:135 msgid "superuser status" msgstr "es superusuario" -#: contrib/auth/models.py:134 +#: contrib/auth/models.py:135 msgid "" "Designates that this user has all permissions without explicitly assigning " "them." @@ -1404,15 +1408,15 @@ msgstr "" "Indica que este usuario tiene todos los permisos sin asignárselos " "explícitamente." -#: contrib/auth/models.py:135 +#: contrib/auth/models.py:136 msgid "last login" msgstr "último inicio de sesión" -#: contrib/auth/models.py:136 +#: contrib/auth/models.py:137 msgid "date joined" msgstr "fecha de alta" -#: contrib/auth/models.py:138 +#: contrib/auth/models.py:139 msgid "" "In addition to the permissions manually assigned, this user will also get " "all permissions granted to each group he/she is in." @@ -1420,23 +1424,24 @@ msgstr "" "Además de los permisos asignados manualmente, este usuario también tendrá " "todos los permisos de los grupos en los que esté." -#: contrib/auth/models.py:139 +#: contrib/auth/models.py:140 msgid "user permissions" msgstr "permisos de usuario" -#: contrib/auth/models.py:143 +#: contrib/auth/models.py:144 contrib/comments/models.py:50 +#: contrib/comments/models.py:168 msgid "user" msgstr "usuario" -#: contrib/auth/models.py:144 +#: contrib/auth/models.py:145 msgid "users" msgstr "usuarios" -#: contrib/auth/models.py:300 +#: contrib/auth/models.py:301 msgid "message" msgstr "mensaje" -#: contrib/auth/views.py:50 +#: contrib/auth/views.py:56 msgid "Logged out" msgstr "Sesión terminada" @@ -1452,10 +1457,20 @@ msgstr "contenido" msgid "Metadata" msgstr "metadatos" +#: contrib/comments/feeds.py:13 +#, python-format +msgid "%(site_name)s comments" +msgstr "comentarios de %(site_name)s" + +#: contrib/comments/feeds.py:23 +#, python-format +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" +msgstr "Nombre" #: contrib/comments/forms.py:94 msgid "Email address" @@ -1464,7 +1479,7 @@ msgstr "dirección de correo electrónico" #: contrib/comments/forms.py:96 #: contrib/comments/templates/comments/moderation_queue.html:35 msgid "Comment" -msgstr "comentario" +msgstr "Comentario" #: contrib/comments/forms.py:173 #, python-format @@ -1478,46 +1493,51 @@ msgid "" "If you enter anything in this field your comment will be treated as spam" msgstr "Si introduce algo en este campo su comentario será tratado como spam" -#: contrib/comments/models.py:23 +#: contrib/comments/models.py:22 contrib/contenttypes/models.py:74 +msgid "content type" +msgstr "tipo de contenido" + +#: contrib/comments/models.py:24 msgid "object ID" msgstr "ID de objeto" -#: contrib/comments/models.py:50 +#: contrib/comments/models.py:52 msgid "user's name" msgstr "nombre del usuario" -#: contrib/comments/models.py:51 +#: contrib/comments/models.py:53 msgid "user's email address" msgstr "dirección de correo electrónico del usuario" -#: contrib/comments/models.py:52 +#: contrib/comments/models.py:54 msgid "user's URL" msgstr "URL del usuario" -#: contrib/comments/models.py:54 +#: contrib/comments/models.py:56 contrib/comments/models.py:76 +#: contrib/comments/models.py:169 msgid "comment" msgstr "comentario" -#: contrib/comments/models.py:57 +#: contrib/comments/models.py:59 msgid "date/time submitted" msgstr "fecha/hora de envío" -#: contrib/comments/models.py:59 +#: contrib/comments/models.py:61 msgid "is public" msgstr "es público" -#: contrib/comments/models.py:60 +#: contrib/comments/models.py:62 msgid "" "Uncheck this box to make the comment effectively disappear from the site." msgstr "" "Desmarque esta casilla para hacer desaparecer el comentario del sitio web de " "forma efectiva." -#: contrib/comments/models.py:62 +#: contrib/comments/models.py:64 msgid "is removed" msgstr "está eliminado" -#: contrib/comments/models.py:63 +#: contrib/comments/models.py:65 msgid "" "Check this box if the comment is inappropriate. A \"This comment has been " "removed\" message will be displayed instead." @@ -1525,7 +1545,11 @@ msgstr "" "Marque esta opción si el comentario es inapropiado. En su lugar se mostrará " "el mensaje \"Este comentario ha sido eliminado\"." -#: contrib/comments/models.py:115 +#: contrib/comments/models.py:77 +msgid "comments" +msgstr "comentarios" + +#: contrib/comments/models.py:119 msgid "" "This comment was posted by an authenticated user and thus the name is read-" "only." @@ -1533,7 +1557,7 @@ msgstr "" "Este comentario ha sido enviado por un usuario autentificado: de modo que su " "nombre no es modificable." -#: contrib/comments/models.py:124 +#: contrib/comments/models.py:128 msgid "" "This comment was posted by an authenticated user and thus the email is read-" "only." @@ -1541,7 +1565,7 @@ msgstr "" "Este comentario ha sido colocado por un usuario autentificado: de modo que " "su dirección de correo electrónico no es modificable." -#: contrib/comments/models.py:149 +#: contrib/comments/models.py:153 #, python-format msgid "" "Posted by %(user)s at %(date)s\n" @@ -1556,6 +1580,22 @@ msgstr "" "\n" "http://%(domain)s%(url)s" +#: contrib/comments/models.py:170 +msgid "flag" +msgstr "marcar" + +#: contrib/comments/models.py:171 +msgid "date" +msgstr "fecha" + +#: contrib/comments/models.py:181 +msgid "comment flag" +msgstr "marca de comentario" + +#: contrib/comments/models.py:182 +msgid "comment flags" +msgstr "marcas de comentario" + #: contrib/comments/templates/comments/approve.html:4 msgid "Approve a comment" msgstr "Aprovar un comentario" @@ -1615,13 +1655,13 @@ msgstr "Marcar" msgid "Thanks for flagging" msgstr "Graciar por marcar" -#: contrib/comments/templates/comments/form.html:16 -#: contrib/comments/templates/comments/preview.html:31 +#: contrib/comments/templates/comments/form.html:17 +#: contrib/comments/templates/comments/preview.html:32 msgid "Post" msgstr "Enviar" -#: contrib/comments/templates/comments/form.html:17 -#: contrib/comments/templates/comments/preview.html:32 +#: contrib/comments/templates/comments/form.html:18 +#: contrib/comments/templates/comments/preview.html:33 msgid "Preview" msgstr "Previsualizar" @@ -1667,33 +1707,29 @@ msgid "Thank you for your comment" msgstr "Gracias por su comentario" #: contrib/comments/templates/comments/preview.html:4 -#: contrib/comments/templates/comments/preview.html:12 +#: contrib/comments/templates/comments/preview.html:13 msgid "Preview your comment" msgstr "Previsualizar su comentario" -#: contrib/comments/templates/comments/preview.html:10 +#: contrib/comments/templates/comments/preview.html:11 msgid "Please correct the error below" msgid_plural "Please correct the errors below" msgstr[0] "Por favor, corrija el siguiente error." msgstr[1] "Por favor, corrija los siguientes errores." -#: contrib/comments/templates/comments/preview.html:15 +#: contrib/comments/templates/comments/preview.html:16 msgid "Post your comment" msgstr "Envie su comentario" -#: contrib/comments/templates/comments/preview.html:15 +#: contrib/comments/templates/comments/preview.html:16 msgid "or make changes" msgstr "o haga cambios" -#: contrib/contenttypes/models.py:67 +#: contrib/contenttypes/models.py:70 msgid "python model class name" msgstr "nombre de la clase modelo de python" -#: contrib/contenttypes/models.py:71 -msgid "content type" -msgstr "tipo de contenido" - -#: contrib/contenttypes/models.py:72 +#: contrib/contenttypes/models.py:75 msgid "content types" msgstr "tipos de contenido" @@ -1762,18 +1798,26 @@ msgstr "" "Lo sentimos, pero su formulario ha expirado. Por favor, continue rellenando " "el formulario en esta página." -#: contrib/gis/forms/fields.py:14 +#: contrib/gis/forms/fields.py:17 msgid "No geometry value provided." msgstr "No se indico ningún valor de geometría." -#: contrib/gis/forms/fields.py:15 +#: contrib/gis/forms/fields.py:18 msgid "Invalid geometry value." msgstr "Valor de geometría inválido." -#: contrib/gis/forms/fields.py:16 +#: contrib/gis/forms/fields.py:19 msgid "Invalid geometry type." msgstr "Tipo de geometría inválido." +#: contrib/gis/forms/fields.py:20 +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." + #: contrib/humanize/templatetags/humanize.py:19 msgid "th" msgstr "º" @@ -1891,11 +1935,11 @@ msgstr "Carinthia" #: contrib/localflavor/at/at_states.py:7 msgid "Lower Austria" -msgstr "" +msgstr "Australia Baja" #: contrib/localflavor/at/at_states.py:8 msgid "Upper Austria" -msgstr "" +msgstr "Australia Alta" #: contrib/localflavor/at/at_states.py:9 msgid "Salzburg" @@ -2112,47 +2156,47 @@ msgstr "Región Bohemia Sur" #: contrib/localflavor/cz/cz_regions.py:11 msgid "Pilsen Region" -msgstr "" +msgstr "Región Pilsen" #: contrib/localflavor/cz/cz_regions.py:12 msgid "Carlsbad Region" -msgstr "" +msgstr "Región Carlsbad" #: contrib/localflavor/cz/cz_regions.py:13 msgid "Usti Region" -msgstr "" +msgstr "Región Usti" #: contrib/localflavor/cz/cz_regions.py:14 msgid "Liberec Region" -msgstr "" +msgstr "Región Liberec" #: contrib/localflavor/cz/cz_regions.py:15 msgid "Hradec Region" -msgstr "" +msgstr "Región Hradec" #: contrib/localflavor/cz/cz_regions.py:16 msgid "Pardubice Region" -msgstr "" +msgstr "Región Pardubice" #: contrib/localflavor/cz/cz_regions.py:17 msgid "Vysocina Region" -msgstr "" +msgstr "Región Vysocina" #: contrib/localflavor/cz/cz_regions.py:18 msgid "South Moravian Region" -msgstr "" +msgstr "Región Moravia Sur" #: contrib/localflavor/cz/cz_regions.py:19 msgid "Olomouc Region" -msgstr "" +msgstr "Región Olomouc" #: contrib/localflavor/cz/cz_regions.py:20 msgid "Zlin Region" -msgstr "" +msgstr "Región Zlin" #: contrib/localflavor/cz/cz_regions.py:21 msgid "Moravian-Silesian Region" -msgstr "" +msgstr "Región Moravia-Silesiana" #: contrib/localflavor/cz/forms.py:27 contrib/localflavor/sk/forms.py:30 msgid "Enter a postal code in the format XXXXX or XXX XX." @@ -2165,7 +2209,8 @@ msgstr "" #: contrib/localflavor/cz/forms.py:48 msgid "Invalid optional parameter Gender, valid values are 'f' and 'm'" -msgstr "El parámetro opcional 'Género' es inválido, los valores válidos son 'f' y 'm'" +msgstr "" +"El parámetro opcional 'Género' es inválido, los valores válidos son 'f' y 'm'" #: contrib/localflavor/cz/forms.py:49 msgid "Enter a valid birth number." @@ -3004,16 +3049,16 @@ msgstr "" msgid "Wrong checksum for the Tax Number (NIP)." msgstr "El Número de Identificación Tributaria (NIP) es incorrecto." -#: contrib/localflavor/pl/forms.py:111 -msgid "National Business Register Number (REGON) consists of 7 or 9 digits." +#: 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 7 o 9 dígitos." +"El Número Nacional de Registro de Negocios (REGON) consiste en 9 o 14 dígitos." -#: contrib/localflavor/pl/forms.py:112 +#: contrib/localflavor/pl/forms.py:110 msgid "Wrong checksum for the National Business Register Number (REGON)." msgstr "El Número Nacional de Registro de Negocios (REGON) es incorrecto." -#: contrib/localflavor/pl/forms.py:155 +#: contrib/localflavor/pl/forms.py:148 msgid "Enter a postal code in the format XX-XXX." msgstr "Introduzca un código postal en el formato XX-XXX." @@ -3850,60 +3895,60 @@ msgstr "nombre para mostrar" msgid "sites" msgstr "sitios" -#: db/models/fields/__init__.py:356 db/models/fields/__init__.py:700 +#: db/models/fields/__init__.py:356 db/models/fields/__init__.py:710 msgid "This value must be an integer." msgstr "Este valor debe ser un entero." -#: db/models/fields/__init__.py:387 +#: db/models/fields/__init__.py:388 msgid "This value must be either True or False." msgstr "Este valor debe ser Verdadero o Falso." -#: db/models/fields/__init__.py:420 +#: db/models/fields/__init__.py:427 msgid "This field cannot be null." msgstr "Este campo no puede estar vacío." -#: db/models/fields/__init__.py:436 +#: db/models/fields/__init__.py:443 msgid "Enter only digits separated by commas." msgstr "Introduzca sólo dígitos separados por comas." -#: db/models/fields/__init__.py:467 +#: db/models/fields/__init__.py:474 msgid "Enter a valid date in YYYY-MM-DD format." msgstr "Introduzca una fecha válida en formato AAAA-MM-DD." -#: db/models/fields/__init__.py:476 +#: db/models/fields/__init__.py:483 #, python-format msgid "Invalid date: %s" msgstr "Fecha no válida: %s" -#: db/models/fields/__init__.py:540 db/models/fields/__init__.py:558 +#: db/models/fields/__init__.py:547 db/models/fields/__init__.py:565 msgid "Enter a valid date/time in YYYY-MM-DD HH:MM[:ss[.uuuuuu]] format." msgstr "" "Introduzca una fecha/hora válida en formato AAAA-MM-DD HH:MM[:ss[.uuuuuu]]." -#: db/models/fields/__init__.py:594 +#: db/models/fields/__init__.py:601 msgid "This value must be a decimal number." msgstr "Este valor debe ser un entero." -#: db/models/fields/__init__.py:676 +#: db/models/fields/__init__.py:686 msgid "This value must be a float." msgstr "Este valor debe ser un número con coma flotante." -#: db/models/fields/__init__.py:736 +#: db/models/fields/__init__.py:746 msgid "This value must be either None, True or False." msgstr "Este valor debe ser Verdadero, Falso o Ninguno." -#: db/models/fields/__init__.py:839 db/models/fields/__init__.py:853 +#: db/models/fields/__init__.py:849 db/models/fields/__init__.py:863 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:779 +#: db/models/fields/related.py:816 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:857 +#: db/models/fields/related.py:894 #, python-format msgid "Please enter valid %(self)s IDs. The value %(value)r is invalid." msgid_plural "" @@ -3970,11 +4015,11 @@ msgstr "Asegúrese de que no hay más de %s decimales." 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:850 +#: forms/fields.py:288 forms/fields.py:863 msgid "Enter a valid date." msgstr "Introduzca una fecha válida." -#: forms/fields.py:322 forms/fields.py:851 +#: forms/fields.py:322 forms/fields.py:864 msgid "Enter a valid time." msgstr "Introduzca una hora válida." @@ -3996,7 +4041,15 @@ msgstr "No se ha enviado ningún fichero" msgid "The submitted file is empty." msgstr "El fichero enviado está vacío." -#: forms/fields.py:478 +#: forms/fields.py:450 +#, python-format +msgid "" +"Ensure this filename has at most %(max)d characters (it has %(length)d)." +msgstr "" +"Asegúrese de que su texto tiene no más de %(max)d caracteres (actualmente " +"tiene %(length)d)." + +#: forms/fields.py:483 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." @@ -4004,106 +4057,140 @@ 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:539 +#: forms/fields.py:544 msgid "Enter a valid URL." msgstr "Introduzca una URL válida." -#: forms/fields.py:540 +#: forms/fields.py:545 msgid "This URL appears to be a broken link." msgstr "La URL parece ser un enlace roto." -#: forms/fields.py:619 forms/fields.py:697 +#: forms/fields.py:625 forms/fields.py:703 #, 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:698 forms/fields.py:759 forms/models.py:729 +#: forms/fields.py:704 forms/fields.py:765 forms/models.py:1003 msgid "Enter a list of values." msgstr "Introduzca una lista de valores." -#: forms/fields.py:879 +#: forms/fields.py:892 msgid "Enter a valid IPv4 address." msgstr "Introduzca una dirección IPv4 válida." -#: forms/fields.py:889 +#: forms/fields.py:902 msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" "Introduzca un 'slug' válido, consistente en letras, números, guiones bajos o " "medios." -#: forms/formsets.py:247 forms/formsets.py:249 +#: forms/formsets.py:271 forms/formsets.py:273 msgid "Order" msgstr "Orden" -#: forms/models.py:289 forms/models.py:298 +#: forms/models.py:367 +#, 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 #, 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:602 +#: forms/models.py:594 +#, 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 +#, 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 +#, 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 "" +"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 +msgid "Please correct the duplicate values below." +msgstr "Por favor, corrija los valores duplicados abajo." + +#: forms/models.py:867 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:659 +#: forms/models.py:930 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:730 +#: forms/models.py:1004 #, 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." -#: template/defaultfilters.py:751 +#: forms/models.py:1006 +#, 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." + +#: template/defaultfilters.py:767 msgid "yes,no,maybe" msgstr "sí,no,tal vez" -#: template/defaultfilters.py:782 +#: template/defaultfilters.py:798 #, python-format msgid "%(size)d byte" msgid_plural "%(size)d bytes" msgstr[0] "%(size)d byte" msgstr[1] "%(size)d bytes" -#: template/defaultfilters.py:784 +#: template/defaultfilters.py:800 #, python-format msgid "%.1f KB" msgstr "%.1f KB" -#: template/defaultfilters.py:786 +#: template/defaultfilters.py:802 #, python-format msgid "%.1f MB" msgstr "%.1f MB" -#: template/defaultfilters.py:787 +#: template/defaultfilters.py:803 #, python-format msgid "%.1f GB" msgstr "%.1f GB" -#: utils/dateformat.py:41 +#: utils/dateformat.py:42 msgid "p.m." msgstr "p.m." -#: utils/dateformat.py:42 +#: utils/dateformat.py:43 msgid "a.m." msgstr "a.m." -#: utils/dateformat.py:47 +#: utils/dateformat.py:48 msgid "PM" msgstr "PM" -#: utils/dateformat.py:48 +#: utils/dateformat.py:49 msgid "AM" msgstr "AM" -#: utils/dateformat.py:97 +#: utils/dateformat.py:98 msgid "midnight" msgstr "media noche" -#: utils/dateformat.py:99 +#: utils/dateformat.py:100 msgid "noon" msgstr "medio día" @@ -4327,16 +4414,16 @@ msgid_plural "minutes" msgstr[0] "minuto" msgstr[1] "minutos" -#: utils/timesince.py:43 +#: utils/timesince.py:45 msgid "minutes" msgstr "minutos" -#: utils/timesince.py:48 +#: utils/timesince.py:50 #, python-format msgid "%(number)d %(type)s" msgstr "%(number)d %(type)s" -#: utils/timesince.py:54 +#: utils/timesince.py:56 #, python-format msgid ", %(number)d %(type)s" msgstr ", %(number)d %(type)s" @@ -4372,6 +4459,11 @@ 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 "verbose_name" +#~ msgid_plural "verbose_name_plural" +#~ msgstr[0] "verbose_name" +#~ msgstr[1] "verbose_name_plural" + #~ msgid "DATE_WITH_TIME_FULL" #~ msgstr "j M Y P" @@ -4411,9 +4503,6 @@ msgstr "El/La %(verbose_name)s ha sido borrado." #~ msgid "is valid rating" #~ msgstr "puntuación válida" -#~ msgid "comments" -#~ msgstr "comentarios" - #~ msgid "Content object" #~ msgstr "Objeto contenido" @@ -4610,9 +4699,6 @@ msgstr "El/La %(verbose_name)s ha sido borrado." #~ msgid "Please enter both fields or leave them both empty." #~ msgstr "Por favor, rellene ambos campos o deje ambos vacíos." -#~ msgid "This field must be given if %(field)s is %(value)s" -#~ msgstr "Se debe proporcionar este campo si %(field)s es %(value)s" - #~ msgid "This field must be given if %(field)s is not %(value)s" #~ msgstr "Se debe proporcionar este campo si %(field)s no es %(value)s" diff --git a/django/conf/urls/defaults.py b/django/conf/urls/defaults.py index 26cdd3e1ff..3ab8bab3ec 100644 --- a/django/conf/urls/defaults.py +++ b/django/conf/urls/defaults.py @@ -6,7 +6,16 @@ __all__ = ['handler404', 'handler500', 'include', 'patterns', 'url'] handler404 = 'django.views.defaults.page_not_found' handler500 = 'django.views.defaults.server_error' -include = lambda urlconf_module: [urlconf_module] +def include(arg, namespace=None, app_name=None): + if isinstance(arg, tuple): + # callable returning a namespace hint + if namespace: + raise ImproperlyConfigured('Cannot override the namespace for a dynamic module that provides a namespace') + urlconf_module, app_name, namespace = arg + else: + # No namespace hint - use manually provided namespace + urlconf_module = arg + return (urlconf_module, app_name, namespace) def patterns(prefix, *args): pattern_list = [] @@ -19,9 +28,10 @@ def patterns(prefix, *args): return pattern_list def url(regex, view, kwargs=None, name=None, prefix=''): - if type(view) == list: + if isinstance(view, (list,tuple)): # For include(...) processing. - return RegexURLResolver(regex, view[0], kwargs) + urlconf_module, app_name, namespace = view + return RegexURLResolver(regex, urlconf_module, kwargs, app_name=app_name, namespace=namespace) else: if isinstance(view, basestring): if not view: diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index 8297eca74e..31a28ccf0b 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -226,24 +226,24 @@ class ModelAdmin(BaseModelAdmin): return self.admin_site.admin_view(view)(*args, **kwargs) return update_wrapper(wrapper, view) - info = self.admin_site.name, self.model._meta.app_label, self.model._meta.module_name + info = self.model._meta.app_label, self.model._meta.module_name urlpatterns = patterns('', url(r'^$', wrap(self.changelist_view), - name='%sadmin_%s_%s_changelist' % info), + name='%s_%s_changelist' % info), url(r'^add/$', wrap(self.add_view), - name='%sadmin_%s_%s_add' % info), + name='%s_%s_add' % info), url(r'^(.+)/history/$', wrap(self.history_view), - name='%sadmin_%s_%s_history' % info), + name='%s_%s_history' % info), url(r'^(.+)/delete/$', wrap(self.delete_view), - name='%sadmin_%s_%s_delete' % info), + name='%s_%s_delete' % info), url(r'^(.+)/$', wrap(self.change_view), - name='%sadmin_%s_%s_change' % info), + name='%s_%s_change' % info), ) return urlpatterns @@ -582,11 +582,12 @@ class ModelAdmin(BaseModelAdmin): 'save_on_top': self.save_on_top, 'root_path': self.admin_site.root_path, }) + context_instance = template.RequestContext(request, current_app=self.admin_site.name) return render_to_response(self.change_form_template or [ "admin/%s/%s/change_form.html" % (app_label, opts.object_name.lower()), "admin/%s/change_form.html" % app_label, "admin/change_form.html" - ], context, context_instance=template.RequestContext(request)) + ], context, context_instance=context_instance) def response_add(self, request, obj, post_url_continue='../%s/'): """ @@ -977,11 +978,12 @@ class ModelAdmin(BaseModelAdmin): 'actions_on_bottom': self.actions_on_bottom, } context.update(extra_context or {}) + context_instance = template.RequestContext(request, current_app=self.admin_site.name) return render_to_response(self.change_list_template or [ 'admin/%s/%s/change_list.html' % (app_label, opts.object_name.lower()), 'admin/%s/change_list.html' % app_label, 'admin/change_list.html' - ], context, context_instance=template.RequestContext(request)) + ], context, context_instance=context_instance) def delete_view(self, request, object_id, extra_context=None): "The 'delete' admin view for this model." @@ -1032,11 +1034,12 @@ class ModelAdmin(BaseModelAdmin): "app_label": app_label, } context.update(extra_context or {}) + context_instance = template.RequestContext(request, current_app=self.admin_site.name) return render_to_response(self.delete_confirmation_template or [ "admin/%s/%s/delete_confirmation.html" % (app_label, opts.object_name.lower()), "admin/%s/delete_confirmation.html" % app_label, "admin/delete_confirmation.html" - ], context, context_instance=template.RequestContext(request)) + ], context, context_instance=context_instance) def history_view(self, request, object_id, extra_context=None): "The 'history' admin view for this model." @@ -1059,11 +1062,12 @@ class ModelAdmin(BaseModelAdmin): 'app_label': app_label, } context.update(extra_context or {}) + context_instance = template.RequestContext(request, current_app=self.admin_site.name) return render_to_response(self.object_history_template or [ "admin/%s/%s/object_history.html" % (app_label, opts.object_name.lower()), "admin/%s/object_history.html" % app_label, "admin/object_history.html" - ], context, context_instance=template.RequestContext(request)) + ], context, context_instance=context_instance) # # DEPRECATED methods. diff --git a/django/contrib/admin/sites.py b/django/contrib/admin/sites.py index 6e9ef1161f..abcff14cd1 100644 --- a/django/contrib/admin/sites.py +++ b/django/contrib/admin/sites.py @@ -5,6 +5,7 @@ from django.contrib.admin import actions from django.contrib.auth import authenticate, login from django.db.models.base import ModelBase from django.core.exceptions import ImproperlyConfigured +from django.core.urlresolvers import reverse from django.shortcuts import render_to_response from django.utils.functional import update_wrapper from django.utils.safestring import mark_safe @@ -38,17 +39,14 @@ class AdminSite(object): login_template = None app_index_template = None - def __init__(self, name=None): + def __init__(self, name=None, app_name='admin'): self._registry = {} # model_class class -> admin_class instance - # TODO Root path is used to calculate urls under the old root() method - # in order to maintain backwards compatibility we are leaving that in - # so root_path isn't needed, not sure what to do about this. - self.root_path = 'admin/' + self.root_path = None if name is None: - name = '' + self.name = 'admin' else: - name += '_' - self.name = name + self.name = name + self.app_name = app_name self._actions = {'delete_selected': actions.delete_selected} self._global_actions = self._actions.copy() @@ -114,20 +112,20 @@ class AdminSite(object): name = name or action.__name__ self._actions[name] = action self._global_actions[name] = action - + def disable_action(self, name): """ Disable a globally-registered action. Raises KeyError for invalid names. """ del self._actions[name] - + def get_action(self, name): """ Explicitally get a registered global action wheather it's enabled or not. Raises KeyError for invalid names. """ return self._global_actions[name] - + def actions(self): """ Get all the enabled actions as an iterable of (name, func). @@ -159,9 +157,9 @@ class AdminSite(object): if 'django.core.context_processors.auth' not in settings.TEMPLATE_CONTEXT_PROCESSORS: raise ImproperlyConfigured("Put 'django.core.context_processors.auth' in your TEMPLATE_CONTEXT_PROCESSORS setting in order to use the admin application.") - def admin_view(self, view): + def admin_view(self, view, cacheable=False): """ - Decorator to create an "admin view attached to this ``AdminSite``. This + Decorator to create an admin view attached to this ``AdminSite``. This wraps the view and provides permission checking by calling ``self.has_permission``. @@ -177,43 +175,49 @@ class AdminSite(object): url(r'^my_view/$', self.admin_view(some_view)) ) return urls + + By default, admin_views are marked non-cacheable using the + ``never_cache`` decorator. If the view can be safely cached, set + cacheable=True. """ def inner(request, *args, **kwargs): if not self.has_permission(request): return self.login(request) return view(request, *args, **kwargs) + if not cacheable: + inner = never_cache(inner) return update_wrapper(inner, view) def get_urls(self): from django.conf.urls.defaults import patterns, url, include - def wrap(view): + def wrap(view, cacheable=False): def wrapper(*args, **kwargs): - return self.admin_view(view)(*args, **kwargs) + return self.admin_view(view, cacheable)(*args, **kwargs) return update_wrapper(wrapper, view) # Admin-site-wide views. urlpatterns = patterns('', url(r'^$', wrap(self.index), - name='%sadmin_index' % self.name), + name='index'), url(r'^logout/$', wrap(self.logout), - name='%sadmin_logout'), + name='logout'), url(r'^password_change/$', - wrap(self.password_change), - name='%sadmin_password_change' % self.name), + wrap(self.password_change, cacheable=True), + name='password_change'), url(r'^password_change/done/$', - wrap(self.password_change_done), - name='%sadmin_password_change_done' % self.name), + wrap(self.password_change_done, cacheable=True), + name='password_change_done'), url(r'^jsi18n/$', - wrap(self.i18n_javascript), - name='%sadmin_jsi18n' % self.name), + wrap(self.i18n_javascript, cacheable=True), + name='jsi18n'), url(r'^r/(?P\d+)/(?P.+)/$', 'django.views.defaults.shortcut'), url(r'^(?P\w+)/$', wrap(self.app_index), - name='%sadmin_app_list' % self.name), + name='app_list') ) # Add in each model's views. @@ -225,7 +229,7 @@ class AdminSite(object): return urlpatterns def urls(self): - return self.get_urls() + return self.get_urls(), self.app_name, self.name urls = property(urls) def password_change(self, request): @@ -233,8 +237,11 @@ class AdminSite(object): Handles the "change password" task -- both form display and validation. """ from django.contrib.auth.views import password_change - return password_change(request, - post_change_redirect='%spassword_change/done/' % self.root_path) + if self.root_path is not None: + url = '%spassword_change/done/' % self.root_path + else: + url = reverse('admin:password_change_done', current_app=self.name) + return password_change(request, post_change_redirect=url) def password_change_done(self, request): """ @@ -362,8 +369,9 @@ class AdminSite(object): 'root_path': self.root_path, } context.update(extra_context or {}) + context_instance = template.RequestContext(request, current_app=self.name) return render_to_response(self.index_template or 'admin/index.html', context, - context_instance=template.RequestContext(request) + context_instance=context_instance ) index = never_cache(index) @@ -376,8 +384,9 @@ class AdminSite(object): 'root_path': self.root_path, } context.update(extra_context or {}) + context_instance = template.RequestContext(request, current_app=self.name) return render_to_response(self.login_template or 'admin/login.html', context, - context_instance=template.RequestContext(request) + context_instance=context_instance ) def app_index(self, request, app_label, extra_context=None): @@ -419,9 +428,10 @@ class AdminSite(object): 'root_path': self.root_path, } context.update(extra_context or {}) + context_instance = template.RequestContext(request, current_app=self.name) return render_to_response(self.app_index_template or ('admin/%s/app_index.html' % app_label, 'admin/app_index.html'), context, - context_instance=template.RequestContext(request) + context_instance=context_instance ) def root(self, request, url): diff --git a/django/contrib/admin/templates/admin/base.html b/django/contrib/admin/templates/admin/base.html index 8cab43963a..95257285eb 100644 --- a/django/contrib/admin/templates/admin/base.html +++ b/django/contrib/admin/templates/admin/base.html @@ -23,7 +23,30 @@ {% block branding %}{% endblock %} {% if user.is_authenticated and user.is_staff %} -
{% trans 'Welcome,' %} {% firstof user.first_name user.username %}. {% block userlinks %}{% url django-admindocs-docroot as docsroot %}{% if docsroot %}{% trans 'Documentation' %} / {% endif %}{% trans 'Change password' %} / {% trans 'Log out' %}{% endblock %}
+
+ {% trans 'Welcome,' %} + {% firstof user.first_name user.username %}. + {% block userlinks %} + {% url django-admindocs-docroot as docsroot %} + {% if docsroot %} + {% trans 'Documentation' %} / + {% endif %} + {% url admin:password_change as password_change_url %} + {% if password_change_url %} + + {% else %} + + {% endif %} + {% trans 'Change password' %} / + {% url admin:logout as logout_url %} + {% if logout_url %} + + {% else %} + + {% endif %} + {% trans 'Log out' %} + {% endblock %} +
{% endif %} {% block nav-global %}{% endblock %} diff --git a/django/contrib/admin/widgets.py b/django/contrib/admin/widgets.py index 7ae5e647db..1a081bc293 100644 --- a/django/contrib/admin/widgets.py +++ b/django/contrib/admin/widgets.py @@ -125,7 +125,7 @@ class ForeignKeyRawIdWidget(forms.TextInput): if value: output.append(self.label_for_value(value)) return mark_safe(u''.join(output)) - + def base_url_parameters(self): params = {} if self.rel.limit_choices_to: @@ -137,14 +137,14 @@ class ForeignKeyRawIdWidget(forms.TextInput): v = str(v) items.append((k, v)) params.update(dict(items)) - return params - + return params + def url_parameters(self): from django.contrib.admin.views.main import TO_FIELD_VAR params = self.base_url_parameters() params.update({TO_FIELD_VAR: self.rel.get_related_field().name}) return params - + def label_for_value(self, value): key = self.rel.get_related_field().name obj = self.rel.to._default_manager.get(**{key: value}) @@ -165,10 +165,10 @@ class ManyToManyRawIdWidget(ForeignKeyRawIdWidget): else: value = '' return super(ManyToManyRawIdWidget, self).render(name, value, attrs) - + def url_parameters(self): return self.base_url_parameters() - + def label_for_value(self, value): return '' @@ -222,8 +222,7 @@ class RelatedFieldWidgetWrapper(forms.Widget): rel_to = self.rel.to info = (rel_to._meta.app_label, rel_to._meta.object_name.lower()) try: - related_info = (self.admin_site.name,) + info - related_url = reverse('%sadmin_%s_%s_add' % related_info) + related_url = reverse('admin:%s_%s_add' % info, current_app=self.admin_site.name) except NoReverseMatch: related_url = '../../../%s/%s/add/' % info self.widget.choices = self.choices diff --git a/django/contrib/admindocs/templates/admin_doc/index.html b/django/contrib/admindocs/templates/admin_doc/index.html index 242fc7339a..a8b21c330d 100644 --- a/django/contrib/admindocs/templates/admin_doc/index.html +++ b/django/contrib/admindocs/templates/admin_doc/index.html @@ -1,6 +1,6 @@ {% extends "admin/base_site.html" %} {% load i18n %} -{% block breadcrumbs %}{% endblock %} +{% block breadcrumbs %}{% endblock %} {% block title %}Documentation{% endblock %} {% block content %} diff --git a/django/contrib/admindocs/views.py b/django/contrib/admindocs/views.py index 4f22fe0a0a..571f393ff8 100644 --- a/django/contrib/admindocs/views.py +++ b/django/contrib/admindocs/views.py @@ -22,11 +22,14 @@ class GenericSite(object): name = 'my site' def get_root_path(): - from django.contrib import admin try: - return urlresolvers.reverse(admin.site.root, args=['']) + return urlresolvers.reverse('admin:index') except urlresolvers.NoReverseMatch: - return getattr(settings, "ADMIN_SITE_ROOT_URL", "/admin/") + from django.contrib import admin + try: + return urlresolvers.reverse(admin.site.root, args=['']) + except urlresolvers.NoReverseMatch: + return getattr(settings, "ADMIN_SITE_ROOT_URL", "/admin/") def doc_index(request): if not utils.docutils_is_available: @@ -179,7 +182,7 @@ model_index = staff_member_required(model_index) def model_detail(request, app_label, model_name): if not utils.docutils_is_available: return missing_docutils_page(request) - + # Get the model class. try: app_mod = models.get_app(app_label) diff --git a/django/contrib/gis/db/models/manager.py b/django/contrib/gis/db/models/manager.py index 2bfda990ff..eac66f4a83 100644 --- a/django/contrib/gis/db/models/manager.py +++ b/django/contrib/gis/db/models/manager.py @@ -19,6 +19,9 @@ class GeoManager(Manager): def centroid(self, *args, **kwargs): return self.get_query_set().centroid(*args, **kwargs) + def collect(self, *args, **kwargs): + return self.get_query_set().collect(*args, **kwargs) + def difference(self, *args, **kwargs): return self.get_query_set().difference(*args, **kwargs) diff --git a/django/contrib/gis/db/models/query.py b/django/contrib/gis/db/models/query.py index 3d31f73c29..d055260a58 100644 --- a/django/contrib/gis/db/models/query.py +++ b/django/contrib/gis/db/models/query.py @@ -62,6 +62,14 @@ class GeoQuerySet(QuerySet): """ return self._geom_attribute('centroid', **kwargs) + def collect(self, **kwargs): + """ + Performs an aggregate collect operation on the given geometry field. + This is analagous to a union operation, but much faster because + boundaries are not dissolved. + """ + return self._spatial_aggregate(aggregates.Collect, **kwargs) + def difference(self, geom, **kwargs): """ Returns the spatial difference of the geographic field in a `difference` diff --git a/django/contrib/gis/db/models/sql/query.py b/django/contrib/gis/db/models/sql/query.py index 5df15a88b1..094fc5815f 100644 --- a/django/contrib/gis/db/models/sql/query.py +++ b/django/contrib/gis/db/models/sql/query.py @@ -13,7 +13,9 @@ from django.contrib.gis.measure import Area, Distance ALL_TERMS = sql.constants.QUERY_TERMS.copy() ALL_TERMS.update(SpatialBackend.gis_terms) +# Pulling out other needed constants/routines to avoid attribute lookups. TABLE_NAME = sql.constants.TABLE_NAME +get_proxied_model = sql.query.get_proxied_model class GeoQuery(sql.Query): """ @@ -153,7 +155,9 @@ class GeoQuery(sql.Query): opts = self.model._meta aliases = set() only_load = self.deferred_to_columns() - proxied_model = opts.proxy and opts.proxy_for_model or 0 + # Skip all proxy to the root proxied model + proxied_model = get_proxied_model(opts) + if start_alias: seen = {None: start_alias} for field, model in opts.get_fields_with_model(): @@ -205,6 +209,10 @@ class GeoQuery(sql.Query): """ values = [] aliases = self.extra_select.keys() + if self.aggregates: + # If we have an aggregate annotation, must extend the aliases + # so their corresponding row values are included. + aliases.extend([None for i in xrange(len(self.aggregates))]) # Have to set a starting row number offset that is used for # determining the correct starting row index -- needed for diff --git a/django/contrib/gis/gdal/prototypes/generation.py b/django/contrib/gis/gdal/prototypes/generation.py index bba715d67c..1303532372 100644 --- a/django/contrib/gis/gdal/prototypes/generation.py +++ b/django/contrib/gis/gdal/prototypes/generation.py @@ -8,6 +8,9 @@ from django.contrib.gis.gdal.prototypes.errcheck import \ check_arg_errcode, check_errcode, check_geom, check_geom_offset, \ check_pointer, check_srs, check_str_arg, check_string, check_const_string +class gdal_char_p(c_char_p): + pass + def double_output(func, argtypes, errcheck=False, strarg=False): "Generates a ctypes function that returns a double value." func.argtypes = argtypes @@ -77,9 +80,9 @@ def string_output(func, argtypes, offset=-1, str_result=False): """ func.argtypes = argtypes if str_result: - # String is the result, don't explicitly define - # the argument type so we can get the pointer. - pass + # Use subclass of c_char_p so the error checking routine + # can free the memory at the pointer's address. + func.restype = gdal_char_p else: # Error code is returned func.restype = c_int diff --git a/django/contrib/gis/geos/prototypes/topology.py b/django/contrib/gis/geos/prototypes/topology.py index 633340901b..65c26f9f37 100644 --- a/django/contrib/gis/geos/prototypes/topology.py +++ b/django/contrib/gis/geos/prototypes/topology.py @@ -10,6 +10,7 @@ __all__ = ['geos_boundary', 'geos_buffer', 'geos_centroid', 'geos_convexhull', from ctypes import c_char_p, c_double, c_int from django.contrib.gis.geos.libgeos import lgeos, GEOM_PTR, GEOS_PREPARE from django.contrib.gis.geos.prototypes.errcheck import check_geom, check_string +from django.contrib.gis.geos.prototypes.geom import geos_char_p def topology(func, *args): "For GEOS unary topology functions." @@ -38,6 +39,7 @@ geos_union = topology(lgeos.GEOSUnion, GEOM_PTR) # GEOSRelate returns a string, not a geometry. geos_relate = lgeos.GEOSRelate geos_relate.argtypes = [GEOM_PTR, GEOM_PTR] +geos_relate.restype = geos_char_p geos_relate.errcheck = check_string # Routines only in GEOS 3.1+ diff --git a/django/contrib/gis/tests/relatedapp/tests.py b/django/contrib/gis/tests/relatedapp/tests.py index 502a3c0be9..60528f177c 100644 --- a/django/contrib/gis/tests/relatedapp/tests.py +++ b/django/contrib/gis/tests/relatedapp/tests.py @@ -1,7 +1,7 @@ import os, unittest from django.contrib.gis.geos import * from django.contrib.gis.db.backend import SpatialBackend -from django.contrib.gis.db.models import Count, Extent, F, Union +from django.contrib.gis.db.models import Collect, Count, Extent, F, Union from django.contrib.gis.tests.utils import no_mysql, no_oracle, no_spatialite from django.conf import settings from models import City, Location, DirectoryEntry, Parcel, Book, Author @@ -231,8 +231,12 @@ class RelatedGeoModelTest(unittest.TestCase): q = pickle.loads(q_str) self.assertEqual(GeoQuery, q.__class__) - def test12_count(self): - "Testing `Count` aggregate use with the `GeoManager`. See #11087." + # TODO: fix on Oracle -- get the following error because the SQL is ordered + # by a geometry object, which Oracle apparently doesn't like: + # ORA-22901: cannot compare nested table or VARRAY or LOB attributes of an object type + @no_oracle + def test12a_count(self): + "Testing `Count` aggregate use with the `GeoManager` on geo-fields." # Creating a new City, 'Fort Worth', that uses the same location # as Dallas. dallas = City.objects.get(name='Dallas') @@ -242,6 +246,8 @@ class RelatedGeoModelTest(unittest.TestCase): loc = Location.objects.annotate(num_cities=Count('city')).get(id=dallas.location.id) self.assertEqual(2, loc.num_cities) + def test12b_count(self): + "Testing `Count` aggregate use with the `GeoManager` on non geo-fields. See #11087." # Creating some data for the Book/Author non-geo models that # use GeoManager. See #11087. tp = Author.objects.create(name='Trevor Paglen') @@ -250,13 +256,19 @@ class RelatedGeoModelTest(unittest.TestCase): Book.objects.create(title='Blank Spots on the Map', author=tp) wp = Author.objects.create(name='William Patry') Book.objects.create(title='Patry on Copyright', author=wp) - + # Should only be one author (Trevor Paglen) returned by this query, and - # the annotation should have 3 for the number of books. + # the annotation should have 3 for the number of books. Also testing + # with a `GeoValuesQuerySet` (see #11489). qs = Author.objects.annotate(num_books=Count('books')).filter(num_books__gt=1) + vqs = Author.objects.values('name').annotate(num_books=Count('books')).filter(num_books__gt=1) self.assertEqual(1, len(qs)) self.assertEqual(3, qs[0].num_books) + self.assertEqual(1, len(vqs)) + self.assertEqual(3, vqs[0]['num_books']) + # TODO: The phantom model does appear on Oracle. + @no_oracle def test13_select_related_null_fk(self): "Testing `select_related` on a nullable ForeignKey via `GeoManager`. See #11381." no_author = Book.objects.create(title='Without Author') @@ -264,6 +276,26 @@ class RelatedGeoModelTest(unittest.TestCase): # Should be `None`, and not a 'dummy' model. self.assertEqual(None, b.author) + @no_mysql + @no_oracle + @no_spatialite + def test14_collect(self): + "Testing the `collect` GeoQuerySet method and `Collect` aggregate." + # Reference query: + # SELECT AsText(ST_Collect("relatedapp_location"."point")) FROM "relatedapp_city" LEFT OUTER JOIN + # "relatedapp_location" ON ("relatedapp_city"."location_id" = "relatedapp_location"."id") + # WHERE "relatedapp_city"."state" = 'TX'; + ref_geom = fromstr('MULTIPOINT(-97.516111 33.058333,-96.801611 32.782057,-95.363151 29.763374,-96.801611 32.782057)') + + c1 = City.objects.filter(state='TX').collect(field_name='location__point') + c2 = City.objects.filter(state='TX').aggregate(Collect('location__point'))['location__point__collect'] + + for coll in (c1, c2): + # Even though Dallas and Ft. Worth share same point, Collect doesn't + # consolidate -- that's why 4 points in MultiPoint. + self.assertEqual(4, len(coll)) + self.assertEqual(ref_geom, coll) + # TODO: Related tests for KML, GML, and distance lookups. def suite(): diff --git a/django/core/urlresolvers.py b/django/core/urlresolvers.py index 10e97bbcd5..4f9eb982e2 100644 --- a/django/core/urlresolvers.py +++ b/django/core/urlresolvers.py @@ -139,7 +139,7 @@ class RegexURLPattern(object): callback = property(_get_callback) class RegexURLResolver(object): - def __init__(self, regex, urlconf_name, default_kwargs=None): + def __init__(self, regex, urlconf_name, default_kwargs=None, app_name=None, namespace=None): # regex is a string representing a regular expression. # urlconf_name is a string representing the module containing URLconfs. self.regex = re.compile(regex, re.UNICODE) @@ -148,19 +148,29 @@ class RegexURLResolver(object): self._urlconf_module = self.urlconf_name self.callback = None self.default_kwargs = default_kwargs or {} - self._reverse_dict = MultiValueDict() + self.namespace = namespace + self.app_name = app_name + self._reverse_dict = None + self._namespace_dict = None + self._app_dict = None def __repr__(self): - return '<%s %s %s>' % (self.__class__.__name__, self.urlconf_name, self.regex.pattern) + return '<%s %s (%s:%s) %s>' % (self.__class__.__name__, self.urlconf_name, self.app_name, self.namespace, self.regex.pattern) - def _get_reverse_dict(self): - if not self._reverse_dict: - lookups = MultiValueDict() - for pattern in reversed(self.url_patterns): - p_pattern = pattern.regex.pattern - if p_pattern.startswith('^'): - p_pattern = p_pattern[1:] - if isinstance(pattern, RegexURLResolver): + def _populate(self): + lookups = MultiValueDict() + namespaces = {} + apps = {} + for pattern in reversed(self.url_patterns): + p_pattern = pattern.regex.pattern + if p_pattern.startswith('^'): + p_pattern = p_pattern[1:] + if isinstance(pattern, RegexURLResolver): + if pattern.namespace: + namespaces[pattern.namespace] = (p_pattern, pattern) + if pattern.app_name: + apps.setdefault(pattern.app_name, []).append(pattern.namespace) + else: parent = normalize(pattern.regex.pattern) for name in pattern.reverse_dict: for matches, pat in pattern.reverse_dict.getlist(name): @@ -168,14 +178,36 @@ class RegexURLResolver(object): for piece, p_args in parent: new_matches.extend([(piece + suffix, p_args + args) for (suffix, args) in matches]) lookups.appendlist(name, (new_matches, p_pattern + pat)) - else: - bits = normalize(p_pattern) - lookups.appendlist(pattern.callback, (bits, p_pattern)) - lookups.appendlist(pattern.name, (bits, p_pattern)) - self._reverse_dict = lookups + for namespace, (prefix, sub_pattern) in pattern.namespace_dict.items(): + namespaces[namespace] = (p_pattern + prefix, sub_pattern) + for app_name, namespace_list in pattern.app_dict.items(): + apps.setdefault(app_name, []).extend(namespace_list) + else: + bits = normalize(p_pattern) + lookups.appendlist(pattern.callback, (bits, p_pattern)) + lookups.appendlist(pattern.name, (bits, p_pattern)) + self._reverse_dict = lookups + self._namespace_dict = namespaces + self._app_dict = apps + + def _get_reverse_dict(self): + if self._reverse_dict is None: + self._populate() return self._reverse_dict reverse_dict = property(_get_reverse_dict) + def _get_namespace_dict(self): + if self._namespace_dict is None: + self._populate() + return self._namespace_dict + namespace_dict = property(_get_namespace_dict) + + def _get_app_dict(self): + if self._app_dict is None: + self._populate() + return self._app_dict + app_dict = property(_get_app_dict) + def resolve(self, path): tried = [] match = self.regex.search(path) @@ -261,12 +293,51 @@ class RegexURLResolver(object): def resolve(path, urlconf=None): return get_resolver(urlconf).resolve(path) -def reverse(viewname, urlconf=None, args=None, kwargs=None, prefix=None): +def reverse(viewname, urlconf=None, args=None, kwargs=None, prefix=None, current_app=None): + resolver = get_resolver(urlconf) args = args or [] kwargs = kwargs or {} + if prefix is None: prefix = get_script_prefix() - return iri_to_uri(u'%s%s' % (prefix, get_resolver(urlconf).reverse(viewname, + + if not isinstance(viewname, basestring): + view = viewname + else: + parts = viewname.split(':') + parts.reverse() + view = parts[0] + path = parts[1:] + + resolved_path = [] + while path: + ns = path.pop() + + # Lookup the name to see if it could be an app identifier + try: + app_list = resolver.app_dict[ns] + # Yes! Path part matches an app in the current Resolver + if current_app and current_app in app_list: + # If we are reversing for a particular app, use that namespace + ns = current_app + elif ns not in app_list: + # The name isn't shared by one of the instances (i.e., the default) + # so just pick the first instance as the default. + ns = app_list[0] + except KeyError: + pass + + try: + extra, resolver = resolver.namespace_dict[ns] + resolved_path.append(ns) + prefix = prefix + extra + except KeyError, key: + if resolved_path: + raise NoReverseMatch("%s is not a registered namespace inside '%s'" % (key, ':'.join(resolved_path))) + else: + raise NoReverseMatch("%s is not a registered namespace" % key) + + return iri_to_uri(u'%s%s' % (prefix, resolver.reverse(view, *args, **kwargs))) def clear_url_caches(): diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py index 8f96049cc4..7d85979294 100644 --- a/django/db/backends/oracle/base.py +++ b/django/db/backends/oracle/base.py @@ -217,12 +217,13 @@ WHEN (new.%(col_name)s IS NULL) # continue to loop break for f in model._meta.many_to_many: - table_name = self.quote_name(f.m2m_db_table()) - sequence_name = get_sequence_name(f.m2m_db_table()) - column_name = self.quote_name('id') - output.append(query % {'sequence': sequence_name, - 'table': table_name, - 'column': column_name}) + if not f.rel.through: + table_name = self.quote_name(f.m2m_db_table()) + sequence_name = get_sequence_name(f.m2m_db_table()) + column_name = self.quote_name('id') + output.append(query % {'sequence': sequence_name, + 'table': table_name, + 'column': column_name}) return output def start_transaction_sql(self): diff --git a/django/db/backends/postgresql/operations.py b/django/db/backends/postgresql/operations.py index ee74be1624..331156ee11 100644 --- a/django/db/backends/postgresql/operations.py +++ b/django/db/backends/postgresql/operations.py @@ -121,14 +121,15 @@ class DatabaseOperations(BaseDatabaseOperations): style.SQL_TABLE(qn(model._meta.db_table)))) break # Only one AutoField is allowed per model, so don't bother continuing. for f in model._meta.many_to_many: - output.append("%s setval('%s', coalesce(max(%s), 1), max(%s) %s null) %s %s;" % \ - (style.SQL_KEYWORD('SELECT'), - style.SQL_FIELD(qn('%s_id_seq' % f.m2m_db_table())), - style.SQL_FIELD(qn('id')), - style.SQL_FIELD(qn('id')), - style.SQL_KEYWORD('IS NOT'), - style.SQL_KEYWORD('FROM'), - style.SQL_TABLE(qn(f.m2m_db_table())))) + if not f.rel.through: + output.append("%s setval('%s', coalesce(max(%s), 1), max(%s) %s null) %s %s;" % \ + (style.SQL_KEYWORD('SELECT'), + style.SQL_FIELD(qn('%s_id_seq' % f.m2m_db_table())), + style.SQL_FIELD(qn('id')), + style.SQL_FIELD(qn('id')), + style.SQL_KEYWORD('IS NOT'), + style.SQL_KEYWORD('FROM'), + style.SQL_TABLE(qn(f.m2m_db_table())))) return output def savepoint_create_sql(self, sid): diff --git a/django/template/context.py b/django/template/context.py index 0ccb5faecf..1c43387468 100644 --- a/django/template/context.py +++ b/django/template/context.py @@ -9,10 +9,11 @@ class ContextPopException(Exception): class Context(object): "A stack container for variable context" - def __init__(self, dict_=None, autoescape=True): + def __init__(self, dict_=None, autoescape=True, current_app=None): dict_ = dict_ or {} self.dicts = [dict_] self.autoescape = autoescape + self.current_app = current_app def __repr__(self): return repr(self.dicts) @@ -96,8 +97,8 @@ class RequestContext(Context): Additional processors can be specified as a list of callables using the "processors" keyword argument. """ - def __init__(self, request, dict=None, processors=None): - Context.__init__(self, dict) + def __init__(self, request, dict=None, processors=None, current_app=None): + Context.__init__(self, dict, current_app=current_app) if processors is None: processors = () else: diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py index 7d91cd6415..de746997ab 100644 --- a/django/template/defaulttags.py +++ b/django/template/defaulttags.py @@ -367,17 +367,17 @@ class URLNode(Node): # {% url ... as var %} construct in which cause return nothing. url = '' try: - url = reverse(self.view_name, args=args, kwargs=kwargs) + url = reverse(self.view_name, args=args, kwargs=kwargs, current_app=context.current_app) except NoReverseMatch, e: if settings.SETTINGS_MODULE: project_name = settings.SETTINGS_MODULE.split('.')[0] try: url = reverse(project_name + '.' + self.view_name, - args=args, kwargs=kwargs) + args=args, kwargs=kwargs, current_app=context.current_app) except NoReverseMatch: if self.asvar is None: # Re-raise the original exception, not the one with - # the path relative to the project. This makes a + # the path relative to the project. This makes a # better error message. raise e else: diff --git a/docs/howto/custom-model-fields.txt b/docs/howto/custom-model-fields.txt index 709ea49b87..9f798f14d5 100644 --- a/docs/howto/custom-model-fields.txt +++ b/docs/howto/custom-model-fields.txt @@ -464,7 +464,7 @@ should raise either a ``ValueError`` if the ``value`` is of the wrong sort (a list when you were expecting an object, for example) or a ``TypeError`` if your field does not support that type of lookup. For many fields, you can get by with handling the lookup types that need special handling for your field -and pass the rest of the :meth:`get_db_prep_lookup` method of the parent class. +and pass the rest to the :meth:`get_db_prep_lookup` method of the parent class. If you needed to implement ``get_db_prep_save()``, you will usually need to implement ``get_db_prep_lookup()``. If you don't, ``get_db_prep_value`` will be diff --git a/docs/howto/deployment/modwsgi.txt b/docs/howto/deployment/modwsgi.txt index 902e312551..308ef2b4d2 100644 --- a/docs/howto/deployment/modwsgi.txt +++ b/docs/howto/deployment/modwsgi.txt @@ -1,69 +1,118 @@ -.. _howto-deployment-modwsgi: - -========================================== -How to use Django with Apache and mod_wsgi -========================================== - -Deploying Django with Apache_ and `mod_wsgi`_ is the recommended way to get -Django into production. - -.. _Apache: http://httpd.apache.org/ -.. _mod_wsgi: http://code.google.com/p/modwsgi/ - -mod_wsgi is an Apache module which can be used to host any Python application -which supports the `Python WSGI interface`_, including Django. Django will work -with any version of Apache which supports mod_wsgi. - -.. _python wsgi interface: http://www.python.org/dev/peps/pep-0333/ - -The `official mod_wsgi documentation`_ is fantastic; it's your source for all -the details about how to use mod_wsgi. You'll probably want to start with the -`installation and configuration documentation`_. - -.. _official mod_wsgi documentation: http://code.google.com/p/modwsgi/ -.. _installation and configuration documentation: http://code.google.com/p/modwsgi/wiki/InstallationInstructions - -Basic Configuration -=================== - -Once you've got mod_wsgi installed and activated, edit your ``httpd.conf`` file -and add:: - - WSGIScriptAlias / /path/to/mysite/apache/django.wsgi - -The first bit above is the url you want to be serving your application at (``/`` -indicates the root url), and the second is the location of a "WSGI file" -- see -below -- on your system, usually inside of your project. This tells Apache -to serve any request below the given URL using the WSGI application defined by that file. - -Next we'll need to actually create this WSGI application, so create the file -mentioned in the second part of ``WSGIScriptAlias`` and add:: - - import os - import sys - - os.environ['DJANGO_SETTINGS_MODULE'] = 'mysite.settings' - - import django.core.handlers.wsgi - application = django.core.handlers.wsgi.WSGIHandler() - -If your project is not on your ``PYTHONPATH`` by default you can add:: - - sys.path.append('/usr/local/django') - -just above the final ``import`` line to place your project on the path. Remember to -replace 'mysite.settings' with your correct settings file, and '/usr/local/django' -with your own project's location. - -See the :ref:`Apache/mod_python documentation` for -directions on serving static media, and the `mod_wsgi documentation`_ for an -explanation of other directives and configuration options you can use. - -Details -======= - -For more details, see the `mod_wsgi documentation`_, which explains the above in -more detail, and walks through all the various options you've got when deploying -under mod_wsgi. - -.. _mod_wsgi documentation: http://code.google.com/p/modwsgi/wiki/IntegrationWithDjango +.. _howto-deployment-modwsgi: + +========================================== +How to use Django with Apache and mod_wsgi +========================================== + +Deploying Django with Apache_ and `mod_wsgi`_ is the recommended way to get +Django into production. + +.. _Apache: http://httpd.apache.org/ +.. _mod_wsgi: http://code.google.com/p/modwsgi/ + +mod_wsgi is an Apache module which can be used to host any Python application +which supports the `Python WSGI interface`_, including Django. Django will work +with any version of Apache which supports mod_wsgi. + +.. _python wsgi interface: http://www.python.org/dev/peps/pep-0333/ + +The `official mod_wsgi documentation`_ is fantastic; it's your source for all +the details about how to use mod_wsgi. You'll probably want to start with the +`installation and configuration documentation`_. + +.. _official mod_wsgi documentation: http://code.google.com/p/modwsgi/ +.. _installation and configuration documentation: http://code.google.com/p/modwsgi/wiki/InstallationInstructions + +Basic Configuration +=================== + +Once you've got mod_wsgi installed and activated, edit your ``httpd.conf`` file +and add:: + + WSGIScriptAlias / /path/to/mysite/apache/django.wsgi + +The first bit above is the url you want to be serving your application at (``/`` +indicates the root url), and the second is the location of a "WSGI file" -- see +below -- on your system, usually inside of your project. This tells Apache +to serve any request below the given URL using the WSGI application defined by that file. + +Next we'll need to actually create this WSGI application, so create the file +mentioned in the second part of ``WSGIScriptAlias`` and add:: + + import os + import sys + + os.environ['DJANGO_SETTINGS_MODULE'] = 'mysite.settings' + + import django.core.handlers.wsgi + application = django.core.handlers.wsgi.WSGIHandler() + +If your project is not on your ``PYTHONPATH`` by default you can add:: + + sys.path.append('/usr/local/django') + +just above the final ``import`` line to place your project on the path. Remember to +replace 'mysite.settings' with your correct settings file, and '/usr/local/django' +with your own project's location. + +Serving media files +=================== + +Django doesn't serve media files itself; it leaves that job to whichever Web +server you choose. + +We recommend using a separate Web server -- i.e., one that's not also running +Django -- for serving media. Here are some good choices: + + * lighttpd_ + * Nginx_ + * TUX_ + * A stripped-down version of Apache_ + * Cherokee_ + +If, however, you have no option but to serve media files on the same Apache +``VirtualHost`` as Django, you can set up Apache to serve some URLs as +static media, and others using the mod_wsgi interface to Django. + +This example sets up Django at the site root, but explicitly serves ``robots.txt``, +``favicon.ico``, any CSS file, and anything in the ``/media/`` URL space as a static +file. All other URLs will be served using mod_wsgi:: + + Alias /robots.txt /usr/local/wsgi/static/robots.txt + Alias /favicon.ico /usr/local/wsgi/static/favicon.ico + + AliasMatch /([^/]*\.css) /usr/local/wsgi/static/styles/$1 + + Alias /media/ /usr/local/wsgi/static/media/ + + + Order deny,allow + Allow from all + + + WSGIScriptAlias / /usr/local/wsgi/scripts/django.wsgi + + + Order allow,deny + Allow from all + + +.. _lighttpd: http://www.lighttpd.net/ +.. _Nginx: http://wiki.codemongers.com/Main +.. _TUX: http://en.wikipedia.org/wiki/TUX_web_server +.. _Apache: http://httpd.apache.org/ +.. _Cherokee: http://www.cherokee-project.com/ + +More details on configuring a mod_wsgi site to serve static files can be found +in the mod_wsgi documentation on `hosting static files`_. + +.. _hosting static files: http://code.google.com/p/modwsgi/wiki/ConfigurationGuidelines#Hosting_Of_Static_Files + +Details +======== + +For more details, see the `mod_wsgi documentation on Django integration`_, +which explains the above in more detail, and walks through all the various +options you've got when deploying under mod_wsgi. + +.. _mod_wsgi documentation on Django integration: http://code.google.com/p/modwsgi/wiki/IntegrationWithDjango diff --git a/docs/howto/error-reporting.txt b/docs/howto/error-reporting.txt index e0750ce327..246e7445d0 100644 --- a/docs/howto/error-reporting.txt +++ b/docs/howto/error-reporting.txt @@ -23,6 +23,10 @@ administrators immediate notification of any errors. The :setting:`ADMINS` will get a description of the error, a complete Python traceback, and details about the HTTP request that caused the error. +By default, Django will send email from root@localhost. However, some mail +providers reject all email from this address. To use a different sender +address, modify the :setting:`SERVER_EMAIL` setting. + To disable this behavior, just remove all entries from the :setting:`ADMINS` setting. @@ -33,12 +37,12 @@ Django can also be configured to email errors about broken links (404 "page not found" errors). Django sends emails about 404 errors when: * :setting:`DEBUG` is ``False`` - + * :setting:`SEND_BROKEN_LINK_EMAILS` is ``True`` - + * Your :setting:`MIDDLEWARE_CLASSES` setting includes ``CommonMiddleware`` (which it does by default). - + If those conditions are met, Django will e-mail the users listed in the :setting:`MANAGERS` setting whenever your code raises a 404 and the request has a referer. (It doesn't bother to e-mail for 404s that don't have a referer -- diff --git a/docs/intro/tutorial03.txt b/docs/intro/tutorial03.txt index f4ef5f76fe..687407a284 100644 --- a/docs/intro/tutorial03.txt +++ b/docs/intro/tutorial03.txt @@ -365,7 +365,7 @@ That takes care of setting ``handler404`` in the current module. As you can see in ``django/conf/urls/defaults.py``, ``handler404`` is set to :func:`django.views.defaults.page_not_found` by default. -Three more things to note about 404 views: +Four more things to note about 404 views: * If :setting:`DEBUG` is set to ``True`` (in your settings module) then your 404 view will never be used (and thus the ``404.html`` template will never diff --git a/docs/ref/contrib/admin/_images/article_actions.png b/docs/ref/contrib/admin/_images/article_actions.png index 254a8ad557..df4ab8f1ec 100644 Binary files a/docs/ref/contrib/admin/_images/article_actions.png and b/docs/ref/contrib/admin/_images/article_actions.png differ diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index 4b1dc55b21..584672e4f0 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -762,12 +762,19 @@ documented in :ref:`topics-http-urls`:: anything, so you'll usually want to prepend your custom URLs to the built-in ones. -Note, however, that the ``self.my_view`` function registered above will *not* -have any permission check done; it'll be accessible to the general public. Since -this is usually not what you want, Django provides a convience wrapper to check -permissions. This wrapper is :meth:`AdminSite.admin_view` (i.e. -``self.admin_site.admin_view`` inside a ``ModelAdmin`` instance); use it like -so:: +However, the ``self.my_view`` function registered above suffers from two +problems: + + * It will *not* perform and permission checks, so it will be accessible to + the general public. + * It will *not* provide any header details to prevent caching. This means if + the page retrieves data from the database, and caching middleware is + active, the page could show outdated information. + +Since this is usually not what you want, Django provides a convenience wrapper +to check permissions and mark the view as non-cacheable. This wrapper is +:meth:`AdminSite.admin_view` (i.e. ``self.admin_site.admin_view`` inside a +``ModelAdmin`` instance); use it like so:: class MyModelAdmin(admin.ModelAdmin): def get_urls(self): @@ -781,7 +788,14 @@ Notice the wrapped view in the fifth line above:: (r'^my_view/$', self.admin_site.admin_view(self.my_view)) -This wrapping will protect ``self.my_view`` from unauthorized access. +This wrapping will protect ``self.my_view`` from unauthorized access and will +apply the ``django.views.decorators.cache.never_cache`` decorator to make sure +it is not cached if the cache middleware is active. + +If the page is cacheable, but you still want the permission check to be performed, +you can pass a ``cacheable=True`` argument to :meth:`AdminSite.admin_view`:: + + (r'^my_view/$', self.admin_site.admin_view(self.my_view, cacheable=True)) .. method:: ModelAdmin.formfield_for_foreignkey(self, db_field, request, **kwargs) @@ -849,7 +863,7 @@ provided some extra mapping data that would not otherwise be available:: 'osm_data': self.get_osm_info(), } return super(MyModelAdmin, self).change_view(request, object_id, - extra_context=my_context)) + extra_context=my_context) ``ModelAdmin`` media definitions -------------------------------- @@ -1228,7 +1242,7 @@ or :attr:`AdminSite.login_template` properties. ``AdminSite`` objects ===================== -.. class:: AdminSite +.. class:: AdminSite(name=None) A Django administrative site is represented by an instance of ``django.contrib.admin.sites.AdminSite``; by default, an instance of @@ -1242,6 +1256,14 @@ or add anything you like. Then, simply create an instance of your Python class), and register your models and ``ModelAdmin`` subclasses with it instead of using the default. +.. versionadded:: 1.1 + +When constructing an instance of an ``AdminSite``, you are able to provide +a unique instance name using the ``name`` argument to the constructor. This +instance name is used to identify the instance, especially when +:ref:`reversing admin URLs `. If no instance name is +provided, a default instance name of ``admin`` will be used. + ``AdminSite`` attributes ------------------------ @@ -1339,10 +1361,10 @@ a pattern for your new view. .. note:: Any view you render that uses the admin templates, or extends the base - admin template, should include in it's context a variable named - ``admin_site`` that contains the name of the :class:`AdminSite` instance. For - :class:`AdminSite` instances, this means ``self.name``; for :class:`ModelAdmin` - instances, this means ``self.admin_site.name``. + admin template, should provide the ``current_app`` argument to + ``RequestContext`` or ``Context`` when rendering the template. It should + be set to either ``self.name`` if your view is on an ``AdminSite`` or + ``self.admin_site.name`` if your view is on a ``ModelAdmin``. .. _admin-reverse-urls: @@ -1356,37 +1378,31 @@ accessible using Django's :ref:`URL reversing system `. The :class:`AdminSite` provides the following named URL patterns: - ====================== =============================== ============= - Page URL name Parameters - ====================== =============================== ============= - Index ``admin_index`` - Logout ``admin_logout`` - Password change ``admin_password_change`` - Password change done ``admin_password_change_done`` - i18n javascript ``admin_jsi18n`` - Application index page ``admin_app_list`` ``app_label`` - ====================== =============================== ============= - -These names will be prefixed with the name of the :class:`AdminSite` instance, -plus an underscore. For example, if your :class:`AdminSite` was named -``custom``, then the Logout view would be served using a URL with the name -``custom_admin_logout``. The default :class:`AdminSite` doesn't use a prefix -in it's URL names. + ====================== ======================== ============= + Page URL name Parameters + ====================== ======================== ============= + Index ``index`` + Logout ``logout`` + Password change ``password_change`` + Password change done ``password_change_done`` + i18n javascript ``jsi18n`` + Application index page ``app_list`` ``app_label`` + ====================== ======================== ============= Each :class:`ModelAdmin` instance provides an additional set of named URLs: - ====================== ===================================================== ============= - Page URL name Parameters - ====================== ===================================================== ============= - Changelist ``admin_{{ app_label }}_{{ model_name }}_changelist`` - Add ``admin_{{ app_label }}_{{ model_name }}_add`` - History ``admin_{{ app_label }}_{{ model_name }}_history`` ``object_id`` - Delete ``admin_{{ app_label }}_{{ model_name }}_delete`` ``object_id`` - Change ``admin_{{ app_label }}_{{ model_name }}_change`` ``object_id`` - ====================== ===================================================== ============= + ====================== =============================================== ============= + Page URL name Parameters + ====================== =============================================== ============= + Changelist ``{{ app_label }}_{{ model_name }}_changelist`` + Add ``{{ app_label }}_{{ model_name }}_add`` + History ``{{ app_label }}_{{ model_name }}_history`` ``object_id`` + Delete ``{{ app_label }}_{{ model_name }}_delete`` ``object_id`` + Change ``{{ app_label }}_{{ model_name }}_change`` ``object_id`` + ====================== =============================================== ============= -Again, these names will be prefixed by the name of the :class:`AdminSite` in -which they are deployed. +These named URLs are registered with the application namespace ``admin``, and +with an instance namespace corresponding to the name of the Site instance. So - if you wanted to get a reference to the Change view for a particular ``Choice`` object (from the polls application) in the default admin, you would @@ -1394,8 +1410,16 @@ call:: >>> from django.core import urlresolvers >>> c = Choice.objects.get(...) - >>> change_url = urlresolvers.reverse('admin_polls_choice_change', args=(c.id,)) + >>> change_url = urlresolvers.reverse('admin:polls_choice_change', args=(c.id,)) -However, if the admin instance was named ``custom``, you would need to call:: +This will find the first registered instance of the admin application (whatever the instance +name), and resolve to the view for changing ``poll.Choice`` instances in that instance. - >>> change_url = urlresolvers.reverse('custom_admin_polls_choice_change', args=(c.id,)) +If you want to find a URL in a specific admin instance, provide the name of that instance +as a ``current_app`` hint to the reverse call. For example, if you specifically wanted +the admin view from the admin instance named ``custom``, you would need to call:: + + >>> change_url = urlresolvers.reverse('custom:polls_choice_change', args=(c.id,)) + +For more details, see the documentation on :ref:`reversing namespaced URLs +`. diff --git a/docs/ref/contrib/contenttypes.txt b/docs/ref/contrib/contenttypes.txt index 94900b3892..8a926afc97 100644 --- a/docs/ref/contrib/contenttypes.txt +++ b/docs/ref/contrib/contenttypes.txt @@ -177,9 +177,9 @@ The ``ContentTypeManager`` .. method:: models.ContentTypeManager.clear_cache() Clears an internal cache used by - :class:`~django.contrib.contenttypes.models.ContentType>` to keep track + :class:`~django.contrib.contenttypes.models.ContentType` to keep track of which models for which it has created - :class:`django.contrib.contenttypes.models.ContentType>` instances. You + :class:`django.contrib.contenttypes.models.ContentType` instances. You probably won't ever need to call this method yourself; Django will call it automatically when it's needed. diff --git a/docs/ref/forms/fields.txt b/docs/ref/forms/fields.txt index e532971179..4bb6a7c444 100644 --- a/docs/ref/forms/fields.txt +++ b/docs/ref/forms/fields.txt @@ -275,7 +275,7 @@ For each field, we describe the default widget used if you don't specify * Default widget: ``CheckboxInput`` * Empty value: ``False`` * Normalizes to: A Python ``True`` or ``False`` value. - * Validates that the check box is checked (i.e. the value is ``True``) if + * Validates that the value is ``True`` (e.g. the check box is checked) if the field has ``required=True``. * Error message keys: ``required`` @@ -287,9 +287,10 @@ For each field, we describe the default widget used if you don't specify .. note:: Since all ``Field`` subclasses have ``required=True`` by default, the - validation condition here is important. If you want to include a checkbox - in your form that can be either checked or unchecked, you must remember to - pass in ``required=False`` when creating the ``BooleanField``. + validation condition here is important. If you want to include a boolean + in your form that can be either ``True`` or ``False`` (e.g. a checked or + unchecked checkbox), you must remember to pass in ``required=False`` when + creating the ``BooleanField``. ``CharField`` ~~~~~~~~~~~~~ @@ -328,7 +329,7 @@ Takes one extra required argument: An iterable (e.g., a list or tuple) of 2-tuples to use as choices for this field. - + ``TypedChoiceField`` ~~~~~~~~~~~~~~~~~~~~ @@ -437,7 +438,7 @@ If no ``input_formats`` argument is provided, the default input formats are:: ``min_value``, ``max_digits``, ``max_decimal_places``, ``max_whole_digits`` -Takes four optional arguments: +Takes four optional arguments: .. attribute:: DecimalField.max_value .. attribute:: DecimalField.min_value @@ -449,7 +450,7 @@ Takes four optional arguments: The maximum number of digits (those before the decimal point plus those after the decimal point, with leading zeros stripped) permitted in the value. - + .. attribute:: DecimalField.decimal_places The maximum number of decimal places permitted. @@ -522,18 +523,18 @@ extra arguments; only ``path`` is required: A regular expression pattern; only files with names matching this expression will be allowed as choices. -``FloatField`` -~~~~~~~~~~~~~~ +``FloatField`` +~~~~~~~~~~~~~~ - * Default widget: ``TextInput`` - * Empty value: ``None`` - * Normalizes to: A Python float. - * Validates that the given value is an float. Leading and trailing - whitespace is allowed, as in Python's ``float()`` function. - * Error message keys: ``required``, ``invalid``, ``max_value``, - ``min_value`` - -Takes two optional arguments for validation, ``max_value`` and ``min_value``. + * Default widget: ``TextInput`` + * Empty value: ``None`` + * Normalizes to: A Python float. + * Validates that the given value is an float. Leading and trailing + whitespace is allowed, as in Python's ``float()`` function. + * Error message keys: ``required``, ``invalid``, ``max_value``, + ``min_value`` + +Takes two optional arguments for validation, ``max_value`` and ``min_value``. These control the range of values permitted in the field. ``ImageField`` @@ -779,10 +780,10 @@ example:: (which is ``"---------"`` by default) with the ``empty_label`` attribute, or you can disable the empty label entirely by setting ``empty_label`` to ``None``:: - + # A custom empty label field1 = forms.ModelChoiceField(queryset=..., empty_label="(Nothing)") - + # No empty label field2 = forms.ModelChoiceField(queryset=..., empty_label=None) diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index 348486b341..f78ebc506a 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -668,7 +668,7 @@ of the arguments is required, but you should use at least one of them. The resulting SQL of the above example would be:: - SELECT blog_blog.*, (SELECT COUNT(*) FROM blog_entry WHERE blog_entry.blog_id = blog_blog.id) + SELECT blog_blog.*, (SELECT COUNT(*) FROM blog_entry WHERE blog_entry.blog_id = blog_blog.id) AS entry_count FROM blog_blog; Note that the parenthesis required by most database engines around diff --git a/docs/ref/templates/api.txt b/docs/ref/templates/api.txt index 05097b7e59..e3260a96f8 100644 --- a/docs/ref/templates/api.txt +++ b/docs/ref/templates/api.txt @@ -86,9 +86,16 @@ Rendering a context Once you have a compiled ``Template`` object, you can render a context -- or multiple contexts -- with it. The ``Context`` class lives at -``django.template.Context``, and the constructor takes one (optional) -argument: a dictionary mapping variable names to variable values. Call the -``Template`` object's ``render()`` method with the context to "fill" the +``django.template.Context``, and the constructor takes two (optional) +arguments: + + * A dictionary mapping variable names to variable values. + + * The name of the current application. This application name is used + to help :ref:`resolve namespaced URLs`. + If you're not using namespaced URLs, you can ignore this argument. + +Call the ``Template`` object's ``render()`` method with the context to "fill" the template:: >>> from django.template import Context, Template @@ -549,13 +556,13 @@ Here are the template loaders that come with Django: Note that the loader performs an optimization when it is first imported: It caches a list of which :setting:`INSTALLED_APPS` packages have a ``templates`` subdirectory. - + This loader is enabled by default. ``django.template.loaders.eggs.load_template_source`` Just like ``app_directories`` above, but it loads templates from Python eggs rather than from the filesystem. - + This loader is disabled by default. Django uses the template loaders in order according to the diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index aedad6562f..a2f8b9f8b3 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -795,6 +795,16 @@ missing. In practice you'll use this to link to views that are optional:: Link to optional stuff {% endif %} +.. versionadded:: 1.1 + +If you'd like to retrieve a namespaced URL, specify the fully qualified name:: + + {% url myapp:view-name %} + +This will follow the normal :ref:`namespaced URL resolution strategy +`, including using any hints provided +by the context as to the current application. + .. templatetag:: widthratio widthratio diff --git a/docs/topics/forms/formsets.txt b/docs/topics/forms/formsets.txt index 8e90b54ced..e6146aeaba 100644 --- a/docs/topics/forms/formsets.txt +++ b/docs/topics/forms/formsets.txt @@ -86,9 +86,9 @@ displayed. Formset validation ------------------ -Validation with a formset is about identical to a regular ``Form``. There is +Validation with a formset is almost identical to a regular ``Form``. There is an ``is_valid`` method on the formset to provide a convenient way to validate -each form in the formset:: +all forms in the formset:: >>> ArticleFormSet = formset_factory(ArticleForm) >>> formset = ArticleFormSet({}) @@ -97,22 +97,25 @@ each form in the formset:: We passed in no data to the formset which is resulting in a valid form. The formset is smart enough to ignore extra forms that were not changed. If we -attempt to provide an article, but fail to do so:: +provide an invalid article:: >>> data = { - ... 'form-TOTAL_FORMS': u'1', - ... 'form-INITIAL_FORMS': u'1', + ... 'form-TOTAL_FORMS': u'2', + ... 'form-INITIAL_FORMS': u'0', ... 'form-0-title': u'Test', - ... 'form-0-pub_date': u'', + ... 'form-0-pub_date': u'16 June 1904', + ... 'form-1-title': u'Test', + ... 'form-1-pub_date': u'', # <-- this date is missing but required ... } >>> formset = ArticleFormSet(data) >>> formset.is_valid() False >>> formset.errors - [{'pub_date': [u'This field is required.']}] + [{}, {'pub_date': [u'This field is required.']}] -As we can see the formset properly performed validation and gave us the -expected errors. +As we can see, ``formset.errors`` is a list whose entries correspond to the +forms in the formset. Validation was performed for each of the two forms, and +the expected error message appears for the second item. .. _understanding-the-managementform: @@ -155,20 +158,40 @@ Custom formset validation ~~~~~~~~~~~~~~~~~~~~~~~~~ A formset has a ``clean`` method similar to the one on a ``Form`` class. This -is where you define your own validation that deals at the formset level:: +is where you define your own validation that works at the formset level:: >>> from django.forms.formsets import BaseFormSet >>> class BaseArticleFormSet(BaseFormSet): ... def clean(self): - ... raise forms.ValidationError, u'An error occured.' + ... """Checks that no two articles have the same title.""" + ... if any(self.errors): + ... # Don't bother validating the formset unless each form is valid on its own + ... return + ... titles = [] + ... for i in range(0, self.total_form_count()): + ... form = self.forms[i] + ... title = form.cleaned_data['title'] + ... if title in titles: + ... raise forms.ValidationError, "Articles in a set must have distinct titles." + ... titles.append(title) >>> ArticleFormSet = formset_factory(ArticleForm, formset=BaseArticleFormSet) - >>> formset = ArticleFormSet({}) + >>> data = { + ... 'form-TOTAL_FORMS': u'2', + ... 'form-INITIAL_FORMS': u'0', + ... 'form-0-title': u'Test', + ... 'form-0-pub_date': u'16 June 1904', + ... 'form-1-title': u'Test', + ... 'form-1-pub_date': u'23 June 1912', + ... } + >>> formset = ArticleFormSet(data) >>> formset.is_valid() False + >>> formset.errors + [{}, {}] >>> formset.non_form_errors() - [u'An error occured.'] + [u'Articles in a set must have distinct titles.'] The formset ``clean`` method is called after all the ``Form.clean`` methods have been called. The errors will be found using the ``non_form_errors()`` diff --git a/docs/topics/http/urls.txt b/docs/topics/http/urls.txt index 4248d4f02e..b2e99dce7f 100644 --- a/docs/topics/http/urls.txt +++ b/docs/topics/http/urls.txt @@ -4,6 +4,8 @@ URL dispatcher ============== +.. module:: django.core.urlresolvers + A clean, elegant URL scheme is an important detail in a high-quality Web application. Django lets you design URLs however you want, with no framework limitations. @@ -40,14 +42,14 @@ algorithm the system follows to determine which Python code to execute: this is the value of the ``ROOT_URLCONF`` setting, but if the incoming ``HttpRequest`` object has an attribute called ``urlconf``, its value will be used in place of the ``ROOT_URLCONF`` setting. - + 2. Django loads that Python module and looks for the variable ``urlpatterns``. This should be a Python list, in the format returned by the function ``django.conf.urls.defaults.patterns()``. - + 3. Django runs through each URL pattern, in order, and stops at the first one that matches the requested URL. - + 4. Once one of the regexes matches, Django imports and calls the given view, which is a simple Python function. The view gets passed an :class:`~django.http.HttpRequest` as its first argument and any values @@ -182,11 +184,13 @@ your URLconf. This gives your module access to these objects: patterns -------- +.. function:: patterns(prefix, pattern_description, ...) + A function that takes a prefix, and an arbitrary number of URL patterns, and returns a list of URL patterns in the format Django needs. The first argument to ``patterns()`` is a string ``prefix``. See -"The view prefix" below. +`The view prefix`_ below. The remaining arguments should be tuples in this format:: @@ -222,6 +226,8 @@ url .. versionadded:: 1.0 +.. function:: url(regex, view, kwargs=None, name=None, prefix='') + You can use the ``url()`` function, instead of a tuple, as an argument to ``patterns()``. This is convenient if you want to specify a name without the optional extra arguments dictionary. For example:: @@ -244,6 +250,8 @@ The ``prefix`` parameter has the same meaning as the first argument to handler404 ---------- +.. data:: handler404 + A string representing the full Python import path to the view that should be called if none of the URL patterns match. @@ -253,6 +261,8 @@ value should suffice. handler500 ---------- +.. data:: handler500 + A string representing the full Python import path to the view that should be called in case of server errors. Server errors happen when you have runtime errors in view code. @@ -263,8 +273,17 @@ value should suffice. include ------- -A function that takes a full Python import path to another URLconf that should -be "included" in this place. See `Including other URLconfs`_ below. +.. function:: include() + +A function that takes a full Python import path to another URLconf module that +should be "included" in this place. + +.. versionadded:: 1.1 + +:func:`include` also accepts as an argument an iterable that returns URL +patterns. + +See `Including other URLconfs`_ below. Notes on capturing text in URLs =============================== @@ -391,6 +410,32 @@ Django encounters ``include()``, it chops off whatever part of the URL matched up to that point and sends the remaining string to the included URLconf for further processing. +.. versionadded:: 1.1 + +Another possibility is to include additional URL patterns not by specifying the +URLconf Python module defining them as the `include`_ argument but by using +directly the pattern list as returned by `patterns`_ instead. For example:: + + from django.conf.urls.defaults import * + + extra_patterns = patterns('', + url(r'reports/(?P\d+)/$', 'credit.views.report', name='credit-reports'), + url(r'charge/$', 'credit.views.charge', name='credit-charge'), + ) + + urlpatterns = patterns('', + url(r'^$', 'apps.main.views.homepage', name='site-homepage'), + (r'^help/', include('apps.help.urls')), + (r'^credit/', include(extra_patterns)), + ) + +This approach can be seen in use when you deploy an instance of the Django +Admin application. The Django Admin is deployed as instances of a +:class:`AdminSite`; each :class:`AdminSite` instance has an attribute +``urls`` that returns the url patterns available to that instance. It is this +attribute that you ``include()`` into your projects ``urlpatterns`` when you +deploy the admin instance. + .. _`Django Web site`: http://www.djangoproject.com/ Captured parameters @@ -413,6 +458,58 @@ the following example is valid:: In the above example, the captured ``"username"`` variable is passed to the included URLconf, as expected. +.. _topics-http-defining-url-namespaces: + +Defining URL Namespaces +----------------------- + +When you need to deploy multiple instances of a single application, it can be +helpful to be able to differentiate between instances. This is especially +important when using _`named URL patterns `, since +multiple instances of a single application will share named URLs. Namespaces +provide a way to tell these named URLs apart. + +A URL namespace comes in two parts, both of which are strings: + + * An **application namespace**. This describes the name of the application + that is being deployed. Every instance of a single application will have + the same application namespace. For example, Django's admin application + has the somewhat predictable application namespace of ``admin``. + + * An **instance namespace**. This identifies a specific instance of an + application. Instance namespaces should be unique across your entire + project. However, an instance namespace can be the same as the + application namespace. This is used to specify a default instance of an + application. For example, the default Django Admin instance has an + instance namespace of ``admin``. + +URL Namespaces can be specified in two ways. + +Firstly, you can provide the application and instance namespace as arguments +to ``include()`` when you construct your URL patterns. For example,:: + + (r'^help/', include('apps.help.urls', namespace='foo', app_name='bar')), + +This will include the URLs defined in ``apps.help.urls`` into the application +namespace ``bar``, with the instance namespace ``foo``. + +Secondly, you can include an object that contains embedded namespace data. If +you ``include()`` a ``patterns`` object, that object will be added to the +global namespace. However, you can also ``include()`` an object that contains +a 3-tuple containing:: + + (, , ) + +This will include the nominated URL patterns into the given application and +instance namespace. For example, the ``urls`` attribute of Django's +:class:`AdminSite` object returns a 3-tuple that contains all the patterns in +an admin site, plus the name of the admin instance, and the application +namespace ``admin``. + +Once you have defined namespaced URLs, you can reverse them. For details on +reversing namespaced urls, see the documentation on :ref:`reversing namespaced +URLs `. + Passing extra options to view functions ======================================= @@ -587,6 +684,86 @@ not restricted to valid Python names. name, will decrease the chances of collision. We recommend something like ``myapp-comment`` instead of ``comment``. +.. _topics-http-reversing-url-namespaces: + +URL namespaces +-------------- + +.. versionadded:: 1.1 + +Namespaced URLs are specified using the ``:`` operator. For example, the main +index page of the admin application is referenced using ``admin:index``. This +indicates a namespace of ``admin``, and a named URL of ``index``. + +Namespaces can also be nested. The named URL ``foo:bar:whiz`` would look for +a pattern named ``whiz`` in the namespace ``bar`` that is itself defined within +the top-level namespace ``foo``. + +When given a namespaced URL (e.g. ``myapp:index``) to resolve, Django splits +the fully qualified name into parts, and then tries the following lookup: + + 1. First, Django looks for a matching application namespace (in this + example, ``myapp``). This will yield a list of instances of that + application. + + 2. If there is a ``current`` application defined, Django finds and returns + the URL resolver for that instance. The ``current`` can be specified + as an attribute on the template context - applications that expect to + have multiple deployments should set the ``current_app`` attribute on + any ``Context`` or ``RequestContext`` that is used to render a + template. + + The current application can also be specified manually as an argument + to the :func:`reverse()` function. + + 3. If there is no current application. Django looks for a default + application instance. The default application instance is the instance + that has an instance namespace matching the application namespace (in + this example, an instance of the ``myapp`` called ``myapp``). + + 4. If there is no default application instance, Django will pick the first + deployed instance of the application, whatever its instance name may be. + + 5. If the provided namespace doesn't match an application namespace in + step 2, Django will attempt a direct lookup of the namespace as an + instance namespace. + +If there are nested namespaces, these steps are repeated for each part of the +namespace until only the view name is unresolved. The view name will then be +resolved into a URL in the namespace that has been found. + +To show this resolution strategy in action, consider an example of two instances +of ``myapp``: one called ``foo``, and one called ``bar``. ``myapp`` has a main +index page with a URL named `index`. Using this setup, the following lookups are +possible: + + * If one of the instances is current - say, if we were rendering a utility page + in the instance ``bar`` - ``myapp:index`` will resolve to the index page of + the instance ``bar``. + + * If there is no current instance - say, if we were rendering a page + somewhere else on the site - ``myapp:index`` will resolve to the first + registered instance of ``myapp``. Since there is no default instance, + the first instance of ``myapp`` that is registered will be used. This could + be ``foo`` or ``bar``, depending on the order they are introduced into the + urlpatterns of the project. + + * ``foo:index`` will always resolve to the index page of the instance ``foo``. + +If there was also a default instance - i.e., an instance named `myapp` - the +following would happen: + + * If one of the instances is current - say, if we were rendering a utility page + in the instance ``bar`` - ``myapp:index`` will resolve to the index page of + the instance ``bar``. + + * If there is no current instance - say, if we were rendering a page somewhere + else on the site - ``myapp:index`` will resolve to the index page of the + default instance. + + * ``foo:index`` will again resolve to the index page of the instance ``foo``. + + Utility methods =============== @@ -597,8 +774,7 @@ If you need to use something similar to the :ttag:`url` template tag in your code, Django provides the following method (in the ``django.core.urlresolvers`` module): -.. currentmodule:: django.core.urlresolvers -.. function:: reverse(viewname, urlconf=None, args=None, kwargs=None) +.. function:: reverse(viewname, urlconf=None, args=None, kwargs=None, current_app=None) ``viewname`` is either the function name (either a function reference, or the string version of the name, if you used that form in ``urlpatterns``) or the @@ -620,6 +796,14 @@ vertical bar (``"|"``) character. You can quite happily use such patterns for matching against incoming URLs and sending them off to views, but you cannot reverse such patterns. +.. versionadded:: 1.1 + +The ``current_app`` argument allows you to provide a hint to the resolver +indicating the application to which the currently executing view belongs. +This ``current_app`` argument is used as a hint to resolve application +namespaces into URLs on specific application instances, according to the +:ref:`namespaced URL resolution strategy `. + .. admonition:: Make sure your views are all correct As part of working out which URL names map to which patterns, the @@ -639,7 +823,6 @@ resolve() The :func:`django.core.urlresolvers.resolve` function can be used for resolving URL paths to the corresponding view functions. It has the following signature: -.. currentmodule:: django.core.urlresolvers .. function:: resolve(path, urlconf=None) ``path`` is the URL path you want to resolve. As with ``reverse()`` above, you diff --git a/docs/topics/i18n.txt b/docs/topics/i18n.txt index 7bf51c11c5..c5f4ab6481 100644 --- a/docs/topics/i18n.txt +++ b/docs/topics/i18n.txt @@ -959,11 +959,11 @@ Using the JavaScript translation catalog To use the catalog, just pull in the dynamically generated script like this:: - + -This is how the admin fetches the translation catalog from the server. When the -catalog is loaded, your JavaScript code can use the standard ``gettext`` -interface to access it:: +This uses reverse URL lookup to find the URL of the JavaScript catalog view. +When the catalog is loaded, your JavaScript code can use the standard +``gettext`` interface to access it:: document.write(gettext('this is to be translated')); diff --git a/docs/topics/testing.txt b/docs/topics/testing.txt index d235e0deba..cec6002b7b 100644 --- a/docs/topics/testing.txt +++ b/docs/topics/testing.txt @@ -686,7 +686,13 @@ arguments at time of construction: user accounts that are valid on your production site will not work under test conditions. You'll need to create users as part of the test suite -- either manually (using the Django model API) or with a test - fixture. + fixture. Remember that if you want your test user to have a password, + you can't set the user's password by setting the password attribute + directly -- you must use the + :meth:`~django.contrib.auth.models.User.set_password()` function to + store a correctly hashed password. Alternatively, you can use the + :meth:`~django.contrib.auth.models.UserManager.create_user` helper + method to create a new user with a correctly hashed password. .. method:: Client.logout() diff --git a/tests/regressiontests/admin_views/customadmin.py b/tests/regressiontests/admin_views/customadmin.py index 70e87ebcfe..80570ea51d 100644 --- a/tests/regressiontests/admin_views/customadmin.py +++ b/tests/regressiontests/admin_views/customadmin.py @@ -10,19 +10,19 @@ import models class Admin2(admin.AdminSite): login_template = 'custom_admin/login.html' index_template = 'custom_admin/index.html' - + # A custom index view. def index(self, request, extra_context=None): return super(Admin2, self).index(request, {'foo': '*bar*'}) - + def get_urls(self): return patterns('', (r'^my_view/$', self.admin_view(self.my_view)), ) + super(Admin2, self).get_urls() - + def my_view(self, request): return HttpResponse("Django is a magical pony!") - + site = Admin2(name="admin2") site.register(models.Article, models.ArticleAdmin) diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py index 99168fdeee..aafa303cec 100644 --- a/tests/regressiontests/admin_views/tests.py +++ b/tests/regressiontests/admin_views/tests.py @@ -10,6 +10,7 @@ from django.contrib.admin.models import LogEntry, DELETION from django.contrib.admin.sites import LOGIN_FORM_KEY from django.contrib.admin.util import quote from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME +from django.utils.cache import get_max_age from django.utils.html import escape # local test models @@ -204,6 +205,11 @@ class AdminViewBasicTest(TestCase): response = self.client.get('/test_admin/%s/admin_views/thing/' % self.urlbit, {'color__id__exact': 'StringNotInteger!'}) self.assertRedirects(response, '/test_admin/%s/admin_views/thing/?e=1' % self.urlbit) + def testLogoutAndPasswordChangeURLs(self): + response = self.client.get('/test_admin/%s/admin_views/article/' % self.urlbit) + self.failIf('' % self.urlbit not in response.content) + self.failIf('' % self.urlbit not in response.content) + def testNamedGroupFieldChoicesChangeList(self): """ Ensures the admin changelist shows correct values in the relevant column @@ -1527,3 +1533,76 @@ class AdminInlineTests(TestCase): self.failUnlessEqual(Category.objects.get(id=2).order, 13) self.failUnlessEqual(Category.objects.get(id=3).order, 1) self.failUnlessEqual(Category.objects.get(id=4).order, 0) + + +class NeverCacheTests(TestCase): + fixtures = ['admin-views-users.xml', 'admin-views-colors.xml', 'admin-views-fabrics.xml'] + + def setUp(self): + self.client.login(username='super', password='secret') + + def tearDown(self): + self.client.logout() + + def testAdminIndex(self): + "Check the never-cache status of the main index" + response = self.client.get('/test_admin/admin/') + self.failUnlessEqual(get_max_age(response), 0) + + def testAppIndex(self): + "Check the never-cache status of an application index" + response = self.client.get('/test_admin/admin/admin_views/') + self.failUnlessEqual(get_max_age(response), 0) + + def testModelIndex(self): + "Check the never-cache status of a model index" + response = self.client.get('/test_admin/admin/admin_views/fabric/') + self.failUnlessEqual(get_max_age(response), 0) + + def testModelAdd(self): + "Check the never-cache status of a model add page" + response = self.client.get('/test_admin/admin/admin_views/fabric/add/') + self.failUnlessEqual(get_max_age(response), 0) + + def testModelView(self): + "Check the never-cache status of a model edit page" + response = self.client.get('/test_admin/admin/admin_views/section/1/') + self.failUnlessEqual(get_max_age(response), 0) + + def testModelHistory(self): + "Check the never-cache status of a model history page" + response = self.client.get('/test_admin/admin/admin_views/section/1/history/') + self.failUnlessEqual(get_max_age(response), 0) + + def testModelDelete(self): + "Check the never-cache status of a model delete page" + response = self.client.get('/test_admin/admin/admin_views/section/1/delete/') + self.failUnlessEqual(get_max_age(response), 0) + + def testLogin(self): + "Check the never-cache status of login views" + self.client.logout() + response = self.client.get('/test_admin/admin/') + self.failUnlessEqual(get_max_age(response), 0) + + def testLogout(self): + "Check the never-cache status of logout view" + response = self.client.get('/test_admin/admin/logout/') + self.failUnlessEqual(get_max_age(response), 0) + + def testPasswordChange(self): + "Check the never-cache status of the password change view" + self.client.logout() + response = self.client.get('/test_admin/password_change/') + self.failUnlessEqual(get_max_age(response), None) + + def testPasswordChangeDone(self): + "Check the never-cache status of the password change done view" + response = self.client.get('/test_admin/admin/password_change/done/') + self.failUnlessEqual(get_max_age(response), None) + + def testJsi18n(self): + "Check the never-cache status of the Javascript i18n view" + response = self.client.get('/test_admin/jsi18n/') + self.failUnlessEqual(get_max_age(response), None) + diff --git a/tests/regressiontests/admin_widgets/widgetadmin.py b/tests/regressiontests/admin_widgets/widgetadmin.py index bd68954a70..9257c306c9 100644 --- a/tests/regressiontests/admin_widgets/widgetadmin.py +++ b/tests/regressiontests/admin_widgets/widgetadmin.py @@ -19,7 +19,7 @@ class CarTireAdmin(admin.ModelAdmin): return db_field.formfield(**kwargs) return super(CarTireAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs) -site = WidgetAdmin() +site = WidgetAdmin(name='widget-admin') site.register(models.User) site.register(models.Car, CarAdmin) diff --git a/tests/regressiontests/m2m_through_regress/fixtures/m2m_through.json b/tests/regressiontests/m2m_through_regress/fixtures/m2m_through.json new file mode 100644 index 0000000000..6f24886f02 --- /dev/null +++ b/tests/regressiontests/m2m_through_regress/fixtures/m2m_through.json @@ -0,0 +1,34 @@ +[ + { + "pk": "1", + "model": "m2m_through_regress.person", + "fields": { + "name": "Guido" + } + }, + { + "pk": "1", + "model": "auth.user", + "fields": { + "username": "Guido", + "email": "bdfl@python.org", + "password": "abcde" + } + }, + { + "pk": "1", + "model": "m2m_through_regress.group", + "fields": { + "name": "Python Core Group" + } + }, + { + "pk": "1", + "model": "m2m_through_regress.usermembership", + "fields": { + "user": "1", + "group": "1", + "price": "100" + } + } +] \ No newline at end of file diff --git a/tests/regressiontests/m2m_through_regress/models.py b/tests/regressiontests/m2m_through_regress/models.py index 8594f19dee..dcf5f8115b 100644 --- a/tests/regressiontests/m2m_through_regress/models.py +++ b/tests/regressiontests/m2m_through_regress/models.py @@ -12,7 +12,9 @@ class Membership(models.Model): def __unicode__(self): return "%s is a member of %s" % (self.person.name, self.group.name) +# using custom id column to test ticket #11107 class UserMembership(models.Model): + id = models.AutoField(db_column='usermembership_id', primary_key=True) user = models.ForeignKey(User) group = models.ForeignKey('Group') price = models.IntegerField(default=100) @@ -196,4 +198,12 @@ doing a join. # Flush the database, just to make sure we can. >>> management.call_command('flush', verbosity=0, interactive=False) +## Regression test for #11107 +Ensure that sequences on m2m_through tables are being created for the through +model, not for a phantom auto-generated m2m table. + +>>> management.call_command('loaddata', 'm2m_through', verbosity=0) +>>> management.call_command('dumpdata', 'm2m_through_regress', format='json') +[{"pk": 1, "model": "m2m_through_regress.usermembership", "fields": {"price": 100, "group": 1, "user": 1}}, {"pk": 1, "model": "m2m_through_regress.person", "fields": {"name": "Guido"}}, {"pk": 1, "model": "m2m_through_regress.group", "fields": {"name": "Python Core Group"}}] + """} diff --git a/tests/regressiontests/urlpatterns_reverse/included_namespace_urls.py b/tests/regressiontests/urlpatterns_reverse/included_namespace_urls.py new file mode 100644 index 0000000000..073190657c --- /dev/null +++ b/tests/regressiontests/urlpatterns_reverse/included_namespace_urls.py @@ -0,0 +1,13 @@ +from django.conf.urls.defaults import * +from namespace_urls import URLObject + +testobj3 = URLObject('testapp', 'test-ns3') + +urlpatterns = patterns('regressiontests.urlpatterns_reverse.views', + url(r'^normal/$', 'empty_view', name='inc-normal-view'), + url(r'^normal/(?P\d+)/(?P\d+)/$', 'empty_view', name='inc-normal-view'), + + (r'^test3/', include(testobj3.urls)), + (r'^ns-included3/', include('regressiontests.urlpatterns_reverse.included_urls', namespace='inc-ns3')), +) + diff --git a/tests/regressiontests/urlpatterns_reverse/namespace_urls.py b/tests/regressiontests/urlpatterns_reverse/namespace_urls.py new file mode 100644 index 0000000000..27cc7f7a22 --- /dev/null +++ b/tests/regressiontests/urlpatterns_reverse/namespace_urls.py @@ -0,0 +1,38 @@ +from django.conf.urls.defaults import * + +class URLObject(object): + def __init__(self, app_name, namespace): + self.app_name = app_name + self.namespace = namespace + + def urls(self): + return patterns('', + url(r'^inner/$', 'empty_view', name='urlobject-view'), + url(r'^inner/(?P\d+)/(?P\d+)/$', 'empty_view', name='urlobject-view'), + ), self.app_name, self.namespace + urls = property(urls) + +testobj1 = URLObject('testapp', 'test-ns1') +testobj2 = URLObject('testapp', 'test-ns2') +default_testobj = URLObject('testapp', 'testapp') + +otherobj1 = URLObject('nodefault', 'other-ns1') +otherobj2 = URLObject('nodefault', 'other-ns2') + +urlpatterns = patterns('regressiontests.urlpatterns_reverse.views', + url(r'^normal/$', 'empty_view', name='normal-view'), + url(r'^normal/(?P\d+)/(?P\d+)/$', 'empty_view', name='normal-view'), + + (r'^test1/', include(testobj1.urls)), + (r'^test2/', include(testobj2.urls)), + (r'^default/', include(default_testobj.urls)), + + (r'^other1/', include(otherobj1.urls)), + (r'^other2/', include(otherobj2.urls)), + + (r'^ns-included1/', include('regressiontests.urlpatterns_reverse.included_namespace_urls', namespace='inc-ns1')), + (r'^ns-included2/', include('regressiontests.urlpatterns_reverse.included_namespace_urls', namespace='inc-ns2')), + + (r'^included/', include('regressiontests.urlpatterns_reverse.included_namespace_urls')), + +) diff --git a/tests/regressiontests/urlpatterns_reverse/tests.py b/tests/regressiontests/urlpatterns_reverse/tests.py index 9def6b2eb2..d4f281ba81 100644 --- a/tests/regressiontests/urlpatterns_reverse/tests.py +++ b/tests/regressiontests/urlpatterns_reverse/tests.py @@ -158,4 +158,84 @@ class ReverseShortcutTests(TestCase): res = redirect('/foo/') self.assertEqual(res['Location'], '/foo/') res = redirect('http://example.com/') - self.assertEqual(res['Location'], 'http://example.com/') \ No newline at end of file + self.assertEqual(res['Location'], 'http://example.com/') + + +class NamespaceTests(TestCase): + urls = 'regressiontests.urlpatterns_reverse.namespace_urls' + + def test_ambiguous_object(self): + "Names deployed via dynamic URL objects that require namespaces can't be resolved" + self.assertRaises(NoReverseMatch, reverse, 'urlobject-view') + self.assertRaises(NoReverseMatch, reverse, 'urlobject-view', args=[37,42]) + self.assertRaises(NoReverseMatch, reverse, 'urlobject-view', kwargs={'arg1':42, 'arg2':37}) + + def test_ambiguous_urlpattern(self): + "Names deployed via dynamic URL objects that require namespaces can't be resolved" + self.assertRaises(NoReverseMatch, reverse, 'inner-nothing') + self.assertRaises(NoReverseMatch, reverse, 'inner-nothing', args=[37,42]) + self.assertRaises(NoReverseMatch, reverse, 'inner-nothing', kwargs={'arg1':42, 'arg2':37}) + + def test_non_existent_namespace(self): + "Non-existent namespaces raise errors" + self.assertRaises(NoReverseMatch, reverse, 'blahblah:urlobject-view') + self.assertRaises(NoReverseMatch, reverse, 'test-ns1:blahblah:urlobject-view') + + def test_normal_name(self): + "Normal lookups work as expected" + self.assertEquals('/normal/', reverse('normal-view')) + self.assertEquals('/normal/37/42/', reverse('normal-view', args=[37,42])) + self.assertEquals('/normal/42/37/', reverse('normal-view', kwargs={'arg1':42, 'arg2':37})) + + def test_simple_included_name(self): + "Normal lookups work on names included from other patterns" + self.assertEquals('/included/normal/', reverse('inc-normal-view')) + self.assertEquals('/included/normal/37/42/', reverse('inc-normal-view', args=[37,42])) + self.assertEquals('/included/normal/42/37/', reverse('inc-normal-view', kwargs={'arg1':42, 'arg2':37})) + + def test_namespace_object(self): + "Dynamic URL objects can be found using a namespace" + self.assertEquals('/test1/inner/', reverse('test-ns1:urlobject-view')) + self.assertEquals('/test1/inner/37/42/', reverse('test-ns1:urlobject-view', args=[37,42])) + self.assertEquals('/test1/inner/42/37/', reverse('test-ns1:urlobject-view', kwargs={'arg1':42, 'arg2':37})) + + def test_embedded_namespace_object(self): + "Namespaces can be installed anywhere in the URL pattern tree" + self.assertEquals('/included/test3/inner/', reverse('test-ns3:urlobject-view')) + self.assertEquals('/included/test3/inner/37/42/', reverse('test-ns3:urlobject-view', args=[37,42])) + self.assertEquals('/included/test3/inner/42/37/', reverse('test-ns3:urlobject-view', kwargs={'arg1':42, 'arg2':37})) + + def test_namespace_pattern(self): + "Namespaces can be applied to include()'d urlpatterns" + self.assertEquals('/ns-included1/normal/', reverse('inc-ns1:inc-normal-view')) + self.assertEquals('/ns-included1/normal/37/42/', reverse('inc-ns1:inc-normal-view', args=[37,42])) + self.assertEquals('/ns-included1/normal/42/37/', reverse('inc-ns1:inc-normal-view', kwargs={'arg1':42, 'arg2':37})) + + def test_multiple_namespace_pattern(self): + "Namespaces can be embedded" + self.assertEquals('/ns-included1/test3/inner/', reverse('inc-ns1:test-ns3:urlobject-view')) + self.assertEquals('/ns-included1/test3/inner/37/42/', reverse('inc-ns1:test-ns3:urlobject-view', args=[37,42])) + self.assertEquals('/ns-included1/test3/inner/42/37/', reverse('inc-ns1:test-ns3:urlobject-view', kwargs={'arg1':42, 'arg2':37})) + + def test_app_lookup_object(self): + "A default application namespace can be used for lookup" + self.assertEquals('/default/inner/', reverse('testapp:urlobject-view')) + self.assertEquals('/default/inner/37/42/', reverse('testapp:urlobject-view', args=[37,42])) + self.assertEquals('/default/inner/42/37/', reverse('testapp:urlobject-view', kwargs={'arg1':42, 'arg2':37})) + + def test_app_lookup_object_with_default(self): + "A default application namespace is sensitive to the 'current' app can be used for lookup" + self.assertEquals('/included/test3/inner/', reverse('testapp:urlobject-view', current_app='test-ns3')) + self.assertEquals('/included/test3/inner/37/42/', reverse('testapp:urlobject-view', args=[37,42], current_app='test-ns3')) + self.assertEquals('/included/test3/inner/42/37/', reverse('testapp:urlobject-view', kwargs={'arg1':42, 'arg2':37}, current_app='test-ns3')) + + def test_app_lookup_object_without_default(self): + "An application namespace without a default is sensitive to the 'current' app can be used for lookup" + self.assertEquals('/other2/inner/', reverse('nodefault:urlobject-view')) + self.assertEquals('/other2/inner/37/42/', reverse('nodefault:urlobject-view', args=[37,42])) + self.assertEquals('/other2/inner/42/37/', reverse('nodefault:urlobject-view', kwargs={'arg1':42, 'arg2':37})) + + self.assertEquals('/other1/inner/', reverse('nodefault:urlobject-view', current_app='other-ns1')) + self.assertEquals('/other1/inner/37/42/', reverse('nodefault:urlobject-view', args=[37,42], current_app='other-ns1')) + self.assertEquals('/other1/inner/42/37/', reverse('nodefault:urlobject-view', kwargs={'arg1':42, 'arg2':37}, current_app='other-ns1')) +