diff --git a/AUTHORS b/AUTHORS index a7ce5c488a..d34e882494 100644 --- a/AUTHORS +++ b/AUTHORS @@ -54,6 +54,7 @@ answer newbie questions, and generally made Django that much better: Jason Davies (Esaj) Alex Dedul deric@monowerks.com + dne@mayonnaise.net Jeremy Dunck Clint Ecker gandalf@owca.info diff --git a/django/conf/__init__.py b/django/conf/__init__.py index d5477201d7..a6a09d772f 100644 --- a/django/conf/__init__.py +++ b/django/conf/__init__.py @@ -116,7 +116,7 @@ class UserSettingsHolder(object): """ Holder for user configured settings. """ - # SETTINGS_MODULE does not really make sense in the manually configured + # SETTINGS_MODULE doesn't make much sense in the manually configured # (standalone) case. SETTINGS_MODULE = None @@ -135,6 +135,13 @@ class UserSettingsHolder(object): settings = LazySettings() -# install the translation machinery so that it is available -from django.utils import translation -translation.install() +# This function replaces itself with django.utils.translation.gettext() the +# first time it's run. This is necessary because the import of +# django.utils.translation requires a working settings module, and loading it +# from within this file would cause a circular import. +def first_time_gettext(*args): + from django.utils.translation import gettext + __builtins__['_'] = gettext + return gettext(*args) + +__builtins__['_'] = first_time_gettext diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py index d3afdd796a..9ef92e10dd 100644 --- a/django/conf/global_settings.py +++ b/django/conf/global_settings.py @@ -1,7 +1,9 @@ # Default Django settings. Override these with settings in the module # pointed-to by the DJANGO_SETTINGS_MODULE environment variable. -from django.utils.translation import gettext_lazy as _ +# This is defined here as a do-nothing function because we can't import +# django.utils.translation -- that module depends on the settings. +gettext_noop = lambda s: s #################### # CORE # @@ -34,34 +36,34 @@ LANGUAGE_CODE = 'en-us' # Languages we provide translations for, out of the box. The language name # should be the utf-8 encoded local name for the language. LANGUAGES = ( - ('bn', _('Bengali')), - ('cs', _('Czech')), - ('cy', _('Welsh')), - ('da', _('Danish')), - ('de', _('German')), - ('el', _('Greek')), - ('en', _('English')), - ('es', _('Spanish')), - ('es_AR', _('Argentinean Spanish')), - ('fr', _('French')), - ('gl', _('Galician')), - ('hu', _('Hungarian')), - ('he', _('Hebrew')), - ('is', _('Icelandic')), - ('it', _('Italian')), - ('ja', _('Japanese')), - ('nl', _('Dutch')), - ('no', _('Norwegian')), - ('pt-br', _('Brazilian')), - ('ro', _('Romanian')), - ('ru', _('Russian')), - ('sk', _('Slovak')), - ('sl', _('Slovenian')), - ('sr', _('Serbian')), - ('sv', _('Swedish')), - ('uk', _('Ukrainian')), - ('zh-cn', _('Simplified Chinese')), - ('zh-tw', _('Traditional Chinese')), + ('bn', gettext_noop('Bengali')), + ('cs', gettext_noop('Czech')), + ('cy', gettext_noop('Welsh')), + ('da', gettext_noop('Danish')), + ('de', gettext_noop('German')), + ('el', gettext_noop('Greek')), + ('en', gettext_noop('English')), + ('es', gettext_noop('Spanish')), + ('es_AR', gettext_noop('Argentinean Spanish')), + ('fr', gettext_noop('French')), + ('gl', gettext_noop('Galician')), + ('hu', gettext_noop('Hungarian')), + ('he', gettext_noop('Hebrew')), + ('is', gettext_noop('Icelandic')), + ('it', gettext_noop('Italian')), + ('ja', gettext_noop('Japanese')), + ('nl', gettext_noop('Dutch')), + ('no', gettext_noop('Norwegian')), + ('pt-br', gettext_noop('Brazilian')), + ('ro', gettext_noop('Romanian')), + ('ru', gettext_noop('Russian')), + ('sk', gettext_noop('Slovak')), + ('sl', gettext_noop('Slovenian')), + ('sr', gettext_noop('Serbian')), + ('sv', gettext_noop('Swedish')), + ('uk', gettext_noop('Ukrainian')), + ('zh-cn', gettext_noop('Simplified Chinese')), + ('zh-tw', gettext_noop('Traditional Chinese')), ) # Languages using BiDi (right-to-left) layout diff --git a/django/conf/locale/fr/LC_MESSAGES/django.mo b/django/conf/locale/fr/LC_MESSAGES/django.mo index d678225f2f..7b2b1f6e9e 100644 Binary files a/django/conf/locale/fr/LC_MESSAGES/django.mo and b/django/conf/locale/fr/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/fr/LC_MESSAGES/django.po b/django/conf/locale/fr/LC_MESSAGES/django.po index 7cc5070840..104e9a2aa1 100644 --- a/django/conf/locale/fr/LC_MESSAGES/django.po +++ b/django/conf/locale/fr/LC_MESSAGES/django.po @@ -514,7 +514,7 @@ msgstr "Supprimé %s." #: contrib/admin/views/main.py:343 msgid "No fields changed." -msgstr "Aucun champs modifié." +msgstr "Aucun champ modifié." #: contrib/admin/views/main.py:346 #, python-format @@ -1906,7 +1906,7 @@ msgstr "" #: db/models/fields/__init__.py:40 #, python-format msgid "%(optname)s with this %(fieldname)s already exists." -msgstr "%(optname)s avec le champs %(fieldname)s existe déjà." +msgstr "%(optname)s avec le champ %(fieldname)s existe déjà." #: db/models/fields/__init__.py:114 db/models/fields/__init__.py:265 #: db/models/fields/__init__.py:542 db/models/fields/__init__.py:553 diff --git a/django/conf/locale/gl/LC_MESSAGES/django.mo b/django/conf/locale/gl/LC_MESSAGES/django.mo index a808d2c6fc..00beabebf8 100644 Binary files a/django/conf/locale/gl/LC_MESSAGES/django.mo and b/django/conf/locale/gl/LC_MESSAGES/django.mo differ diff --git a/django/conf/locale/gl/LC_MESSAGES/django.po b/django/conf/locale/gl/LC_MESSAGES/django.po index ca4f0f3f90..394f9cd016 100644 --- a/django/conf/locale/gl/LC_MESSAGES/django.po +++ b/django/conf/locale/gl/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2006-05-16 10:11+0200\n" -"PO-Revision-Date: 2005-12-20 10:48+0100\n" +"PO-Revision-Date: 2006-07-03 14:06+0200\n" "Last-Translator: Afonso Fernández Nogueira \n" "Language-Team: Galego\n" "MIME-Version: 1.0\n" @@ -91,9 +91,8 @@ msgstr "" "comentario foi borrado\" no canto do seu contido." #: contrib/comments/models.py:91 -#, fuzzy msgid "comments" -msgstr "comentario" +msgstr "comentarios" #: contrib/comments/models.py:131 contrib/comments/models.py:207 msgid "Content object" @@ -120,21 +119,19 @@ msgstr "nome da persoa" #: contrib/comments/models.py:171 msgid "ip address" -msgstr "Enderezo IP" +msgstr "enderezo IP" #: contrib/comments/models.py:173 msgid "approved by staff" msgstr "aprobado polos moderadores" #: contrib/comments/models.py:176 -#, fuzzy msgid "free comment" -msgstr "Comentario libre" +msgstr "comentario libre" #: contrib/comments/models.py:177 -#, fuzzy msgid "free comments" -msgstr "Comentarios libres" +msgstr "comentarios libres" #: contrib/comments/models.py:233 msgid "score" @@ -145,14 +142,12 @@ msgid "score date" msgstr "data da puntuación" #: contrib/comments/models.py:237 -#, fuzzy msgid "karma score" -msgstr "Puntuación de karma" +msgstr "puntos de karma" #: contrib/comments/models.py:238 -#, fuzzy msgid "karma scores" -msgstr "Puntuacións de karma" +msgstr "puntos de karma" #: contrib/comments/models.py:242 #, python-format @@ -175,14 +170,12 @@ msgid "flag date" msgstr "data da marca" #: contrib/comments/models.py:268 -#, fuzzy msgid "user flag" -msgstr "Marca de usuario" +msgstr "marca de usuario" #: contrib/comments/models.py:269 -#, fuzzy msgid "user flags" -msgstr "Marcas de usuario" +msgstr "marcas de usuario" #: contrib/comments/models.py:273 #, python-format @@ -194,19 +187,17 @@ msgid "deletion date" msgstr "data de borrado" #: contrib/comments/models.py:280 -#, fuzzy msgid "moderator deletion" -msgstr "Borrado de moderación" +msgstr "borrado de moderador" #: contrib/comments/models.py:281 -#, fuzzy msgid "moderator deletions" -msgstr "Borrados de moderación" +msgstr "borrados de moderador" #: contrib/comments/models.py:285 #, python-format msgid "Moderator deletion by %r" -msgstr "Borrado de moderación por %r" +msgstr "Borrado polo moderador %r" #: contrib/comments/views/karma.py:19 msgid "Anonymous users cannot vote" @@ -218,7 +209,7 @@ msgstr "ID de comentario non válida" #: contrib/comments/views/karma.py:25 msgid "No voting for yourself" -msgstr "Non se pode votar a si mesmo" +msgstr "Vostede non se pode votar a si mesmo" #: contrib/comments/views/comments.py:28 msgid "" @@ -297,9 +288,8 @@ msgid "Password:" msgstr "Contrasinal:" #: contrib/comments/templates/comments/form.html:6 -#, fuzzy msgid "Forgotten your password?" -msgstr "Cambiar o contrasinal" +msgstr "Esqueceu o contrasinal?" #: contrib/comments/templates/comments/form.html:8 #: contrib/admin/templates/admin/object_history.html:3 @@ -320,7 +310,7 @@ msgstr "Cambiar o contrasinal" #: contrib/admin/templates/admin_doc/index.html:4 #: contrib/admin/templates/admin_doc/model_index.html:5 msgid "Log out" -msgstr "Saír" +msgstr "Rematar sesión" #: contrib/comments/templates/comments/form.html:12 msgid "Ratings" @@ -329,33 +319,30 @@ msgstr "" #: contrib/comments/templates/comments/form.html:12 #: contrib/comments/templates/comments/form.html:23 msgid "Required" -msgstr "" +msgstr "Requirido" #: contrib/comments/templates/comments/form.html:12 #: contrib/comments/templates/comments/form.html:23 msgid "Optional" -msgstr "" +msgstr "Opcional" #: contrib/comments/templates/comments/form.html:23 msgid "Post a photo" -msgstr "" +msgstr "Publicar unha foto" #: contrib/comments/templates/comments/form.html:27 #: contrib/comments/templates/comments/freeform.html:5 -#, fuzzy msgid "Comment:" -msgstr "Comentario" +msgstr "Comentario:" #: contrib/comments/templates/comments/form.html:32 #: contrib/comments/templates/comments/freeform.html:9 -#, fuzzy msgid "Preview comment" -msgstr "Comentario libre" +msgstr "Previsualizar comentario" #: contrib/comments/templates/comments/freeform.html:4 -#, fuzzy msgid "Your name:" -msgstr "nome de usuario" +msgstr "Nome:" #: contrib/admin/filterspecs.py:40 #, python-format @@ -440,12 +427,13 @@ msgstr "Todas as datas" msgid "" "Please enter a correct username and password. Note that both fields are case-" "sensitive." -msgstr "" +msgstr "Insira un nome de usuario e un contrasinal correctos. Teña en conta que " +"nos dous campos se distingue entre maiúsculas e minúsculas." #: contrib/admin/views/decorators.py:23 #: contrib/admin/templates/admin/login.html:25 msgid "Log in" -msgstr "Entrar" +msgstr "Iniciar sesión" #: contrib/admin/views/decorators.py:61 msgid "" @@ -757,7 +745,7 @@ msgstr "Sentímolo, pero non se atopou a páxina solicitada." #: contrib/admin/templates/admin/index.html:17 #, python-format msgid "Models available in the %(name)s application." -msgstr "" +msgstr "Modelos dispoñíbeis na aplicación %(name)s." #: contrib/admin/templates/admin/index.html:28 #: contrib/admin/templates/admin/change_form.html:15 @@ -1061,12 +1049,11 @@ msgstr "Hora" #: contrib/admin/templates/widget/file.html:2 msgid "Currently:" -msgstr "" +msgstr "Agora:" #: contrib/admin/templates/widget/file.html:3 -#, fuzzy msgid "Change:" -msgstr "Modificar" +msgstr "Modificar:" #: contrib/redirects/models.py:7 msgid "redirect from" @@ -1155,24 +1142,20 @@ msgid "codename" msgstr "código" #: contrib/auth/models.py:17 -#, fuzzy msgid "permission" -msgstr "Permiso" +msgstr "permiso" #: contrib/auth/models.py:18 contrib/auth/models.py:27 -#, fuzzy msgid "permissions" -msgstr "Permisos" +msgstr "permisos" #: contrib/auth/models.py:29 -#, fuzzy msgid "group" -msgstr "Grupo" +msgstr "grupo" #: contrib/auth/models.py:30 contrib/auth/models.py:65 -#, fuzzy msgid "groups" -msgstr "Grupos" +msgstr "grupos" #: contrib/auth/models.py:55 msgid "username" @@ -1231,19 +1214,16 @@ msgstr "" "permisos concedidos a cada un dos grupos aos que pertence." #: contrib/auth/models.py:67 -#, fuzzy msgid "user permissions" -msgstr "Permisos" +msgstr "permisos de usuario" #: contrib/auth/models.py:70 -#, fuzzy msgid "user" -msgstr "Usuario" +msgstr "usuario" #: contrib/auth/models.py:71 -#, fuzzy msgid "users" -msgstr "Usuarios" +msgstr "usuarios" #: contrib/auth/models.py:76 msgid "Personal info" @@ -1262,18 +1242,17 @@ msgid "Groups" msgstr "Grupos" #: contrib/auth/models.py:219 -#, fuzzy msgid "message" -msgstr "Mensaxe" +msgstr "mensaxe" #: contrib/auth/forms.py:30 msgid "" "Your Web browser doesn't appear to have cookies enabled. Cookies are " "required for logging in." -msgstr "" +msgstr "Semella que o seu navegador non acepta 'cookies'. Requírense " +"'cookies' para iniciar sesión." #: contrib/contenttypes/models.py:25 -#, fuzzy msgid "python model class name" msgstr "nome do módulo Python" @@ -1410,54 +1389,52 @@ msgid "December" msgstr "decembro" #: utils/dates.py:19 -#, fuzzy msgid "jan" -msgstr "e" +msgstr "xan" #: utils/dates.py:19 msgid "feb" -msgstr "" +msgstr "feb" #: utils/dates.py:19 msgid "mar" -msgstr "" +msgstr "mar" #: utils/dates.py:19 msgid "apr" -msgstr "" +msgstr "abr" #: utils/dates.py:19 -#, fuzzy msgid "may" -msgstr "día" +msgstr "mai" #: utils/dates.py:19 msgid "jun" -msgstr "" +msgstr "xuñ" #: utils/dates.py:20 msgid "jul" -msgstr "" +msgstr "xul" #: utils/dates.py:20 msgid "aug" -msgstr "" +msgstr "ago" #: utils/dates.py:20 msgid "sep" -msgstr "" +msgstr "set" #: utils/dates.py:20 msgid "oct" -msgstr "" +msgstr "out" #: utils/dates.py:20 msgid "nov" -msgstr "" +msgstr "nov" #: utils/dates.py:20 msgid "dec" -msgstr "" +msgstr "dec" #: utils/dates.py:27 msgid "Jan." @@ -1502,8 +1479,8 @@ msgstr[1] "meses" #: utils/timesince.py:14 msgid "week" msgid_plural "weeks" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "semana" +msgstr[1] "semanas" #: utils/timesince.py:15 msgid "day" @@ -1545,7 +1522,7 @@ msgstr "alemán" #: conf/global_settings.py:42 msgid "Greek" -msgstr "" +msgstr "grego" #: conf/global_settings.py:43 msgid "English" @@ -1565,11 +1542,11 @@ msgstr "galego" #: conf/global_settings.py:47 msgid "Hungarian" -msgstr "" +msgstr "húngaro" #: conf/global_settings.py:48 msgid "Hebrew" -msgstr "" +msgstr "hebreo" #: conf/global_settings.py:49 msgid "Icelandic" @@ -1581,11 +1558,11 @@ msgstr "italiano" #: conf/global_settings.py:51 msgid "Japanese" -msgstr "" +msgstr "xaponés" #: conf/global_settings.py:52 msgid "Dutch" -msgstr "" +msgstr "holandés" #: conf/global_settings.py:53 msgid "Norwegian" @@ -1608,9 +1585,8 @@ msgid "Slovak" msgstr "eslovaco" #: conf/global_settings.py:58 -#, fuzzy msgid "Slovenian" -msgstr "eslovaco" +msgstr "esloveno" #: conf/global_settings.py:59 msgid "Serbian" @@ -1621,9 +1597,8 @@ msgid "Swedish" msgstr "sueco" #: conf/global_settings.py:61 -#, fuzzy msgid "Ukrainian" -msgstr "brasileiro" +msgstr "ucraíno" #: conf/global_settings.py:62 msgid "Simplified Chinese" @@ -1631,20 +1606,19 @@ msgstr "chinés simplificado" #: conf/global_settings.py:63 msgid "Traditional Chinese" -msgstr "" +msgstr "chinés tradicional" #: core/validators.py:60 msgid "This value must contain only letters, numbers and underscores." msgstr "Este valor soamente pode conter letras, números e guións baixos (_)." #: core/validators.py:64 -#, fuzzy msgid "" "This value must contain only letters, numbers, underscores, dashes or " "slashes." msgstr "" -"Este valor soamente pode conter letras, números, guións baixos (_) e barras " -"inclinadas." +"Este valor soamente pode conter letras, números, guións baixos (_), guións (-) e barras " +"inclinadas (/)." #: core/validators.py:72 msgid "Uppercase letters are not allowed here." @@ -1903,9 +1877,9 @@ msgstr "" "comeza con \"%(start)s\")." #: db/models/manipulators.py:302 -#, fuzzy, python-format +#, python-format msgid "%(object)s with this %(type)s already exists for the given %(field)s." -msgstr "Xa existe un/ha %(optname)s con este/a %(fieldname)s." +msgstr "Xa existe un obxecto %(object)s con este %(type)s para o campo %(field)s." #: db/models/fields/__init__.py:40 #, python-format @@ -1919,19 +1893,16 @@ msgid "This field is required." msgstr "Requírese este campo." #: db/models/fields/__init__.py:337 -#, fuzzy msgid "This value must be an integer." -msgstr "Este valor ten que ser unha potencia de %s." +msgstr "Este valor ten que ser un número enteiro." #: db/models/fields/__init__.py:369 -#, fuzzy msgid "This value must be either True or False." -msgstr "Este valor ten que ser unha potencia de %s." +msgstr "Este valor ten que verdadeiro ou falso." #: db/models/fields/__init__.py:385 -#, fuzzy msgid "This field cannot be null." -msgstr "Este campo non é válido." +msgstr "Este campo non pode ser nulo." #: db/models/fields/__init__.py:562 msgid "Enter a valid filename." @@ -1943,12 +1914,10 @@ msgid "Please enter a valid %s." msgstr "Insira un %s válido/a." #: db/models/fields/related.py:579 -#, fuzzy msgid "Separate multiple IDs with commas." -msgstr " Separe IDs múltiplas con comas." +msgstr "Separe IDs múltiplas con comas." #: db/models/fields/related.py:581 -#, fuzzy msgid "" "Hold down \"Control\", or \"Command\" on a Mac, to select more than one." msgstr "" diff --git a/django/conf/locale/gl/LC_MESSAGES/djangojs.mo b/django/conf/locale/gl/LC_MESSAGES/djangojs.mo index bedf6fd743..140f9a220e 100644 Binary files a/django/conf/locale/gl/LC_MESSAGES/djangojs.mo and b/django/conf/locale/gl/LC_MESSAGES/djangojs.mo differ diff --git a/django/conf/locale/gl/LC_MESSAGES/djangojs.po b/django/conf/locale/gl/LC_MESSAGES/djangojs.po index 4c0a53ec1a..2a8f284659 100644 --- a/django/conf/locale/gl/LC_MESSAGES/djangojs.po +++ b/django/conf/locale/gl/LC_MESSAGES/djangojs.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2005-12-09 11:51+0100\n" -"PO-Revision-Date: 2005-12-08 15:28+0100\n" +"PO-Revision-Date: 2005-07-02 13:25+0200\n" "Last-Translator: Afonso Fernández Nogueira \n" "Language-Team: Galego\n" "MIME-Version: 1.0\n" @@ -18,33 +18,32 @@ msgstr "" #: contrib/admin/media/js/SelectFilter2.js:33 #, perl-format msgid "Available %s" -msgstr "" +msgstr "%s dispoñíbeis" #: contrib/admin/media/js/SelectFilter2.js:41 -#, fuzzy msgid "Choose all" -msgstr "Escolla unha hora" +msgstr "Escoller todo" #: contrib/admin/media/js/SelectFilter2.js:46 msgid "Add" -msgstr "" +msgstr "Engadir" #: contrib/admin/media/js/SelectFilter2.js:48 msgid "Remove" -msgstr "" +msgstr "Quitar" #: contrib/admin/media/js/SelectFilter2.js:53 #, perl-format msgid "Chosen %s" -msgstr "" +msgstr "%s escollido/a(s)" #: contrib/admin/media/js/SelectFilter2.js:54 msgid "Select your choice(s) and click " -msgstr "" +msgstr "Seleccione unha ou varias entrada e faga clic " #: contrib/admin/media/js/SelectFilter2.js:59 msgid "Clear all" -msgstr "" +msgstr "Quitar todo" #: contrib/admin/media/js/dateparse.js:26 #: contrib/admin/media/js/calendar.js:24 @@ -52,7 +51,7 @@ msgid "" "January February March April May June July August September October November " "December" msgstr "" -"xaneiro febeiro marzo abril maio xuño xullo agosto setembro outubro novembro " +"xaneiro febreiro marzo abril maio xuño xullo agosto setembro outubro novembro " "decembro" #: contrib/admin/media/js/dateparse.js:27 diff --git a/django/contrib/admin/media/js/admin/DateTimeShortcuts.js b/django/contrib/admin/media/js/admin/DateTimeShortcuts.js index ed99c6103d..35199baf84 100644 --- a/django/contrib/admin/media/js/admin/DateTimeShortcuts.js +++ b/django/contrib/admin/media/js/admin/DateTimeShortcuts.js @@ -217,7 +217,7 @@ var DateTimeShortcuts = { DateTimeShortcuts.dismissCalendar(num); }, cancelEventPropagation: function(e) { - if (!e) var e = window.event; + if (!e) e = window.event; e.cancelBubble = true; if (e.stopPropagation) e.stopPropagation(); } diff --git a/django/contrib/admin/media/js/core.js b/django/contrib/admin/media/js/core.js index 8eba69c9bb..d35bd29c1c 100644 --- a/django/contrib/admin/media/js/core.js +++ b/django/contrib/admin/media/js/core.js @@ -19,6 +19,7 @@ function removeEvent(obj, evType, fn) { return true; } else if (obj.detachEvent) { obj.detachEvent("on" + evType, fn); + return true; } else { return false; } diff --git a/django/contrib/admin/templates/admin/base.html b/django/contrib/admin/templates/admin/base.html index 7838bba6a5..67fe27529c 100644 --- a/django/contrib/admin/templates/admin/base.html +++ b/django/contrib/admin/templates/admin/base.html @@ -30,7 +30,7 @@ {% endif %} {% if messages %} -
    {% for message in messages %}
  • {{ message }}
  • {% endfor %}
