Commit 3ae9117c authored by Chris Beaven's avatar Chris Beaven
Browse files

Fixes #7817 and #9456.

- 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
parent 99742d8d
Loading
Loading
Loading
Loading
+85 −17
Original line number Diff line number Diff line
@@ -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)
+65 −16
Original line number Diff line number Diff line
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)
+38 −26
Original line number Diff line number Diff line
@@ -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.

    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 %}
    """
    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()
    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 subtag %s for 'blocktrans' found" % tag)
            return (countervar, counter, extra_context)
            raise TemplateSyntaxError('Unknown argument for %r tag: %r.' %
                                      (bits[0], option))
        options[option] = value

    countervar, counter, extra_context = BlockTranslateParser(token.contents).top()
    if 'count' in options:
        countervar, counter = options['count'].items()[0]
    else:
        countervar, counter = None, None
    extra_context = options.get('with', {}) 

    singular = []
    plural = []
+27 −3
Original line number Diff line number Diff line
@@ -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
+1 −1
Original line number Diff line number Diff line
@@ -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 %}
Loading