- The include tag now has a 'with' option to include to provide extra context
  vairables to the included template.

- The include tag now has an 'only' option to exclude the current context
  when rendering the included template.

- The with tag now accepts multiple variable assignments.

- The with, include and blocktrans tags now use a new keyword argument format
  for variable assignments (e.g. `{% with foo=1 bar=2 %}`).

git-svn-id: http://code.djangoproject.com/svn/django/trunk@14922 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Chris Beaven 2010-12-18 02:50:26 +00:00
parent 99742d8d73
commit 3ae9117c46
8 changed files with 274 additions and 83 deletions

View File

@ -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):
@ -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 "<WithNode>"
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)

View File

@ -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)

View File

@ -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 = []

View File

@ -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

View File

@ -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 %}
<p>You have {{ emails|length }} email(s)</p>
{% for email in emails %}

View File

@ -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::
<form action="/comments/add/" method="post">
{% with comment_form as form %}
{% include "form_snippet.html" %}
{% endwith %}
{% include "form_snippet.html" with form=comment_form %}
<p><input type="submit" value="Submit comment" /></p>
</form>

View File

@ -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 <pluralization-var-notes>`
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

View File

@ -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'α &amp; β'),
'i18n18': ('{% load i18n %}{% blocktrans with anton|force_escape as berta %}{{ berta }}{% endblocktrans %}', {'anton': 'α & β'}, u'α &amp; β'),
'i18n17': ('{% load i18n %}{% blocktrans with berta=anton|escape %}{{ berta }}{% endblocktrans %}', {'anton': 'α & β'}, u'α &amp; β'),
'i18n18': ('{% load i18n %}{% blocktrans with berta=anton|force_escape %}{{ berta }}{% endblocktrans %}', {'anton': 'α & β'}, u'α &amp; β'),
'i18n19': ('{% load i18n %}{% blocktrans %}{{ andrew }}{% endblocktrans %}', {'andrew': 'a & b'}, u'a &amp; b'),
'i18n20': ('{% load i18n %}{% trans andrew %}', {'andrew': 'a & b'}, u'a &amp; 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'α &amp; β'),
'legacyi18n18': ('{% load i18n %}{% blocktrans with anton|force_escape as berta %}{{ berta }}{% endblocktrans %}', {'anton': 'α & β'}, u'α &amp; β'),
# 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,8 +1301,13 @@ 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'),
'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),