+
    {% for message in messages %}
  • {{ message|escape }}
  • {% endfor %}
{% endif %} diff --git a/django/contrib/admin/templates/admin/index.html b/django/contrib/admin/templates/admin/index.html index 246086861b..f7b121723a 100644 --- a/django/contrib/admin/templates/admin/index.html +++ b/django/contrib/admin/templates/admin/index.html @@ -15,7 +15,7 @@ {% for app in app_list %}
- + {% for model in app.models %} {% if model.perms.change %} diff --git a/django/contrib/admin/views/doc.py b/django/contrib/admin/views/doc.py index f7ce2fa498..5aa143e1fd 100644 --- a/django/contrib/admin/views/doc.py +++ b/django/contrib/admin/views/doc.py @@ -188,7 +188,7 @@ def model_detail(request, app_label, model_name): 'name': field.name, 'data_type': data_type, 'verbose': verbose, - 'help': field.help_text, + 'help_text': field.help_text, }) # Gather model methods. diff --git a/django/contrib/comments/templates/comments/form.html b/django/contrib/comments/templates/comments/form.html index 3e171b1e67..4a6b5fc320 100644 --- a/django/contrib/comments/templates/comments/form.html +++ b/django/contrib/comments/templates/comments/form.html @@ -28,9 +28,11 @@


