diff --git a/django/utils/text.py b/django/utils/text.py index f51113b483..3b8fc581bf 100644 --- a/django/utils/text.py +++ b/django/utils/text.py @@ -7,7 +7,9 @@ from io import BytesIO from django.utils import six from django.utils.encoding import force_text -from django.utils.functional import SimpleLazyObject, keep_lazy, keep_lazy_text +from django.utils.functional import ( + SimpleLazyObject, keep_lazy, keep_lazy_text, lazy, +) from django.utils.safestring import SafeText, mark_safe from django.utils.six.moves import html_entities from django.utils.translation import pgettext, ugettext as _, ugettext_lazy @@ -434,3 +436,12 @@ def camel_case_to_spaces(value): trailing whitespace. """ return re_camel_case.sub(r' \1', value).strip().lower() + + +def _format_lazy(format_string, *args, **kwargs): + """ + Apply str.format() on 'format_string' where format_string, args, + and/or kwargs might be lazy. + """ + return format_string.format(*args, **kwargs) +format_lazy = lazy(_format_lazy, six.text_type) diff --git a/docs/ref/utils.txt b/docs/ref/utils.txt index e7b3b2a997..07fa53990e 100644 --- a/docs/ref/utils.txt +++ b/docs/ref/utils.txt @@ -868,6 +868,26 @@ appropriate entities. .. module:: django.utils.text :synopsis: Text manipulation. +.. function:: format_lazy(format_string, *args, **kwargs) + + .. versionadded:: 1.11 + + A version of :meth:`str.format` for when ``format_string``, ``args``, + and/or ``kwargs`` contain lazy objects. The first argument is the string to + be formatted. For example:: + + from django.utils.text import format_lazy + from django.utils.translation import pgettext_lazy + + urlpatterns = [ + url(format_lazy(r'{person}/(?P\d+)/$', person=pgettext_lazy('URL', 'person')), + PersonDetailView.as_view()), + ] + + This example allows translators to translate part of the URL. If "person" + is translated to "persona", the regular expression will match + ``persona/(?P\d+)/$``, e.g. ``persona/5/``. + .. function:: slugify(allow_unicode=False) Converts to ASCII if ``allow_unicode`` is ``False`` (default). Converts spaces to diff --git a/tests/utils_tests/test_text.py b/tests/utils_tests/test_text.py index df4c055503..1ce993bdb2 100644 --- a/tests/utils_tests/test_text.py +++ b/tests/utils_tests/test_text.py @@ -6,7 +6,8 @@ import json from django.test import SimpleTestCase from django.utils import six, text from django.utils.functional import lazystr -from django.utils.translation import override +from django.utils.text import format_lazy +from django.utils.translation import override, ugettext_lazy IS_WIDE_BUILD = (len('\U0001F4A9') == 1) @@ -225,3 +226,24 @@ class TestUtilsText(SimpleTestCase): out = text.compress_sequence(seq) compressed_length = len(b''.join(out)) self.assertTrue(compressed_length < actual_length) + + def test_format_lazy(self): + self.assertEqual('django/test', format_lazy('{}/{}', 'django', lazystr('test'))) + self.assertEqual('django/test', format_lazy('{0}/{1}', *('django', 'test'))) + self.assertEqual('django/test', format_lazy('{a}/{b}', **{'a': 'django', 'b': 'test'})) + self.assertEqual('django/test', format_lazy('{a[0]}/{a[1]}', a=('django', 'test'))) + + t = {} + s = format_lazy('{0[a]}-{p[a]}', t, p=t) + t['a'] = lazystr('django') + self.assertEqual('django-django', s) + t['a'] = 'update' + self.assertEqual('update-update', s) + + # The format string can be lazy. (string comes from contrib.admin) + s = format_lazy( + ugettext_lazy("Added {name} \"{object}\"."), + name='article', object='My first try', + ) + with override('fr'): + self.assertEqual('article «\xa0My first try\xa0» ajouté.', s)