diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py index 73ecea3bd7..0662b5f2e7 100644 --- a/django/template/defaulttags.py +++ b/django/template/defaulttags.py @@ -16,6 +16,55 @@ register = Library() # Regex for token keyword arguments kwarg_re = re.compile(r"(?:(\w+)=)?(.+)") +def token_kwargs(bits, parser, support_legacy=False): + """ + A utility method for parsing token keyword arguments. + + :param bits: A list containing remainder of the token (split by spaces) + that is to be checked for arguments. Valid arguments will be removed + from this list. + + :param support_legacy: If set to true ``True``, the legacy format + ``1 as foo`` will be accepted. Otherwise, only the standard ``foo=1`` + format is allowed. + + :returns: A dictionary of the arguments retrieved from the ``bits`` token + list. + + There is no requirement for all remaining token ``bits`` to be keyword + arguments, so the dictionary will be returned as soon as an invalid + argument format is reached. + """ + if not bits: + return {} + match = kwarg_re.match(bits[0]) + kwarg_format = match and match.group(1) + if not kwarg_format: + if not support_legacy: + return {} + if len(bits) < 3 or bits[1] != 'as': + return {} + + kwargs = {} + while bits: + if kwarg_format: + match = kwarg_re.match(bits[0]) + if not match or not match.group(1): + return kwargs + key, value = match.groups() + del bits[:1] + else: + if len(bits) < 3 or bits[1] != 'as': + return kwargs + key, value = bits[2], bits[0] + del bits[:3] + kwargs[key] = parser.compile_filter(value) + if bits and not kwarg_format: + if bits[0] != 'and': + return kwargs + del bits[:1] + return kwargs + class AutoEscapeControlNode(Node): """Implements the actions of the autoescape tag.""" def __init__(self, setting, nodelist): @@ -298,7 +347,7 @@ class SsiNode(Node): def render(self, context): filepath = self.filepath if not self.legacy_filepath: - filepath = filepath.resolve(context) + filepath = filepath.resolve(context) if not include_is_allowed(filepath): if settings.DEBUG: @@ -433,18 +482,25 @@ class WidthRatioNode(Node): return str(int(round(ratio))) class WithNode(Node): - def __init__(self, var, name, nodelist): - self.var = var - self.name = name + def __init__(self, var, name, nodelist, extra_context=None, + isolated_context=False): self.nodelist = nodelist + # var and name are legacy attributes, being left in case they are used + # by third-party subclasses of this Node. + self.extra_context = extra_context or {} + if name: + self.extra_context[name] = var + self.isolated_context = isolated_context def __repr__(self): return "" def render(self, context): - val = self.var.resolve(context) - context.push() - context[self.name] = val + values = dict([(key, val.resolve(context)) for key, val in + self.extra_context.iteritems()]) + if self.isolated_context: + return self.nodelist.render(Context(values)) + context.update(values) output = self.nodelist.render(context) context.pop() return output @@ -1276,22 +1332,34 @@ widthratio = register.tag(widthratio) #@register.tag def do_with(parser, token): """ - Adds a value to the context (inside of this block) for caching and easy - access. + Adds one or more values to the context (inside of this block) for caching + and easy access. For example:: - {% with person.some_sql_method as total %} + {% with total=person.some_sql_method %} {{ total }} object{{ total|pluralize }} {% endwith %} + + Multiple values can be added to the context:: + + {% with foo=1 bar=2 %} + ... + {% endwith %} + + The legacy format of ``{% with person.some_sql_method as total %}`` is + still accepted. """ - bits = list(token.split_contents()) - if len(bits) != 4 or bits[2] != "as": - raise TemplateSyntaxError("%r expected format is 'value as name'" % - bits[0]) - var = parser.compile_filter(bits[1]) - name = bits[3] + bits = token.split_contents() + remaining_bits = bits[1:] + extra_context = token_kwargs(remaining_bits, parser, support_legacy=True) + if not extra_context: + raise TemplateSyntaxError("%r expected at least one variable " + "assignment" % bits[0]) + if remaining_bits: + raise TemplateSyntaxError("%r received an invalid token: %r" % + (bits[0], remaining_bits[0])) nodelist = parser.parse(('endwith',)) parser.delete_first_token() - return WithNode(var, name, nodelist) + return WithNode(None, None, nodelist, extra_context=extra_context) do_with = register.tag('with', do_with) diff --git a/django/template/loader_tags.py b/django/template/loader_tags.py index cc4773927c..55b3ede02a 100644 --- a/django/template/loader_tags.py +++ b/django/template/loader_tags.py @@ -1,5 +1,7 @@ from django.template.base import TemplateSyntaxError, TemplateDoesNotExist, Variable from django.template.base import Library, Node, TextNode +from django.template.context import Context +from django.template.defaulttags import token_kwargs from django.template.loader import get_template from django.conf import settings from django.utils.safestring import mark_safe @@ -124,8 +126,25 @@ class ExtendsNode(Node): # the same. return compiled_parent._render(context) -class ConstantIncludeNode(Node): - def __init__(self, template_path): +class BaseIncludeNode(Node): + def __init__(self, *args, **kwargs): + self.extra_context = kwargs.pop('extra_context', {}) + self.isolated_context = kwargs.pop('isolated_context', False) + super(BaseIncludeNode, self).__init__(*args, **kwargs) + + def render_template(self, template, context): + values = dict([(name, var.resolve(context)) for name, var + in self.extra_context.iteritems()]) + if self.isolated_context: + return template.render(Context(values)) + context.update(values) + output = template.render(context) + context.pop() + return output + +class ConstantIncludeNode(BaseIncludeNode): + def __init__(self, template_path, *args, **kwargs): + super(ConstantIncludeNode, self).__init__(*args, **kwargs) try: t = get_template(template_path) self.template = t @@ -135,21 +154,21 @@ class ConstantIncludeNode(Node): self.template = None def render(self, context): - if self.template: - return self.template.render(context) - else: + if not self.template: return '' + return self.render_template(self.template, context) -class IncludeNode(Node): - def __init__(self, template_name): - self.template_name = Variable(template_name) +class IncludeNode(BaseIncludeNode): + def __init__(self, template_name, *args, **kwargs): + super(IncludeNode, self).__init__(*args, **kwargs) + self.template_name = template_name def render(self, context): try: template_name = self.template_name.resolve(context) - t = get_template(template_name) - return t.render(context) - except TemplateSyntaxError, e: + template = get_template(template_name) + return self.render_template(template, context) + except TemplateSyntaxError: if settings.TEMPLATE_DEBUG: raise return '' @@ -201,19 +220,49 @@ def do_extends(parser, token): def do_include(parser, token): """ - Loads a template and renders it with the current context. + Loads a template and renders it with the current context. You can pass + additional context using keyword arguments. Example:: {% include "foo/some_include" %} + {% include "foo/some_include" with bar="BAZZ!" baz="BING!" %} + + Use the ``only`` argument to exclude the current context when rendering + the included template:: + + {% include "foo/some_include" only %} + {% include "foo/some_include" with bar="1" only %} """ bits = token.split_contents() - if len(bits) != 2: - raise TemplateSyntaxError("%r tag takes one argument: the name of the template to be included" % bits[0]) + if len(bits) < 2: + raise TemplateSyntaxError("%r tag takes at least one argument: the name of the template to be included." % bits[0]) + options = {} + remaining_bits = bits[2:] + while remaining_bits: + option = remaining_bits.pop(0) + if option in options: + raise TemplateSyntaxError('The %r option was specified more ' + 'than once.' % option) + if option == 'with': + value = token_kwargs(remaining_bits, parser, support_legacy=False) + if not value: + raise TemplateSyntaxError('"with" in %r tag needs at least ' + 'one keyword argument.' % bits[0]) + elif option == 'only': + value = True + else: + raise TemplateSyntaxError('Unknown argument for %r tag: %r.' % + (bits[0], option)) + options[option] = value + isolated_context = options.get('only', False) + namemap = options.get('with', {}) path = bits[1] if path[0] in ('"', "'") and path[-1] == path[0]: - return ConstantIncludeNode(path[1:-1]) - return IncludeNode(bits[1]) + return ConstantIncludeNode(path[1:-1], extra_context=namemap, + isolated_context=isolated_context) + return IncludeNode(parser.compile_filter(bits[1]), extra_context=namemap, + isolated_context=isolated_context) register.tag('block', do_block) register.tag('extends', do_extends) diff --git a/django/templatetags/i18n.py b/django/templatetags/i18n.py index 8bc02357fc..0cb7e6a1e0 100644 --- a/django/templatetags/i18n.py +++ b/django/templatetags/i18n.py @@ -6,6 +6,7 @@ from django.template import TOKEN_TEXT, TOKEN_VAR from django.template.base import _render_value_in_context from django.utils import translation from django.utils.encoding import force_unicode +from django.template.defaulttags import token_kwargs register = Library() @@ -97,7 +98,7 @@ class BlockTranslateNode(Node): def render(self, context): tmp_context = {} for var, val in self.extra_context.items(): - tmp_context[var] = val.render(context) + tmp_context[var] = val.resolve(context) # Update() works like a push(), so corresponding context.pop() is at # the end of function context.update(tmp_context) @@ -284,43 +285,54 @@ def do_block_translate(parser, token): Usage:: - {% blocktrans with foo|filter as bar and baz|filter as boo %} + {% blocktrans with bar=foo|filter boo=baz|filter %} This is {{ bar }} and {{ boo }}. {% endblocktrans %} Additionally, this supports pluralization:: - {% blocktrans count var|length as count %} + {% blocktrans count count=var|length %} There is {{ count }} object. {% plural %} There are {{ count }} objects. {% endblocktrans %} This is much like ngettext, only in template syntax. - """ - class BlockTranslateParser(TokenParser): - def top(self): - countervar = None - counter = None - extra_context = {} - while self.more(): - tag = self.tag() - if tag == 'with' or tag == 'and': - value = self.value() - if self.tag() != 'as': - raise TemplateSyntaxError("variable bindings in 'blocktrans' must be 'with value as variable'") - extra_context[self.tag()] = VariableNode( - parser.compile_filter(value)) - elif tag == 'count': - counter = parser.compile_filter(self.value()) - if self.tag() != 'as': - raise TemplateSyntaxError("counter specification in 'blocktrans' must be 'count value as variable'") - countervar = self.tag() - else: - raise TemplateSyntaxError("unknown subtag %s for 'blocktrans' found" % tag) - return (countervar, counter, extra_context) - countervar, counter, extra_context = BlockTranslateParser(token.contents).top() + The "var as value" legacy format is still supported:: + + {% blocktrans with foo|filter as bar and baz|filter as boo %} + {% blocktrans count var|length as count %} + """ + bits = token.split_contents() + + options = {} + remaining_bits = bits[1:] + while remaining_bits: + option = remaining_bits.pop(0) + if option in options: + raise TemplateSyntaxError('The %r option was specified more ' + 'than once.' % option) + if option == 'with': + value = token_kwargs(remaining_bits, parser, support_legacy=True) + if not value: + raise TemplateSyntaxError('"with" in %r tag needs at least ' + 'one keyword argument.' % bits[0]) + elif option == 'count': + value = token_kwargs(remaining_bits, parser, support_legacy=True) + if len(value) != 1: + raise TemplateSyntaxError('"count" in %r tag expected exactly ' + 'one keyword argument.' % bits[0]) + else: + raise TemplateSyntaxError('Unknown argument for %r tag: %r.' % + (bits[0], option)) + options[option] = value + + if 'count' in options: + countervar, counter = options['count'].items()[0] + else: + countervar, counter = None, None + extra_context = options.get('with', {}) singular = [] plural = [] diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index da8cac8555..0dc0372d59 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -639,9 +639,19 @@ including it. This example produces the output ``"Hello, John"``: * The ``name_snippet.html`` template:: - Hello, {{ person }} + {{ greeting }}, {{ person|default:"friend" }}! -See also: ``{% ssi %}``. +.. versionchanged:: 1.3 + Additional context and exclusive context. + +You can pass additional context to the template using keyword arguments:: + + {% include "name_snippet.html" with person="Jane" greeting="Hello" "%} + +If you want to only render the context with the variables provided (or even +no variables at all), use the ``only`` option:: + + {% include "name_snippet.html" with greeting="Hi" only %} .. note:: The :ttag:`include` tag should be considered as an implementation of @@ -650,6 +660,8 @@ See also: ``{% ssi %}``. This means that there is no shared state between included templates -- each include is a completely independent rendering process. +See also: ``{% ssi %}``. + .. templatetag:: load load @@ -1044,18 +1056,30 @@ with .. versionadded:: 1.0 +.. versionchanged:: 1.3 + New keyword argument format and multiple variable assignments. + Caches a complex variable under a simpler name. This is useful when accessing an "expensive" method (e.g., one that hits the database) multiple times. For example:: - {% with business.employees.count as total %} + {% with total=business.employees.count %} {{ total }} employee{{ total|pluralize }} {% endwith %} The populated variable (in the example above, ``total``) is only available between the ``{% with %}`` and ``{% endwith %}`` tags. +You can assign more than one context variable:: + + {% with alpha=1 beta=2 %} + ... + {% endwith %} + +.. note:: The previous more verbose format is still supported: + ``{% with business.employees.count as total %}`` + .. _ref-templates-builtins-filters: Built-in filter reference diff --git a/docs/topics/db/optimization.txt b/docs/topics/db/optimization.txt index fda26f1744..ec5b6fa160 100644 --- a/docs/topics/db/optimization.txt +++ b/docs/topics/db/optimization.txt @@ -206,7 +206,7 @@ many-to-many relation to User, the following template code is optimal: .. code-block:: html+django {% if display_inbox %} - {% with user.emails.all as emails %} + {% with emails=user.emails.all %} {% if emails %}

