Having to remember to escape every variable node in an html template when it's the normal behaviour seems so un-DRY.

What I wanted was a way of adding the escape filter to every variable node (eg {{ object.name }}) without changing the core django code.
So I made a block tag which can add filters to all child variable nodes (finalfilter) and a filter for variable nodes which shouldn't be touched (finalized).

My current usage is to wrap my entire "base.html" with {% load filtertags %}{% finalfilter escape %} {% endfinalfilter %}.
If every I have a variable node which I don't want to escape, I just do {% load filtertags %}{{ already_escaped|finalized }}, but this isn't very often.

I also made a block tag to apply filters to each item of a list to work with the unordered_list filter (listfilter). This one is just a fringe-need because undordered_list is ugly and I don't know why anyone would want to use it, but it could be useful for other custom filters I guess.

Anyway, here's the docstring for the finalfilter block tag:
    Add common filters to all variable nodes within this block tag.
    Use the `finalized` filter in a variable node to skip adding the filters
    to that node. If a common filter has already been explicitly added to a
    variable node, it will *not* be added again.

    Filters can also be piped through each other, and they can have
    arguments -- just like in variable syntax.

    Note: This tag adds the filter(s) at render time so this happens across
    all extended block tags.

    Example usage::

        {% finalfilter escape %}
            {{ html_menu|finalized }}
            <h2>{{ object.company }}</h2>
            <p>
                <a href="" object.url }}">
                    {{ object.first_name }} {{ object.last_name }}
                </a>
            </p>
        {% endfinalfilter %}

    This example would add the escape filter to the end of each variable node
    except for `html_menu`.

--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups "Django developers" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at http://groups.google.com/group/django-developers
-~----------~----~----~----~------~----~------~--~---

from django.template import Library, Node, TemplateSyntaxError
from django.template import VariableNode, Parser, FilterExpression, FILTER_SEPARATOR

register = Library()

FINALIZED_FILTER_NAME = 'finalized'

class FinalFilterNode(Node):
    def __init__(self, nodelist, filters):
        self.nodelist = nodelist
        self.filters = filters

    def render(self, context):
        nodelist = self.nodelist
        for node in nodelist.get_nodes_by_type(VariableNode):
            node_filters = node.filter_expression.filters
            filter_funcs = [filter[0] for filter in node_filters]
            if finalized not in filter_funcs:
                # Ignore the node if it has the finalized functions in
                # its filters.
                for filter in self.filters:
                    if filter[0] not in filter_funcs:
                        # Don't double-up on filters which have already
                        # been applied to this VariableNode.
                        node_filters.append(filter)
                    
        return nodelist.render(context)

class ListFilterNode(Node):
    def __init__(self, context_list, filter_expr):
        self.context_list = context_list
        self.filter_expr = filter_expr
        
    def render(self, context):
        if context.get(self.context_list):
            context[self.context_list] = self.recursive_filter(context[self.context_list])
        return ''
    
    def recursive_filter(self, item):
        if hasattr(item, '__iter__'):
            # If the item is iterable then recurse.
            return map(self.recursive_filter, item)
        return self.filter_expr.resolve({self.filter_expr.var: item}, ignore_failures=True)

def finalfilter(parser, token):
    """
    Add common filters to all variable nodes within this block tag.
    Use the `finalized` filter in a variable node to skip adding the filters
    to that node. If a common filter has already been explicitly added to a
    variable node, it will *not* be added again.

    Filters can also be piped through each other, and they can have
    arguments -- just like in variable syntax.

    Note: This tag adds the filter(s) at render time so this happens across
    all extended block tags.

    Example usage::

        {% finalfilter escape %}
            {{ html_menu|finalized }}
            <h2>{{ object.company }}</h2>
            <p>
                <a href="{{ object.url }}">
                    {{ object.first_name }} {{ object.last_name }}
                </a>
            </p>
        {% endfinalfilter %}

    This example would add the escape filter to the end of each variable node
    except for `html_menu`.
    """
    nodelist = parser.parse(('endfinalfilter',))
    parser.delete_first_token()
    
    try:
        _, rest = token.contents.split(None, 1)
    except ValueError:
        raise TemplateSyntaxError, "'finalfilter' requires at least one filter"
    
    # Build the final filters list.
    filter_expression = parser.compile_filter('var%s%s' % (FILTER_SEPARATOR, rest))
    filters = filter_expression.filters

    return FinalFilterNode(nodelist, filters)
finalfilter = register.tag(finalfilter)

def listfilter(parser, token):
    """
    Replace a list in the context with one where each item has been processed
    by the given filters. This also works with nested lists.

    Filters can also be piped through each other, and they can have
    arguments -- just like in variable syntax.
    
    Example usage::

        list_menu = ['People', [['Bob & Anne', []]]]

        {% listfilter list_menu escape %}
        <ul>{{ list_menu|unordered_list}}</ul>

    This example would output::

        <ul><li>People
        <ul>
            <li>Bob &amp; Anne</li>
        </ul>
        </li></ul>
    """
    try:
        context_list, rest = token.contents.split(None, 2)[1:]
    except TypeError:
        raise TemplateSyntaxError, "'listfilter' requires at least one argument"
    
    filter_expr = parser.compile_filter('var%s%s' % (FILTER_SEPARATOR, rest))

    return ListFilterNode(context_list, filter_expr)
listfilter = register.tag(listfilter)

def finalized(value):
    """
    Filter used to cancel the effect of {% finalfilter %}, has no other effect.
    """
    return value
register.filter(FINALIZED_FILTER_NAME, finalized)

Reply via email to