+

-

+ +

{% endif %} diff --git a/django/contrib/comments/templates/comments/freeform.html b/django/contrib/comments/templates/comments/freeform.html index 99a02b4d97..f0d00b91c7 100644 --- a/django/contrib/comments/templates/comments/freeform.html +++ b/django/contrib/comments/templates/comments/freeform.html @@ -3,9 +3,11 @@


+

-

+ +

{% endif %} diff --git a/django/core/management.py b/django/core/management.py index 04a5b71ac1..3b7b8a403b 100644 --- a/django/core/management.py +++ b/django/core/management.py @@ -1006,6 +1006,8 @@ def get_validation_errors(outfile, app=None): # Check core=True, if needed. for related in opts.get_followed_related_objects(): + if not related.edit_inline: + continue try: for f in related.opts.fields: if f.core: @@ -1045,7 +1047,10 @@ def _check_for_validation_errors(app=None): s = StringIO() num_errors = get_validation_errors(s, app) if num_errors: - sys.stderr.write(style.ERROR("Error: %s couldn't be installed, because there were errors in your model:\n" % app)) + if app: + sys.stderr.write(style.ERROR("Error: %s couldn't be installed, because there were errors in your model:\n" % app)) + else: + sys.stderr.write(style.ERROR("Error: Couldn't install apps, because there were errors in one or more models:\n")) s.seek(0) sys.stderr.write(s.read()) sys.exit(1) diff --git a/django/db/backends/postgresql/client.py b/django/db/backends/postgresql/client.py index 3d0d7a0d2a..8123ec7848 100644 --- a/django/db/backends/postgresql/client.py +++ b/django/db/backends/postgresql/client.py @@ -2,13 +2,14 @@ from django.conf import settings import os def runshell(): - args = [''] - args += ["-U%s" % settings.DATABASE_USER] + args = ['psql'] + if settings.DATABASE_USER: + args += ["-U", settings.DATABASE_USER] if settings.DATABASE_PASSWORD: args += ["-W"] if settings.DATABASE_HOST: - args += ["-h %s" % settings.DATABASE_HOST] + args.extend(["-h", settings.DATABASE_HOST]) if settings.DATABASE_PORT: - args += ["-p %s" % settings.DATABASE_PORT] + args.extend(["-p", str(settings.DATABASE_PORT)]) args += [settings.DATABASE_NAME] os.execvp('psql', args) diff --git a/django/db/backends/sqlite3/introspection.py b/django/db/backends/sqlite3/introspection.py index c5fa738249..cdabffdc1c 100644 --- a/django/db/backends/sqlite3/introspection.py +++ b/django/db/backends/sqlite3/introspection.py @@ -2,18 +2,56 @@ from django.db import transaction from django.db.backends.sqlite3.base import quote_name def get_table_list(cursor): - cursor.execute("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name") + "Returns a list of table names in the current database." + # Skip the sqlite_sequence system table used for autoincrement key + # generation. + cursor.execute(""" + SELECT name FROM sqlite_master + WHERE type='table' AND NOT name='sqlite_sequence' + ORDER BY name""") return [row[0] for row in cursor.fetchall()] def get_table_description(cursor, table_name): - cursor.execute("PRAGMA table_info(%s)" % quote_name(table_name)) - return [(row[1], row[2], None, None) for row in cursor.fetchall()] + "Returns a description of the table, with the DB-API cursor.description interface." + return [(info['name'], info['type'], None, None, None, None, + info['null_ok']) for info in _table_info(cursor, table_name)] def get_relations(cursor, table_name): raise NotImplementedError def get_indexes(cursor, table_name): - raise NotImplementedError + """ + Returns a dictionary of fieldname -> infodict for the given table, + where each infodict is in the format: + {'primary_key': boolean representing whether it's the primary key, + 'unique': boolean representing whether it's a unique index} + """ + indexes = {} + for info in _table_info(cursor, table_name): + indexes[info['name']] = {'primary_key': info['pk'] != 0, + 'unique': False} + cursor.execute('PRAGMA index_list(%s)' % quote_name(table_name)) + # seq, name, unique + for index, unique in [(field[1], field[2]) for field in cursor.fetchall()]: + if not unique: + continue + cursor.execute('PRAGMA index_info(%s)' % quote_name(index)) + info = cursor.fetchall() + # Skip indexes across multiple fields + if len(info) != 1: + continue + name = info[0][2] # seqno, cid, name + indexes[name]['unique'] = True + return indexes + +def _table_info(cursor, name): + cursor.execute('PRAGMA table_info(%s)' % quote_name(name)) + # cid, name, type, notnull, dflt_value, pk + return [{'name': field[1], + 'type': field[2], + 'null_ok': not field[3], + 'pk': field[5] # undocumented + } for field in cursor.fetchall()] # Maps SQL types to Django Field types. Some of the SQL types have multiple # entries here because SQLite allows for anything and doesn't normalize the diff --git a/django/db/models/base.py b/django/db/models/base.py index 883ed7c81a..e40c3350ae 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -44,6 +44,11 @@ class ModelBase(type): # For 'django.contrib.sites.models', this would be 'sites'. new_class._meta.app_label = model_module.__name__.split('.')[-2] + # Bail out early if we have already created this class. + m = get_model(new_class._meta.app_label, name) + if m is not None: + return m + # Add all attributes to the class. for obj_name, obj in attrs.items(): new_class.add_to_class(obj_name, obj) @@ -60,10 +65,10 @@ class ModelBase(type): new_class._prepare() register_models(new_class._meta.app_label, new_class) - # Because of the way imports happen (recursively), we may or may not be - # the first class for this model to register with the framework. There - # should only be one class for each model, so we must always return the - # registered version. + # Because of the way imports happen (recursively), we may or may not be + # the first class for this model to register with the framework. There + # should only be one class for each model, so we must always return the + # registered version. return get_model(new_class._meta.app_label, name) class Model(object): diff --git a/django/db/models/query.py b/django/db/models/query.py index 6af00abf28..73275d66a4 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -679,7 +679,7 @@ def get_cached_row(klass, row, index_start): def fill_table_cache(opts, select, tables, where, old_prefix, cache_tables_seen): """ Helper function that recursively populates the select, tables and where (in - place) for fill-cache queries. + place) for select_related queries. """ backend = opts.connection_info.backend for f in opts.fields: @@ -701,6 +701,7 @@ def fill_table_cache(opts, select, tables, where, old_prefix, cache_tables_seen) def parse_lookup(kwarg_items, opts): # Helper function that handles converting API kwargs # (e.g. "name__exact": "tom") to SQL. + # Returns a tuple of (tables, joins, where, params). # 'joins' is a sorted dictionary describing the tables that must be joined # to complete the query. The dictionary is sorted because creation order diff --git a/django/forms/__init__.py b/django/forms/__init__.py index 1e9cb2c596..4907bd76f7 100644 --- a/django/forms/__init__.py +++ b/django/forms/__init__.py @@ -773,15 +773,18 @@ class DatetimeField(TextField): def html2python(data): "Converts the field into a datetime.datetime object" import datetime - date, time = data.split() - y, m, d = date.split('-') - timebits = time.split(':') - h, mn = timebits[:2] - if len(timebits) > 2: - s = int(timebits[2]) - else: - s = 0 - return datetime.datetime(int(y), int(m), int(d), int(h), int(mn), s) + try: + date, time = data.split() + y, m, d = date.split('-') + timebits = time.split(':') + h, mn = timebits[:2] + if len(timebits) > 2: + s = int(timebits[2]) + else: + s = 0 + return datetime.datetime(int(y), int(m), int(d), int(h), int(mn), s) + except ValueError: + return None html2python = staticmethod(html2python) class DateField(TextField): @@ -806,7 +809,7 @@ class DateField(TextField): time_tuple = time.strptime(data, '%Y-%m-%d') return datetime.date(*time_tuple[0:3]) except (ValueError, TypeError): - return data + return None html2python = staticmethod(html2python) class TimeField(TextField): diff --git a/django/template/__init__.py b/django/template/__init__.py index 08f433fec9..a1d1d402d0 100644 --- a/django/template/__init__.py +++ b/django/template/__init__.py @@ -318,7 +318,7 @@ class Parser(object): self.tags.update(lib.tags) self.filters.update(lib.filters) - def compile_filter(self,token): + def compile_filter(self, token): "Convenient wrapper for FilterExpression" return FilterExpression(token, self) @@ -543,11 +543,14 @@ class FilterExpression(object): raise TemplateSyntaxError, "Could not parse the remainder: %s" % token[upto:] self.var, self.filters = var, filters - def resolve(self, context): + def resolve(self, context, ignore_failures=False): try: obj = resolve_variable(self.var, context) except VariableDoesNotExist: - obj = settings.TEMPLATE_STRING_IF_INVALID + if ignore_failures: + return None + else: + return settings.TEMPLATE_STRING_IF_INVALID for func, args in self.filters: arg_vals = [] for lookup, arg in args: @@ -611,7 +614,11 @@ def resolve_variable(path, context): (The example assumes VARIABLE_ATTRIBUTE_SEPARATOR is '.') """ - if path[0].isdigit(): + if path == 'False': + current = False + elif path == 'True': + current = True + elif path[0].isdigit(): number_type = '.' in path and float or int try: current = number_type(path) diff --git a/django/template/context.py b/django/template/context.py index 44a97f95a8..6d9efdc7ec 100644 --- a/django/template/context.py +++ b/django/template/context.py @@ -37,7 +37,7 @@ class Context(object): for d in self.dicts: if d.has_key(key): return d[key] - return settings.TEMPLATE_STRING_IF_INVALID + raise KeyError(key) def __delitem__(self, key): "Delete a variable from the current context" @@ -49,7 +49,7 @@ class Context(object): return True return False - def get(self, key, otherwise): + def get(self, key, otherwise=None): for d in self.dicts: if d.has_key(key): return d[key] diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py index 9bd6e7cb3c..5d56ec0c49 100644 --- a/django/template/defaultfilters.py +++ b/django/template/defaultfilters.py @@ -430,20 +430,32 @@ def filesizeformat(bytes): return "%.1f MB" % (bytes / (1024 * 1024)) return "%.1f GB" % (bytes / (1024 * 1024 * 1024)) -def pluralize(value): - "Returns 's' if the value is not 1, for '1 vote' vs. '2 votes'" +def pluralize(value, arg='s'): + """ + Returns a plural suffix if the value is not 1, for '1 vote' vs. '2 votes' + By default, 's' is used as a suffix; if an argument is provided, that string + is used instead. If the provided argument contains a comma, the text before + the comma is used for the singular case. + """ + if not ',' in arg: + arg = ',' + arg + bits = arg.split(',') + if len(bits) > 2: + return '' + singular_suffix, plural_suffix = bits[:2] + try: if int(value) != 1: - return 's' + return plural_suffix except ValueError: # invalid string that's not a number pass except TypeError: # value isn't a string or a number; maybe it's a list? try: if len(value) != 1: - return 's' + return plural_suffix except TypeError: # len() of unsized object pass - return '' + return singular_suffix def phone2numeric(value): "Takes a phone number and converts it in to its numerical equivalent" diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py index 88cb5f68be..0a4fe33d82 100644 --- a/django/template/defaulttags.py +++ b/django/template/defaulttags.py @@ -45,7 +45,10 @@ class FirstOfNode(Node): def render(self, context): for var in self.vars: - value = resolve_variable(var, context) + try: + value = resolve_variable(var, context) + except VariableDoesNotExist: + continue if value: return str(value) return '' @@ -144,8 +147,14 @@ class IfEqualNode(Node): return "" def render(self, context): - val1 = resolve_variable(self.var1, context) - val2 = resolve_variable(self.var2, context) + try: + val1 = resolve_variable(self.var1, context) + except VariableDoesNotExist: + val1 = None + try: + val2 = resolve_variable(self.var2, context) + except VariableDoesNotExist: + val2 = None if (self.negate and val1 != val2) or (not self.negate and val1 == val2): return self.nodelist_true.render(context) return self.nodelist_false.render(context) @@ -177,7 +186,7 @@ class IfNode(Node): if self.link_type == IfNode.LinkTypes.or_: for ifnot, bool_expr in self.bool_exprs: try: - value = bool_expr.resolve(context) + value = bool_expr.resolve(context, True) except VariableDoesNotExist: value = None if (value and not ifnot) or (ifnot and not value): @@ -186,7 +195,7 @@ class IfNode(Node): else: for ifnot, bool_expr in self.bool_exprs: try: - value = bool_expr.resolve(context) + value = bool_expr.resolve(context, True) except VariableDoesNotExist: value = None if not ((value and not ifnot) or (ifnot and not value)): diff --git a/django/templatetags/i18n.py b/django/templatetags/i18n.py index 37b8d93908..6718a0fbac 100644 --- a/django/templatetags/i18n.py +++ b/django/templatetags/i18n.py @@ -12,7 +12,7 @@ class GetAvailableLanguagesNode(Node): def render(self, context): from django.conf import settings - context[self.variable] = settings.LANGUAGES + context[self.variable] = [(k, translation.gettext(v)) for k, v in settings.LANGUAGES] return '' class GetCurrentLanguageNode(Node): @@ -30,7 +30,7 @@ class GetCurrentLanguageBidiNode(Node): def render(self, context): context[self.variable] = translation.get_language_bidi() return '' - + class TranslateNode(Node): def __init__(self, value, noop): self.value = value @@ -171,7 +171,7 @@ def do_translate(parser, token): else: noop = False return (value, noop) - (value, noop) = TranslateParser(token.contents).top() + value, noop = TranslateParser(token.contents).top() return TranslateNode(value, noop) def do_block_translate(parser, token): @@ -216,7 +216,7 @@ def do_block_translate(parser, token): raise TemplateSyntaxError, "unknown subtag %s for 'blocktrans' found" % tag return (countervar, counter, extra_context) - (countervar, counter, extra_context) = BlockTranslateParser(token.contents).top() + countervar, counter, extra_context = BlockTranslateParser(token.contents).top() singular = [] plural = [] diff --git a/django/utils/synch.py b/django/utils/synch.py index 1e6b546a78..6fcd81390e 100644 --- a/django/utils/synch.py +++ b/django/utils/synch.py @@ -6,7 +6,10 @@ Synchronization primitives: (Contributed to Django by eugene@lazutkin.com) """ -import threading +try: + import threading +except ImportError: + import dummy_threading as threading class RWLock: """ diff --git a/django/utils/translation/__init__.py b/django/utils/translation/__init__.py new file mode 100644 index 0000000000..276e59f4bd --- /dev/null +++ b/django/utils/translation/__init__.py @@ -0,0 +1,8 @@ +from django.conf import settings + +if settings.USE_I18N: + from trans_real import * +else: + from trans_null import * + +del settings diff --git a/django/utils/translation/trans_null.py b/django/utils/translation/trans_null.py new file mode 100644 index 0000000000..e3a97d4912 --- /dev/null +++ b/django/utils/translation/trans_null.py @@ -0,0 +1,18 @@ +# These are versions of the functions in django.utils.translation.trans_real +# that don't actually do anything. This is purely for performance, so that +# settings.USE_I18N = False can use this module rather than trans_real.py. + +from django.conf import settings + +def ngettext(singular, plural, number): + if number == 1: return singular + return plural +ngettext_lazy = ngettext + +gettext = gettext_noop = gettext_lazy = _ = lambda x: x +string_concat = lambda *strings: ''.join([str(el) for el in strings]) +activate = lambda x: None +deactivate = install = lambda: None +get_language = lambda: settings.LANGUAGE_CODE +get_date_formats = lambda: settings.DATE_FORMAT, settings.DATETIME_FORMAT, settings.TIME_FORMAT +get_partial_date_formats = lambda: settings.YEAR_MONTH_FORMAT, settings.MONTH_DAY_FORMAT diff --git a/django/utils/translation.py b/django/utils/translation/trans_real.py similarity index 99% rename from django/utils/translation.py rename to django/utils/translation/trans_real.py index a73c43c257..94df23a8e9 100644 --- a/django/utils/translation.py +++ b/django/utils/translation/trans_real.py @@ -1,4 +1,4 @@ -"translation helper functions" +"Translation helper functions" import os, re, sys import gettext as gettext_module @@ -221,7 +221,6 @@ def get_language_bidi(): False = left-to-right layout True = right-to-left layout """ - from django.conf import settings return get_language() in settings.LANGUAGES_BIDI @@ -389,7 +388,7 @@ def get_partial_date_formats(): def install(): """ Installs the gettext function as the default translation function under - the name _. + the name '_'. """ __builtins__['_'] = gettext diff --git a/docs/db-api.txt b/docs/db-api.txt index 15b70ee028..ce6bb0ab3b 100644 --- a/docs/db-api.txt +++ b/docs/db-api.txt @@ -1121,35 +1121,37 @@ Note this is only available in MySQL and requires direct manipulation of the database to add the full-text index. Default lookups are exact -~~~~~~~~~~~~~~~~~~~~~~~~~ +------------------------- If you don't provide a lookup type -- that is, if your keyword argument doesn't contain a double underscore -- the lookup type is assumed to be ``exact``. For example, the following two statements are equivalent:: - Blog.objects.get(id=14) - Blog.objects.get(id__exact=14) + Blog.objects.get(id__exact=14) # Explicit form + Blog.objects.get(id=14) # __exact is implied This is for convenience, because ``exact`` lookups are the common case. The pk lookup shortcut -~~~~~~~~~~~~~~~~~~~~~~ +---------------------- For convenience, Django provides a ``pk`` lookup type, which stands for "primary_key". This is shorthand for "an exact lookup on the primary-key." In the example ``Blog`` model, the primary key is the ``id`` field, so these -two statements are equivalent:: +three statements are equivalent:: - Blog.objects.get(id__exact=14) - Blog.objects.get(pk=14) + Blog.objects.get(id__exact=14) # Explicit form + Blog.objects.get(id=14) # __exact is implied + Blog.objects.get(pk=14) # pk implies id__exact -``pk`` lookups also work across joins. For example, these two statements are +``pk`` lookups also work across joins. For example, these three statements are equivalent:: - Entry.objects.filter(blog__id__exact=3) - Entry.objects.filter(blog__pk=3) + Entry.objects.filter(blog__id__exact=3) # Explicit form + Entry.objects.filter(blog__id=3) # __exact is implied + Entry.objects.filter(blog__pk=3) # __pk implies __id__exact Lookups that span relationships ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1550,6 +1552,21 @@ loaded, Django iterates over every model in ``INSTALLED_APPS`` and creates the backward relationships in memory as needed. Essentially, one of the functions of ``INSTALLED_APPS`` is to tell Django the entire model domain. +Queries over related objects +---------------------------- + +Queries involving related objects follow the same rules as queries involving +normal value fields. When specifying the the value for a query to match, you +may use either an object instance itself, or the primary key value for the +object. + +For example, if you have a Blog object ``b`` with ``id=5``, the following +three queries would be identical:: + + Entry.objects.filter(blog=b) # Query using object instance + Entry.objects.filter(blog=b.id) # Query using id from instance + Entry.objects.filter(blog=5) # Query using id directly + Deleting objects ================ diff --git a/docs/django-admin.txt b/docs/django-admin.txt index 3334ae4530..04d86aa3b4 100644 --- a/docs/django-admin.txt +++ b/docs/django-admin.txt @@ -126,8 +126,9 @@ you run it, you'll want to look over the generated models yourself to make customizations. In particular, you'll need to rearrange models' order, so that models that refer to other models are ordered properly. -Primary keys are automatically introspected for PostgreSQL and MySQL, in which -case Django puts in the ``primary_key=True`` where needed. +Primary keys are automatically introspected for PostgreSQL, MySQL and +SQLite, in which case Django puts in the ``primary_key=True`` where +needed. ``inspectdb`` works with PostgreSQL, MySQL and SQLite. Foreign-key detection only works in PostgreSQL and with certain types of MySQL tables. diff --git a/docs/forms.txt b/docs/forms.txt index 5026bc1bab..2fbe373744 100644 --- a/docs/forms.txt +++ b/docs/forms.txt @@ -50,7 +50,7 @@ model that "knows" how to create or modify instances of that model and how to validate data for the object. Manipulators come in two flavors: ``AddManipulators`` and ``ChangeManipulators``. Functionally they are quite similar, but the former knows how to create new instances of the model, while -the later modifies existing instances. Both types of classes are automatically +the latter modifies existing instances. Both types of classes are automatically created when you define a new class:: >>> from mysite.myapp.models import Place diff --git a/docs/outputting_pdf.txt b/docs/outputting_pdf.txt index a58cf2c217..edd34aca24 100644 --- a/docs/outputting_pdf.txt +++ b/docs/outputting_pdf.txt @@ -110,7 +110,7 @@ efficient. Here's the above "Hello World" example rewritten to use from cStringIO import StringIO from reportlab.pdfgen import canvas - from django.utils.httpwrappers import HttpResponse + from django.http import HttpResponse def some_view(request): # Create the HttpResponse object with the appropriate PDF headers. diff --git a/docs/sessions.txt b/docs/sessions.txt index 2dba491159..c473d0a3db 100644 --- a/docs/sessions.txt +++ b/docs/sessions.txt @@ -10,18 +10,22 @@ Cookies contain a session ID -- not the data itself. Enabling sessions ================= -Sessions are implemented via middleware_. +Sessions are implemented via a piece of middleware_ and a Django model. -Turn session functionality on and off by editing the ``MIDDLEWARE_CLASSES`` -setting. To activate sessions, make sure ``MIDDLEWARE_CLASSES`` contains -``'django.contrib.sessions.middleware.SessionMiddleware'``. +To enable session functionality, do these two things: -The default ``settings.py`` created by ``django-admin.py startproject`` has -``SessionMiddleware`` activated. + * Edit the ``MIDDLEWARE_CLASSES`` setting and make sure + ``MIDDLEWARE_CLASSES`` contains ``'django.contrib.sessions.middleware.SessionMiddleware'``. + The default ``settings.py`` created by ``django-admin.py startproject`` has + ``SessionMiddleware`` activated. + + * Add ``'django.contrib.sessions'`` to your ``INSTALLED_APPS`` setting, and + run ``manage.py syncdb`` to install the single database table that stores + session data. If you don't want to use sessions, you might as well remove the -``SessionMiddleware`` line from ``MIDDLEWARE_CLASSES``. It'll save you a small -bit of overhead. +``SessionMiddleware`` line from ``MIDDLEWARE_CLASSES`` and ``'django.contrib.sessions'`` +from your ``INSTALLED_APPS``. It'll save you a small bit of overhead. .. _middleware: http://www.djangoproject.com/documentation/middleware/ diff --git a/docs/settings.txt b/docs/settings.txt index 4f4fb70298..9e1c6b529b 100644 --- a/docs/settings.txt +++ b/docs/settings.txt @@ -729,6 +729,10 @@ TIME_ZONE Default: ``'America/Chicago'`` A string representing the time zone for this installation. `See available choices`_. +(Note that list of available choices lists more than one on the same line; +you'll want to use just one of the choices for a given time zone. For instance, +one line says ``'Europe/London GB GB-Eire'``, but you should use the first bit +of that -- ``'Europe/London'`` -- as your ``TIME_ZONE`` setting.) Note that this is the time zone to which Django will convert all dates/times -- not necessarily the timezone of the server. For example, one server may serve @@ -750,7 +754,7 @@ Default: ``True`` A boolean that specifies whether Django's internationalization system should be enabled. This provides an easy way to turn it off, for performance. If this is -set to ``False, Django will make some optimizations so as not to load the +set to ``False``, Django will make some optimizations so as not to load the internationalization machinery. YEAR_MONTH_FORMAT diff --git a/docs/templates.txt b/docs/templates.txt index 42947510d1..4ba52b3263 100644 --- a/docs/templates.txt +++ b/docs/templates.txt @@ -951,12 +951,26 @@ any string. pluralize ~~~~~~~~~ -Returns ``'s'`` if the value is not 1. +Returns a plural suffix if the value is not 1. By default, this suffix is ``'s'``. Example:: You have {{ num_messages }} message{{ num_messages|pluralize }}. +For words that require a suffix other than ``'s'``, you can provide an alternate +suffix as a parameter to the filter. + +Example:: + + You have {{ num_walruses }} walrus{{ num_walrus|pluralize:"es" }}. + +For words that don't pluralize by simple suffix, you can specify both a +singular and plural suffix, separated by a comma. + +Example:: + + You have {{ num_cherries }} cherr{{ num_cherries|pluralize:"y,ies" }}. + pprint ~~~~~~ diff --git a/tests/othertests/defaultfilters.py b/tests/othertests/defaultfilters.py index 46f2519285..1636b948d0 100644 --- a/tests/othertests/defaultfilters.py +++ b/tests/othertests/defaultfilters.py @@ -313,6 +313,36 @@ False >>> pluralize(2) 's' +>>> pluralize([1]) +'' + +>>> pluralize([]) +'s' + +>>> pluralize([1,2,3]) +'s' + +>>> pluralize(1,'es') +'' + +>>> pluralize(0,'es') +'es' + +>>> pluralize(2,'es') +'es' + +>>> pluralize(1,'y,ies') +'y' + +>>> pluralize(0,'y,ies') +'ies' + +>>> pluralize(2,'y,ies') +'ies' + +>>> pluralize(0,'y,ies,error') +'' + >>> phone2numeric('0800 flowers') '0800 3569377' diff --git a/tests/othertests/templates.py b/tests/othertests/templates.py index c0333c90a6..d3b09c5310 100644 --- a/tests/othertests/templates.py +++ b/tests/othertests/templates.py @@ -78,7 +78,7 @@ TEMPLATE_TESTS = { 'basic-syntax03': ("{{ first }} --- {{ second }}", {"first" : 1, "second" : 2}, "1 --- 2"), # Fail silently when a variable is not found in the current context - 'basic-syntax04': ("as{{ missing }}df", {}, "asdf"), + 'basic-syntax04': ("as{{ missing }}df", {}, "asINVALIDdf"), # A variable may not contain more than one word 'basic-syntax06': ("{{ multi word variable }}", {}, template.TemplateSyntaxError), @@ -94,7 +94,7 @@ TEMPLATE_TESTS = { 'basic-syntax10': ("{{ var.otherclass.method }}", {"var": SomeClass()}, "OtherClass.method"), # Fail silently when a variable's attribute isn't found - 'basic-syntax11': ("{{ var.blech }}", {"var": SomeClass()}, ""), + 'basic-syntax11': ("{{ var.blech }}", {"var": SomeClass()}, "INVALID"), # Raise TemplateSyntaxError when trying to access a variable beginning with an underscore 'basic-syntax12': ("{{ var.__dict__ }}", {"var": SomeClass()}, template.TemplateSyntaxError), @@ -110,10 +110,10 @@ TEMPLATE_TESTS = { 'basic-syntax18': ("{{ foo.bar }}", {"foo" : {"bar" : "baz"}}, "baz"), # Fail silently when a variable's dictionary key isn't found - 'basic-syntax19': ("{{ foo.spam }}", {"foo" : {"bar" : "baz"}}, ""), + 'basic-syntax19': ("{{ foo.spam }}", {"foo" : {"bar" : "baz"}}, "INVALID"), # Fail silently when accessing a non-simple method - 'basic-syntax20': ("{{ var.method2 }}", {"var": SomeClass()}, ""), + 'basic-syntax20': ("{{ var.method2 }}", {"var": SomeClass()}, "INVALID"), # Basic filter usage 'basic-syntax21': ("{{ var|upper }}", {"var": "Django is the greatest!"}, "DJANGO IS THE GREATEST!"), @@ -152,7 +152,7 @@ TEMPLATE_TESTS = { 'basic-syntax32': (r'{{ var|yesno:"yup,nup,mup" }} {{ var|yesno }}', {"var": True}, 'yup yes'), # Fail silently for methods that raise an exception with a "silent_variable_failure" attribute - 'basic-syntax33': (r'1{{ var.method3 }}2', {"var": SomeClass()}, "12"), + 'basic-syntax33': (r'1{{ var.method3 }}2', {"var": SomeClass()}, "1INVALID2"), # In methods that raise an exception without a "silent_variable_attribute" set to True, # the exception propogates @@ -495,7 +495,7 @@ TEMPLATE_TESTS = { '{{ item.foo }}' + \ '{% endfor %},' + \ '{% endfor %}', - {}, ''), + {}, 'INVALID:INVALIDINVALIDINVALIDINVALIDINVALIDINVALIDINVALID,'), ### TEMPLATETAG TAG ####################################################### 'templatetag01': ('{% templatetag openblock %}', {}, '{%'), @@ -579,6 +579,9 @@ def run_tests(verbosity=0, standalone=False): # Turn TEMPLATE_DEBUG off, because tests assume that. old_td, settings.TEMPLATE_DEBUG = settings.TEMPLATE_DEBUG, False + # Set TEMPLATE_STRING_IF_INVALID to a known string + old_invalid, settings.TEMPLATE_STRING_IF_INVALID = settings.TEMPLATE_STRING_IF_INVALID, 'INVALID' + for name, vals in tests: install() if 'LANGUAGE_CODE' in vals[1]: @@ -609,6 +612,7 @@ def run_tests(verbosity=0, standalone=False): loader.template_source_loaders = old_template_loaders deactivate() settings.TEMPLATE_DEBUG = old_td + settings.TEMPLATE_STRING_IF_INVALID = old_invalid if failed_tests and not standalone: msg = "Template tests %s failed." % failed_tests diff --git a/tests/regressiontests/many_to_one_regress/__init__.py b/tests/regressiontests/many_to_one_regress/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/regressiontests/many_to_one_regress/models.py b/tests/regressiontests/many_to_one_regress/models.py new file mode 100644 index 0000000000..485e928777 --- /dev/null +++ b/tests/regressiontests/many_to_one_regress/models.py @@ -0,0 +1,13 @@ +from django.db import models + +class First(models.Model): + second = models.IntegerField() + +class Second(models.Model): + first = models.ForeignKey(First, related_name = 'the_first') + +# If ticket #1578 ever slips back in, these models will not be able to be +# created (the field names being lower-cased versions of their opposite +# classes is important here). + +API_TESTS = ""
{{ app.name }}{% blocktrans with app.name as name %}{{ name }}{% endblocktrans %}