You have {{ emails|length }} email(s)

{% for email in emails %} diff --git a/docs/topics/forms/index.txt b/docs/topics/forms/index.txt index 30b09c00a9..fc19ff63ef 100644 --- a/docs/topics/forms/index.txt +++ b/docs/topics/forms/index.txt @@ -371,12 +371,11 @@ using the :ttag:`include` tag to reuse it in other templates:: {% endfor %} If the form object passed to a template has a different name within the -context, you can alias it using the :ttag:`with` tag:: +context, you can alias it using the ``with`` argument of the :ttag:`include` +tag::
- {% with comment_form as form %} - {% include "form_snippet.html" %} - {% endwith %} + {% include "form_snippet.html" with form=comment_form %}

diff --git a/docs/topics/i18n/internationalization.txt b/docs/topics/i18n/internationalization.txt index c2bd6eb309..cf009e4244 100644 --- a/docs/topics/i18n/internationalization.txt +++ b/docs/topics/i18n/internationalization.txt @@ -438,6 +438,9 @@ It's not possible to mix a template variable inside a string within ``{% trans ``blocktrans`` template tag --------------------------- +.. versionchanged:: 1.3 + New keyword argument format. + Contrarily to the ``trans`` tag, the ``blocktrans`` tag allows you to mark complex sentences consisting of literals and variable content for translation by making use of placeholders:: @@ -448,18 +451,18 @@ To translate a template expression -- say, accessing object attributes or using template filters -- you need to bind the expression to a local variable for use within the translation block. Examples:: - {% blocktrans with article.price as amount %} + {% blocktrans with amount=article.price %} That will cost $ {{ amount }}. {% endblocktrans %} - {% blocktrans with value|filter as myvar %} + {% blocktrans with myvar=value|filter %} This will have {{ myvar }} inside. {% endblocktrans %} If you need to bind more than one expression inside a ``blocktrans`` tag, separate the pieces with ``and``:: - {% blocktrans with book|title as book_t and author|title as author_t %} + {% blocktrans with book_t=book|title author_t=author|title %} This is {{ book_t }} by {{ author_t }} {% endblocktrans %} @@ -474,7 +477,7 @@ This tag also provides for pluralization. To use it: An example:: - {% blocktrans count list|length as counter %} + {% blocktrans count counter=list|length %} There is only one {{ name }} object. {% plural %} There are {{ counter }} {{ name }} objects. @@ -482,7 +485,7 @@ An example:: A more complex example:: - {% blocktrans with article.price as amount count i.length as years %} + {% blocktrans with amount=article.price count years=i.length %} That will cost $ {{ amount }} per year. {% plural %} That will cost $ {{ amount }} per {{ years }} years. @@ -494,6 +497,9 @@ construct is internally converted to an ``ungettext`` call. This means the same :ref:`notes regarding ungettext variables ` apply. +.. note:: The previous more verbose format is still supported: + ``{% blocktrans with book|title as book_t and author|title as author_t %}`` + .. _template-translation-vars: Other tags diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py index 475f92327d..d363f50e6a 100644 --- a/tests/regressiontests/templates/tests.py +++ b/tests/regressiontests/templates/tests.py @@ -930,7 +930,7 @@ class Templates(unittest.TestCase): 'ifnotequal03': ("{% ifnotequal a b %}yes{% else %}no{% endifnotequal %}", {"a": 1, "b": 2}, "yes"), 'ifnotequal04': ("{% ifnotequal a b %}yes{% else %}no{% endifnotequal %}", {"a": 1, "b": 1}, "no"), - ### INCLUDE TAG ########################################################### + ## INCLUDE TAG ########################################################### 'include01': ('{% include "basic-syntax01" %}', {}, "something cool"), 'include02': ('{% include "basic-syntax02" %}', {'headline': 'Included'}, "Included"), 'include03': ('{% include template_name %}', {'template_name': 'basic-syntax02', 'headline': 'Included'}, "Included"), @@ -938,6 +938,23 @@ class Templates(unittest.TestCase): 'include 05': ('template with a space', {}, 'template with a space'), 'include06': ('{% include "include 05"%}', {}, 'template with a space'), + # extra inline context + 'include07': ('{% include "basic-syntax02" with headline="Inline" %}', {'headline': 'Included'}, 'Inline'), + 'include08': ('{% include headline with headline="Dynamic" %}', {'headline': 'basic-syntax02'}, 'Dynamic'), + 'include09': ('{{ first }}--{% include "basic-syntax03" with first=second|lower|upper second=first|upper %}--{{ second }}', {'first': 'Ul', 'second': 'lU'}, 'Ul--LU --- UL--lU'), + + # isolated context + 'include10': ('{% include "basic-syntax03" only %}', {'first': '1'}, (' --- ', 'INVALID --- INVALID')), + 'include11': ('{% include "basic-syntax03" only with second=2 %}', {'first': '1'}, (' --- 2', 'INVALID --- 2')), + 'include12': ('{% include "basic-syntax03" with first=1 only %}', {'second': '2'}, ('1 --- ', '1 --- INVALID')), + + 'include-error01': ('{% include "basic-syntax01" with %}', {}, template.TemplateSyntaxError), + 'include-error02': ('{% include "basic-syntax01" with "no key" %}', {}, template.TemplateSyntaxError), + 'include-error03': ('{% include "basic-syntax01" with dotted.arg="error" %}', {}, template.TemplateSyntaxError), + 'include-error04': ('{% include "basic-syntax01" something_random %}', {}, template.TemplateSyntaxError), + 'include-error05': ('{% include "basic-syntax01" foo="duplicate" foo="key" %}', {}, template.TemplateSyntaxError), + 'include-error06': ('{% include "basic-syntax01" only only %}', {}, template.TemplateSyntaxError), + ### NAMED ENDBLOCKS ####################################################### # Basic test @@ -1098,7 +1115,8 @@ class Templates(unittest.TestCase): 'i18n03': ('{% load i18n %}{% blocktrans %}{{ anton }}{% endblocktrans %}', {'anton': '\xc3\x85'}, u"Å"), # simple translation of a variable and filter - 'i18n04': ('{% load i18n %}{% blocktrans with anton|lower as berta %}{{ berta }}{% endblocktrans %}', {'anton': '\xc3\x85'}, u'å'), + 'i18n04': ('{% load i18n %}{% blocktrans with berta=anton|lower %}{{ berta }}{% endblocktrans %}', {'anton': '\xc3\x85'}, u'å'), + 'legacyi18n04': ('{% load i18n %}{% blocktrans with anton|lower as berta %}{{ berta }}{% endblocktrans %}', {'anton': '\xc3\x85'}, u'å'), # simple translation of a string with interpolation 'i18n05': ('{% load i18n %}{% blocktrans %}xxx{{ anton }}xxx{% endblocktrans %}', {'anton': 'yyy'}, "xxxyyyxxx"), @@ -1107,10 +1125,12 @@ class Templates(unittest.TestCase): 'i18n06': ('{% load i18n %}{% trans "Page not found" %}', {'LANGUAGE_CODE': 'de'}, "Seite nicht gefunden"), # translation of singular form - 'i18n07': ('{% load i18n %}{% blocktrans count number as counter %}singular{% plural %}{{ counter }} plural{% endblocktrans %}', {'number': 1}, "singular"), + 'i18n07': ('{% load i18n %}{% blocktrans count counter=number %}singular{% plural %}{{ counter }} plural{% endblocktrans %}', {'number': 1}, "singular"), + 'legacyi18n07': ('{% load i18n %}{% blocktrans count number as counter %}singular{% plural %}{{ counter }} plural{% endblocktrans %}', {'number': 1}, "singular"), # translation of plural form 'i18n08': ('{% load i18n %}{% blocktrans count number as counter %}singular{% plural %}{{ counter }} plural{% endblocktrans %}', {'number': 2}, "2 plural"), + 'legacyi18n08': ('{% load i18n %}{% blocktrans count counter=number %}singular{% plural %}{{ counter }} plural{% endblocktrans %}', {'number': 2}, "2 plural"), # simple non-translation (only marking) of a string to german 'i18n09': ('{% load i18n %}{% trans "Page not found" noop %}', {'LANGUAGE_CODE': 'de'}, "Page not found"), @@ -1132,12 +1152,14 @@ class Templates(unittest.TestCase): # Escaping inside blocktrans and trans works as if it was directly in the # template. - 'i18n17': ('{% load i18n %}{% blocktrans with anton|escape as berta %}{{ berta }}{% endblocktrans %}', {'anton': 'α & β'}, u'α & β'), - 'i18n18': ('{% load i18n %}{% blocktrans with anton|force_escape as berta %}{{ berta }}{% endblocktrans %}', {'anton': 'α & β'}, u'α & β'), + 'i18n17': ('{% load i18n %}{% blocktrans with berta=anton|escape %}{{ berta }}{% endblocktrans %}', {'anton': 'α & β'}, u'α & β'), + 'i18n18': ('{% load i18n %}{% blocktrans with berta=anton|force_escape %}{{ berta }}{% endblocktrans %}', {'anton': 'α & β'}, u'α & β'), 'i18n19': ('{% load i18n %}{% blocktrans %}{{ andrew }}{% endblocktrans %}', {'andrew': 'a & b'}, u'a & b'), 'i18n20': ('{% load i18n %}{% trans andrew %}', {'andrew': 'a & b'}, u'a & b'), 'i18n21': ('{% load i18n %}{% blocktrans %}{{ andrew }}{% endblocktrans %}', {'andrew': mark_safe('a & b')}, u'a & b'), 'i18n22': ('{% load i18n %}{% trans andrew %}', {'andrew': mark_safe('a & b')}, u'a & b'), + 'legacyi18n17': ('{% load i18n %}{% blocktrans with anton|escape as berta %}{{ berta }}{% endblocktrans %}', {'anton': 'α & β'}, u'α & β'), + 'legacyi18n18': ('{% load i18n %}{% blocktrans with anton|force_escape as berta %}{{ berta }}{% endblocktrans %}', {'anton': 'α & β'}, u'α & β'), # Use filters with the {% trans %} tag, #5972 'i18n23': ('{% load i18n %}{% trans "Page not found"|capfirst|slice:"6:" %}', {'LANGUAGE_CODE': 'de'}, u'nicht gefunden'), @@ -1145,10 +1167,16 @@ class Templates(unittest.TestCase): 'i18n25': ('{% load i18n %}{% trans somevar|upper %}', {'somevar': 'Page not found', 'LANGUAGE_CODE': 'de'}, u'SEITE NICHT GEFUNDEN'), # translation of plural form with extra field in singular form (#13568) - 'i18n26': ('{% load i18n %}{% blocktrans with myextra_field as extra_field count number as counter %}singular {{ extra_field }}{% plural %}plural{% endblocktrans %}', {'number': 1, 'myextra_field': 'test'}, "singular test"), + 'i18n26': ('{% load i18n %}{% blocktrans with extra_field=myextra_field count counter=number %}singular {{ extra_field }}{% plural %}plural{% endblocktrans %}', {'number': 1, 'myextra_field': 'test'}, "singular test"), + 'legacyi18n26': ('{% load i18n %}{% blocktrans with myextra_field as extra_field count number as counter %}singular {{ extra_field }}{% plural %}plural{% endblocktrans %}', {'number': 1, 'myextra_field': 'test'}, "singular test"), # translation of singular form in russian (#14126) - 'i18n27': ('{% load i18n %}{% blocktrans count number as counter %}1 result{% plural %}{{ counter }} results{% endblocktrans %}', {'number': 1, 'LANGUAGE_CODE': 'ru'}, u'1 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442'), + 'i18n27': ('{% load i18n %}{% blocktrans count counter=number %}1 result{% plural %}{{ counter }} results{% endblocktrans %}', {'number': 1, 'LANGUAGE_CODE': 'ru'}, u'1 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442'), + 'legacyi18n27': ('{% load i18n %}{% blocktrans count number as counter %}1 result{% plural %}{{ counter }} results{% endblocktrans %}', {'number': 1, 'LANGUAGE_CODE': 'ru'}, u'1 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442'), + + # simple translation of multiple variables + 'i18n28': ('{% load i18n %}{% blocktrans with a=anton b=berta %}{{ a }} + {{ b }}{% endblocktrans %}', {'anton': 'α', 'berta': 'β'}, u'α + β'), + 'legacyi18n28': ('{% load i18n %}{% blocktrans with anton as a and berta as b %}{{ a }} + {{ b }}{% endblocktrans %}', {'anton': 'α', 'berta': 'β'}, u'α + β'), # retrieving language information 'i18n28': ('{% load i18n %}{% get_language_info for "de" as l %}{{ l.code }}: {{ l.name }}/{{ l.name_local }} bidi={{ l.bidi }}', {}, 'de: German/Deutsch bidi=False'), @@ -1273,11 +1301,16 @@ class Templates(unittest.TestCase): 'widthratio11': ('{% widthratio a b c %}', {'a':50,'b':100, 'c': 100}, '50'), ### WITH TAG ######################################################## - 'with01': ('{% with dict.key as key %}{{ key }}{% endwith %}', {'dict': {'key':50}}, '50'), - 'with02': ('{{ key }}{% with dict.key as key %}{{ key }}-{{ dict.key }}-{{ key }}{% endwith %}{{ key }}', {'dict': {'key':50}}, ('50-50-50', 'INVALID50-50-50INVALID')), + 'with01': ('{% with key=dict.key %}{{ key }}{% endwith %}', {'dict': {'key': 50}}, '50'), + 'legacywith01': ('{% with dict.key as key %}{{ key }}{% endwith %}', {'dict': {'key': 50}}, '50'), - 'with-error01': ('{% with dict.key xx key %}{{ key }}{% endwith %}', {'dict': {'key':50}}, template.TemplateSyntaxError), - 'with-error02': ('{% with dict.key as %}{{ key }}{% endwith %}', {'dict': {'key':50}}, template.TemplateSyntaxError), + 'with02': ('{{ key }}{% with key=dict.key %}{{ key }}-{{ dict.key }}-{{ key }}{% endwith %}{{ key }}', {'dict': {'key': 50}}, ('50-50-50', 'INVALID50-50-50INVALID')), + 'legacywith02': ('{{ key }}{% with dict.key as key %}{{ key }}-{{ dict.key }}-{{ key }}{% endwith %}{{ key }}', {'dict': {'key': 50}}, ('50-50-50', 'INVALID50-50-50INVALID')), + + 'with03': ('{% with a=alpha b=beta %}{{ a }}{{ b }}{% endwith %}', {'alpha': 'A', 'beta': 'B'}, 'AB'), + + 'with-error01': ('{% with dict.key xx key %}{{ key }}{% endwith %}', {'dict': {'key': 50}}, template.TemplateSyntaxError), + 'with-error02': ('{% with dict.key as %}{{ key }}{% endwith %}', {'dict': {'key': 50}}, template.TemplateSyntaxError), ### NOW TAG ######################################################## # Simple case